RefConfig.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. <?php
  2. Lib::loadClass('Type_Field');
  3. Lib::loadClass('Type_RefConfig');
  4. Lib::loadClass('Api_WfsNs');
  5. Lib::loadClass('ACL');
  6. Lib::loadClass('SchemaVersionUpgrade');
  7. /*
  8. RefStorage - for ref data manipulation
  9. RefConfig - for ref config
  10. `CRM_REF_CONFIG`:
  11. - `ID`: primaryKey (used to generate **Storage** name for data)
  12. - `ROOT_OBJECT_NS`: root object namespace
  13. - `CHILD_NAME`: child typeName (or alias name - not supported yet)
  14. - `CHILD_NS`: child namespace (required when use `CHILD_NAME` as alias)
  15. - `A_STATUS`: status
  16. - 'WAITING': not Active, 'NORMAL': Active, 'DELETED': removed
  17. - `VERSION`: **Storage** version
  18. - `A_LAST_ACTION_DATE`: last action timestamp
  19. - `SOURCE`: place where data is stored
  20. - 'table': table with data
  21. - 'view': view by flat relation cache
  22. - 'backRef': view on ref table with replaced fields
  23. **Storage** - table or view - place where data is stored
  24. RefConfig::isActive($objectNamespace, $childTypeName) // Ref is active when: CRM_REF_CONFIG.A_STATUS = NORMAL
  25. RefConfig::getRefTable($objectNamespace, $childTypeName) // ref table - TODO not needed?
  26. */
  27. class RefConfig {
  28. static $REF_TABLE_VERSION = 6;
  29. // TODO: add unique key to (PRIMARY_KEY, REMOTE_PRIMARY_KEY)
  30. // $REF_TABLE_VERSION = 4; // added ref event log table - `CRM__#REF_LOG__*`
  31. static function getVersion() {
  32. return self::$REF_TABLE_VERSION;
  33. }
  34. static function getToUpdateTotal() {
  35. return DB::getPDO()->fetchValue("
  36. select count(*) as cnt
  37. from `CRM_REF_CONFIG` c
  38. where c.A_STATUS = 'NORMAL' and c.VERSION < :version
  39. and c.SOURCE in ( 'table', 'view', 'backRef' )
  40. ", [
  41. ':version' => self::$REF_TABLE_VERSION,
  42. ]);
  43. }
  44. static function getToUpdateItems($type = 'table', $limit = 0, $idLast = 0) {
  45. $sqlAndWhere = "";
  46. $sqlLimit = "";
  47. if ($limit > 0) { // && $idLast > 0) {
  48. $sqlAndWhere .= " and c.ID > {$idLast} ";
  49. $sqlLimit .= " order by c.ID ASC limit {$limit} ";
  50. }
  51. switch ($type) {
  52. case 'backRef': $sqlAndWhere .= " and c.SOURCE in ( 'backRef' ) "; break;
  53. default: $sqlAndWhere .= " and c.SOURCE in ( 'table', 'view' ) "; break;
  54. }
  55. return DB::getPDO()->fetchAll("
  56. select c.ID, c.ROOT_OBJECT_NS, c.CHILD_NAME, c.CHILD_NS
  57. from `CRM_REF_CONFIG` c
  58. where c.A_STATUS = 'NORMAL' and c.VERSION < :version {$sqlAndWhere}
  59. {$sqlLimit}
  60. ", [
  61. ':version' => self::$REF_TABLE_VERSION,
  62. ]);
  63. }
  64. static function backRefCheck() {
  65. // TODO: p_ID != NULL - parent Ref must exist
  66. // TODO: p_SOURCE != 'backRef' - only ( 'table', 'view' ) allowed as source ref
  67. // TODO: p_SOURCE = 'NORMAL'
  68. // whene p_SOURCE = 'WAITING' then update p_SOURCE to 'WAITING' too
  69. // whene p_SOURCE = 'DELETED' then update p_SOURCE to 'DELETED' too
  70. $backRefCheckSql = "
  71. select c.ID, c.SOURCE, c.A_STATUS, c.ROOT_OBJECT_NS, c.CHILD_NAME, c.CHILD_NS
  72. , p.ID as p_ID, p.SOURCE as p_SOURCE, p.A_STATUS as p_A_STATUS, p.ROOT_OBJECT_NS as p_ROOT_OBJECT_NS, p.CHILD_NAME as p_CHILD_NAME, p.CHILD_NS as p_CHILD_NS
  73. from `CRM_REF_CONFIG` c
  74. left join `CRM_REF_CONFIG` p on ( p.ROOT_OBJECT_NS = c.CHILD_NS and p.CHILD_NS = c.ROOT_OBJECT_NS )
  75. where c.A_STATUS = 'NORMAL' and c.SOURCE in ( 'backRef' )
  76. ";
  77. return DB::getPDO()->fetchAll($backRefCheckSql);
  78. }
  79. static function isActive($objectNamespace, $childTypeName) {
  80. $refInfo = self::fetch($objectNamespace, $childTypeName);
  81. return ('NORMAL' === $refInfo->status);
  82. }
  83. /** static function getRefConfig(Type_Namespace $rootObjectNamespace, Type_TypeName $childName, Type_Namespace $childNamespace = null): Type_RefConfig */
  84. static function getRefConfig($rootObjectNamespace, $childTypeName, $childNamespace = null) {
  85. static $cacheRefConfigs = array();
  86. $cacheKey = "{$rootObjectNamespace}/{$childTypeName}";
  87. if (array_key_exists($cacheKey, $cacheRefConfigs)) return $cacheRefConfigs[$cacheKey];
  88. $rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
  89. if (!($rootAcl instanceof AntAclBase)) throw new Exception("Ref allowed only for AntAcl objects");
  90. $fieldInfo = $rootAcl->_getField($childTypeName); // throws Exception if field not exists
  91. $refConfig = self::fetch($rootObjectNamespace, $childTypeName, $childNamespace);
  92. if ('WAITING' == $refConfig->status || $refConfig->version < self::$REF_TABLE_VERSION) {
  93. $typeField = Type_Field::build($fieldInfo);
  94. self::update($rootObjectNamespace, $childTypeName, $typeField, $refConfig);
  95. $refConfig = self::fetch($rootObjectNamespace, $childTypeName, $childNamespace);
  96. }
  97. $cacheRefConfigs[$cacheKey] = $refConfig;
  98. return $refConfig;
  99. }
  100. static function getRefConfigById($id) {
  101. $refInfo = DB::getPDO()->fetchFirst("
  102. select c.ID, c.A_STATUS, c.VERSION, c.SOURCE
  103. , c.ROOT_OBJECT_NS
  104. , c.CHILD_NAME
  105. from `CRM_REF_CONFIG` c
  106. where c.ID = :id
  107. ", [
  108. ':id' => $id,
  109. ]);
  110. if (!$refInfo['ID']) throw new Exception("Ref table not found in ref config table (id '{$id}')");
  111. $rootObjectNamespace = $refInfo['ROOT_OBJECT_NS'];
  112. $childTypeName = $refInfo['CHILD_NAME'];
  113. $rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
  114. if (!($rootAcl instanceof AntAclBase)) throw new Exception("Ref allowed only for AntAcl objects");
  115. $fieldInfo = $rootAcl->_getField($childTypeName); // throws Exception if field not exists
  116. $refConfig = Type_RefConfig::build($refInfo);
  117. if ('WAITING' == $refConfig->status || $refConfig->version < self::$REF_TABLE_VERSION) {
  118. $typeField = Type_Field::build($fieldInfo);
  119. self::update($rootObjectNamespace, $childTypeName, $typeField, $refConfig);
  120. $refConfig = self::fetch($rootObjectNamespace, $childTypeName, $childNamespace);
  121. }
  122. $cacheRefConfigs[$cacheKey] = $refConfig;
  123. return $refConfig;
  124. }
  125. /** static function fetch(Type_Namespace $rootObjectNamespace, Type_TypeName $childName, Type_Namespace $childNamespace = null): Type_RefConfig */
  126. static function fetch($rootObjectNamespace, $childName, $childNamespace = null) { // @returns Type_RefConfig
  127. SchemaVersionUpgrade::upgradeSchema();
  128. $rootObjectNamespace = ACL::getBaseNamespace($rootObjectNamespace);
  129. $rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
  130. if (!($rootAcl instanceof AntAclBase)) throw new Exception("Ref allowed only for AntAcl objects");
  131. // $fieldInfo = $rootAcl->_getField($childName); // throws Exception if field not exists
  132. $childFieldXsdType = $rootAcl->getXsdFieldType($childName); // throws Exception if field not exists
  133. if (!$childNamespace && false !== strpos($childName, '__x3A__') && false !== strpos($childName, ':')) $childNamespace = Api_WfsNs::namespaceFromTypeName($childName);
  134. if (!$childNamespace) {
  135. $childXsdType = $rootAcl->getXsdFieldType($childName);
  136. list($typePrefix, $childTypeName) = explode(':', $childXsdType, 2);
  137. $childNamespace = Api_WfsNs::namespaceFromTypeName($childTypeName);
  138. DBG::log(['$childXsdType' => $childXsdType, '$typePrefix' => $typePrefix, '$childNamespace' => $childNamespace], 'array', "DBG get ref table ...");
  139. switch ($typePrefix) {
  140. case 'ref_uri': $childAcl = ACL::getAclByNamespace($childNamespace); break;
  141. case 'ref': $childAcl = ACL::getAclByTypeName($childNamespace); break;
  142. default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
  143. }
  144. }
  145. $refInfo = DB::getPDO()->fetchFirst("
  146. select c.ID, c.A_STATUS, c.VERSION, c.SOURCE
  147. from `CRM_REF_CONFIG` c
  148. where c.ROOT_OBJECT_NS = :ROOT_OBJECT_NS
  149. and c.CHILD_NAME = :CHILD_NAME
  150. and c.CHILD_NS = :CHILD_NS
  151. ", [
  152. ':ROOT_OBJECT_NS' => $rootObjectNamespace,
  153. ':CHILD_NAME' => $childName,
  154. ':CHILD_NS' => $childNamespace,
  155. ]);
  156. if (empty($refInfo)) {
  157. $refInfo = [ 'ID' => 0, 'A_STATUS' => 'WAITING', 'VERSION' => 1, 'SOURCE' => 'table',
  158. 'ROOT_OBJECT_NS' => $rootObjectNamespace,
  159. 'CHILD_NAME' => $childName,
  160. 'CHILD_NS' => $childNamespace,
  161. ];
  162. $refInfo['ID'] = DB::getPDO()->insert("CRM_REF_CONFIG", [
  163. 'ROOT_OBJECT_NS' => $rootObjectNamespace,
  164. 'CHILD_NAME' => $childName,
  165. 'CHILD_NS' => $childNamespace,
  166. 'VERSION' => 1 // need update @see getRefConfig - require update SOURCE if needed
  167. ]);
  168. }
  169. if (!$refInfo['ID']) throw new Exception("Ref table not found in ref config table for field '{$childName}' in object '{$rootObjectNamespace}'");
  170. return Type_RefConfig::build($refInfo);
  171. }
  172. static function getChildRefFullList($namespace) {
  173. $namespace = ACL::getBaseNamespace($namespace);
  174. if (!$namespace) throw new Exception("Missing namespace");
  175. return array_map('Type_RefConfig::build', DB::getPDO()->fetchAll("
  176. select c.ID
  177. , c.A_STATUS
  178. , c.SOURCE
  179. , c.CHILD_NAME
  180. from CRM_REF_CONFIG c
  181. where c.ROOT_OBJECT_NS = :namespace
  182. -- and c.A_STATUS = 'NORMAL'
  183. ", [
  184. ':namespace' => $namespace,
  185. ]));
  186. }
  187. static function isRefField($objectNamespace, $fieldName) {
  188. return (false !== strpos($fieldName, ':'));
  189. }
  190. /*
  191. @param $appInfo = [
  192. [type] => ref:default_db__x3A__BI_audit_CEIDG:BI_audit_CEIDG
  193. [minOccurs] => 0
  194. [maxOccurs] => unbounded
  195. [restrictions] => []
  196. [appInfo] => []
  197. ]
  198. */
  199. static function needUpdate($objectNamespace, $childTypeName, Type_Field $newField, Type_Field $oldField = null) {
  200. DBG::log(['objectNamespace' => $objectNamespace, 'childTypeName' => $childTypeName, 'newField' => $newField, 'oldField' => $oldField], 'array', "RefConfig::needUpdate...");
  201. if (!$oldField) throw new Exception("Missig oldField in RefConfig::needUpdate({$objectNamespace}, {$childTypeName}, ...) - TODO: fetch from #acl cache");
  202. if (!($newField instanceof Type_Field_Ref)) return false;
  203. if (!($oldField instanceof Type_Field_Ref)) return false;
  204. $oldRefActive = self::isActive($objectNamespace, $childTypeName);
  205. if (!$oldRefActive) return false; // if old not installed / adtivated then just fix struct and install
  206. $newRefSource = $newField->source;
  207. $oldRefConf = self::fetch($objectNamespace, $childTypeName);
  208. $oldRefSource = $oldRefConf->source;
  209. if ($newRefSource !== $oldRefSource) DBG::log("RefConfig::needUpdate Change ref source from '{$oldRefSource}' to '{$newRefSource}'");
  210. if ($newRefSource !== $oldRefSource) return true;
  211. return false;
  212. }
  213. static function update($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
  214. if (!($newField instanceof Type_Field_Ref)) return;
  215. // $oldRefActive = self::isActive($objectNamespace, $childTypeName);
  216. // if (!$oldRefActive) return; // if old not installed / adtivated then just fix struct and install
  217. $newRefSource = $newField->source;
  218. $refConfig = ($refConfig) ? $refConfig : self::fetch($objectNamespace, $childTypeName);
  219. $oldRefSource = $refConfig->source;
  220. if ($newRefSource !== $oldRefSource) DBG::log("RefConfig::update Change ref source from '{$oldRefSource}' to '{$newRefSource}'");
  221. // always update ref config at reinstall - drop / create ref tables (table or view)
  222. if ($refConfig->version < 4) self::installEventLogTable($objectNamespace, $childTypeName, $newField, $refConfig);
  223. // if ($refConfig->version < 5) {
  224. // Lib::loadClass('RefConfig_UpdateToVersion5');
  225. // RefConfig_UpdateToVersion4::updateToVersion5($objectNamespace, $childTypeName, $newField, $refConfig);
  226. // }
  227. switch ($newRefSource) {
  228. case 'table': return self::installRefTable($objectNamespace, $childTypeName, $newField, $refConfig);
  229. case 'view': return self::installRefView($objectNamespace, $childTypeName, $newField, $refConfig);
  230. case 'backRef': return self::installBackRef($objectNamespace, $childTypeName, $newField, $refConfig);
  231. default: throw new Exception("Not Implemented ref source '{$newRefSource}'");
  232. }
  233. }
  234. static function createRefTable($objectNamespace, $childTypeName, Type_RefConfig $refConfig = null) {
  235. if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
  236. // TODO: check if table has data
  237. // TODO: if no data in table then DROP ?
  238. DB::getPDO()->execSql("
  239. CREATE TABLE IF NOT EXISTS `{$refConfig->tableName}` (
  240. `PRIMARY_KEY` int(11) NOT NULL
  241. , `REMOTE_PRIMARY_KEY` int(11) NOT NULL
  242. , `REMOTE_TYPENAME` varchar(255) NOT NULL DEFAULT ''
  243. , `A_STATUS` enum('WAITING', 'NORMAL', 'DELETED') NOT NULL DEFAULT 'WAITING'
  244. , `TRANSACTION_ID` int(11) NOT NULL
  245. , `A_LAST_ACTION_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
  246. , UNIQUE KEY `unique_ref` (`PRIMARY_KEY`,`REMOTE_PRIMARY_KEY`)
  247. , KEY `PRIMARY_KEY` (`PRIMARY_KEY`)
  248. , KEY `REMOTE_PRIMARY_KEY` (`REMOTE_PRIMARY_KEY`)
  249. , KEY `TRANSACTION_ID` (`TRANSACTION_ID`)
  250. ) ENGINE=MyISAM DEFAULT CHARSET=latin2 COMMENT='{$objectNamespace} #REF {$childTypeName}';
  251. ");
  252. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  253. 'A_STATUS' => "NORMAL",
  254. // 'VERSION' => self::$REF_TABLE_VERSION
  255. ]);
  256. self::upgradeRefTableFrom1to2($refConfig);
  257. // self::upgradeRefTableFrom4to5($refConfig);
  258. // self::upgradeRefTableFrom5to6($refConfig);
  259. }
  260. static function upgradeRefTableFrom1to2(Type_RefConfig $refConfig) { // TODO: rm ACL::upgradeRefConfigFrom1to2
  261. if (1 == $refConfig->version) {
  262. if ('table' === $refConfig->source && 'NORMAL' == $refConfig->status) {
  263. try {
  264. DB::getPDO()->execSql(" CREATE INDEX `TRANSACTION_ID` ON `{$refConfig->tableName}` (`TRANSACTION_ID`) ");
  265. } catch (Exception $e) {
  266. DBG::log($e);
  267. }
  268. }
  269. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  270. 'VERSION' => 2
  271. ]);
  272. }
  273. }
  274. // static function upgradeRefTableFrom4to5(Type_RefConfig $refConfig) { // TODO: rm ACL::upgradeRefConfigFrom1to2
  275. // if ($refConfig->version < 5) {
  276. // if ('table' === $refConfig->source && 'NORMAL' == $refConfig->status) {
  277. // try {
  278. // DB::getPDO()->execSql(" ALTER TABLE `{$refConfig->tableName}` ADD `REF_PARAMS` varchar(1024) DEFAULT '' ");
  279. // } catch (Exception $e) {
  280. // DBG::log($e);
  281. // }
  282. // }
  283. // $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  284. // 'VERSION' => 5
  285. // ]);
  286. // }
  287. // }
  288. // static function upgradeRefTableFrom5to6(Type_RefConfig $refConfig) { // TODO: rm ACL::upgradeRefConfigFrom1to2
  289. // if ($refConfig->version < 6) {
  290. // if ('table' === $refConfig->source && 'NORMAL' == $refConfig->status) {
  291. // try {
  292. // DB::getPDO()->execSql(" ALTER TABLE `{$refConfig->tableName}` DROP COLUMN `REF_PARAMS` ");
  293. // } catch (Exception $e) {
  294. // DBG::log($e);
  295. // }
  296. // }
  297. // }
  298. // $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  299. // 'VERSION' => 6
  300. // ]);
  301. // }
  302. static function installRefTable($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
  303. if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
  304. self::createRefTable($objectNamespace, $childTypeName, $refConfig);
  305. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  306. 'SOURCE' => "table",
  307. 'A_STATUS' => "NORMAL",
  308. 'VERSION' => self::$REF_TABLE_VERSION,
  309. ]);
  310. }
  311. static function installRefView($objectNamespace, $childTypeName, Type_Field $typeField, Type_RefConfig $refConfig = null) {
  312. if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
  313. Lib::loadClass('SchemaFactory');
  314. $item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($objectNamespace, [ 'propertyName' => '*,field' ]);
  315. $appInfo = (!empty($item['appInfo'])) ? @json_decode($item['appInfo'], $assoc = true) : null;
  316. $charset = (!empty($appInfo) && !empty($appInfo['table_structure']['@charset'])) ? $appInfo['table_structure']['@charset'] : null;
  317. $viewSelectSql = RefConfig::generateRefSelectSqlByFlatRelationCache($objectNamespace, $childTypeName, $typeField, $charset);
  318. $refTableViewName = Type_RefConfig::generateTableName($refConfig->id, 'view');
  319. DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableViewName}` AS {$viewSelectSql} ");
  320. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  321. 'SOURCE' => 'view',
  322. 'A_STATUS' => "NORMAL",
  323. 'VERSION' => self::$REF_TABLE_VERSION,
  324. ]);
  325. }
  326. static function installBackRef($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
  327. $viewSelectSql = self::generateRefSelectSqlByBackRef($objectNamespace, $childTypeName);
  328. if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
  329. $backRefTableViewName = Type_RefConfig::generateTableName($refConfig->id, 'backRef');
  330. try {
  331. DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$backRefTableViewName}` AS {$viewSelectSql} ");
  332. } catch (Exception $e) {
  333. DBG::log($e);
  334. if ('HY000' === $e->getCode()) { // SQLSTATE[HY000]: General error: 1271 Illegal mix of collations for operation ' IN '
  335. // Missing @charset=latin2 in `CRM_#CACHE_ACL_OBJECT`.`appInfo`
  336. // FIX: add xs:appInfo: <system_cache__appinfo:table_structure system_cache__appinfo:charset="latin2"/>
  337. }
  338. throw $e;
  339. }
  340. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  341. 'SOURCE' => 'backRef',
  342. 'A_STATUS' => "NORMAL",
  343. 'VERSION' => self::$REF_TABLE_VERSION,
  344. ]);
  345. }
  346. static function generateRefSelectSqlByBackRef($rootObjectNamespace, $childName) {
  347. // generate view which is select from {replaced(pk, remote pk) on ref table from backRef}
  348. // {
  349. // DBG::nicePrint($refInfo, "\$refInfo");
  350. // DBG::nicePrint($rootObjectNamespace, "\$rootObjectNamespace");
  351. // DBG::nicePrint($childName, "\$childName");
  352. // DBG::nicePrint($childNamespace, "\$childNamespace");
  353. // $replacedObjNs = Api_WfsNs::namespaceFromTypeName($childName);
  354. // $replacedChildName = Api_WfsNs::typeName($rootObjectNamespace);
  355. // DBG::nicePrint($replacedObjNs, "\$replacedObjNs");
  356. // DBG::nicePrint($replacedChildName, "\$replacedChildName");
  357. // return ACL::getRefTable($replacedObjNs, $replacedChildName, 1);
  358. // throw new Exception("Not Implemented ref SOURCE = '{$refInfo['SOURCE']}'");
  359. // }
  360. $childNs = Api_WfsNs::namespaceFromTypeName($childName);
  361. $rootTypeName = Api_WfsNs::typeName($rootObjectNamespace);
  362. // $refConfig = self::getRefConfig($childNs, $rootTypeName); // NOTE: Uwaga getRefConfig recurence loop
  363. $refConfig = self::fetch($childNs, $rootTypeName);
  364. if ('WAITING' == $refConfig->status || $refConfig->version < self::$REF_TABLE_VERSION) {
  365. throw new Exception("Error: Install/Update ref table from {$childNs} to {$rootTypeName} first");
  366. }
  367. $backRefTable = $refConfig->tableName;
  368. DBG::log($backRefTable, 'array', "ACL::getRefTable({$childNs}, {$rootTypeName})");
  369. // TODO: check if ref_config is not backRef to avoid loop // $refInfo = self::getRefConfig($fieldNs, $item['typeName'], $item['typeName']);
  370. $lastActionDateField = "NULL"; // , IF(l.A_RECORD_UPDATE_DATE > r.A_RECORD_UPDATE_DATE, l.A_RECORD_UPDATE_DATE, r.A_RECORD_UPDATE_DATE) as A_LAST_ACTION_DATE
  371. $sql = "
  372. select backRef.REMOTE_PRIMARY_KEY as PRIMARY_KEY
  373. , backRef.PRIMARY_KEY as REMOTE_PRIMARY_KEY
  374. , backRef.REMOTE_TYPENAME as REMOTE_TYPENAME
  375. , backRef.A_STATUS as A_STATUS
  376. , 0 as TRANSACTION_ID
  377. , {$lastActionDateField} as A_LAST_ACTION_DATE
  378. from `{$backRefTable}` backRef
  379. ";
  380. DBG::log($sql, 'sql', "generateRefSelectSqlByBackRef");
  381. return $sql;
  382. }
  383. static function generateRefSelectSqlByFlatRelationCache($rootObjectNamespace, $childName, Type_Field $typeField, $charset = 'utf8') { // CRM_REF_CONFIG
  384. $appInfo = $typeField->appInfo;
  385. if (empty($appInfo)) throw new Exception("Empty app:info for field '{$rootObjectNamespace}/{$childName}'");
  386. DBG::log(['$appInfo'=>$appInfo, '$rootObjectNamespace'=>$rootObjectNamespace, '$childName'=>$childName], 'array', "\$appInfo");
  387. $rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
  388. $childXsdType = $rootAcl->getXsdFieldType($childName);
  389. list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
  390. switch ($typePrefix) {
  391. case 'ref_uri': $childAcl = ACL::getAclByNamespace($childNamespace); break;
  392. case 'ref': $childAcl = ACL::getAclByTypeName($childNamespace); break;
  393. default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
  394. }
  395. $lastActionDateField = "NULL"; // , IF(l.A_RECORD_UPDATE_DATE > r.A_RECORD_UPDATE_DATE, l.A_RECORD_UPDATE_DATE, r.A_RECORD_UPDATE_DATE) as A_LAST_ACTION_DATE
  396. $rootPrimaryKeyField = $rootAcl->getPrimaryKeyField();
  397. $childPrimaryKeyField = $childAcl->getPrimaryKeyField();
  398. $rootTableName = $rootAcl->getRootTableName();
  399. $childTableName = $childAcl->getRootTableName();
  400. // '$appInfo' => [
  401. // 'flat_relation_cache' => [
  402. // 'source' => [
  403. // '@name' => 'ID',
  404. // '@xpath' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK/ID_PROCES',
  405. // ),
  406. // ),
  407. // ),
  408. // '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
  409. // '$childName' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK',
  410. // '$appInfo' => [
  411. // 'flat_relation_cache' => [
  412. // 'source' => [
  413. // '@name' => 'ID',
  414. // '@xpath' => 'default_db__x3A__CRM_PROCES:PROCES/PARENT_ID',
  415. // ),
  416. // ),
  417. // ),
  418. // '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
  419. // '$childName' => 'default_db__x3A__CRM_PROCES:PROCES',
  420. $appInfoRootFieldName = null;
  421. $appInfoChildFieldName = null;
  422. {
  423. if (empty($appInfo['flat_relation_cache']['source']['@name'])) throw new Exception("Missing flat_relation_cache/source/@name");
  424. if (empty($appInfo['flat_relation_cache']['source']['@xpath'])) throw new Exception("Missing flat_relation_cache/source/@xpath");
  425. $appInfoName = $appInfo['flat_relation_cache']['source']['@name'];
  426. $appInfoXpath = $appInfo['flat_relation_cache']['source']['@xpath'];
  427. // $rootNs = $rootAcl->getNamespace()
  428. if ("{$childName}/" === substr($appInfoXpath, 0, strlen("{$childName}/"))) {
  429. $appInfoRootFieldName = substr($appInfoXpath, strlen("{$childName}/"));
  430. $appInfoChildFieldName = $appInfoName;
  431. } else {
  432. throw new Exception("TODO parse flat_relation_cache '{$rootObjectNamespace}' field '{$childName}'");
  433. }
  434. }
  435. if (!$appInfoRootFieldName || !$appInfoChildFieldName) throw new Exception("Error Processing flat_relation_cache");
  436. $sqlWhereFromRestrictions = [];
  437. DBG::log(['root'=>$rootAcl->getFields(), 'child'=>$childAcl->getFields()], 'array', "rootAcl and childAcl fields - xsdRestrictions");
  438. if ($rootAcl instanceof AntAclBase && $childAcl instanceof AntAclBase) {
  439. $rootLocalFieldsWithRestrictions = array_filter($rootAcl->getFields(), function ($field) {
  440. if (!$field['isLocal']) return false;
  441. if (empty($field['xsdRestrictions'])) return false;
  442. if ('[]' == $field['xsdRestrictions']) return false;
  443. return true;
  444. });
  445. $childLocalFieldsWithRestrictions = array_filter($childAcl->getFields(), function ($field) {
  446. if (!$field['isLocal']) return false;
  447. if (empty($field['xsdRestrictions'])) return false;
  448. if ('[]' == $field['xsdRestrictions']) return false;
  449. return true;
  450. });
  451. DBG::log(['root'=>$rootLocalFieldsWithRestrictions, 'child'=>$childLocalFieldsWithRestrictions], 'array', "root and child fields with xsdRestrictions");
  452. if (!empty($rootLocalFieldsWithRestrictions)) {
  453. $sqlTablePrefix = 'root';
  454. $sqlWhereFromRestrictions = array_reduce(
  455. array_map(function ($field) use ($sqlTablePrefix, $charset) {
  456. $sqlRestrictions = [];
  457. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  458. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  459. if (!empty($restrictions)) {
  460. if (!empty($restrictions['enumeration'])) {
  461. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map(function ($option) use ($charset) {
  462. return ($charset && $charset !== 'utf8')
  463. ? "CONVERT(" . DB::getPDO()->quote($option) . " using {$charset})"
  464. : DB::getPDO()->quote($option);
  465. }, array_keys($restrictions['enumeration']))) . ")";
  466. }
  467. if (array_key_exists('minInclusive', $restrictions)) {
  468. $minInclusive = (int)$restrictions['minInclusive'];
  469. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` > {$minInclusive}";
  470. }
  471. }
  472. return $sqlRestrictions;
  473. }, $rootLocalFieldsWithRestrictions),
  474. function ($ret, $cur) {
  475. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  476. },
  477. $sqlWhereFromRestrictions
  478. );
  479. }
  480. if (!empty($childLocalFieldsWithRestrictions)) {
  481. $sqlTablePrefix = 'child';
  482. $sqlWhereFromRestrictions = array_reduce(
  483. array_map(function ($field) use ($sqlTablePrefix, $charset) {
  484. $sqlRestrictions = [];
  485. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  486. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  487. if (!empty($restrictions)) {
  488. if (!empty($restrictions['enumeration'])) {
  489. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map(function ($option) use ($charset) {
  490. return ($charset && $charset !== 'utf8')
  491. ? "CONVERT(" . DB::getPDO()->quote($option) . " using {$charset})"
  492. : DB::getPDO()->quote($option);
  493. }, array_keys($restrictions['enumeration']))) . ")";
  494. }
  495. }
  496. return $sqlRestrictions;
  497. }, $childLocalFieldsWithRestrictions),
  498. function ($ret, $cur) {
  499. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  500. },
  501. $sqlWhereFromRestrictions
  502. );
  503. }
  504. }
  505. $sqlWhereFromRestrictions = (!empty($sqlWhereFromRestrictions)) ? implode("\n\t and ", $sqlWhereFromRestrictions) : "1=1";
  506. $sqlChildFieldName = $childAcl->getSqlFieldName($appInfoRootFieldName);
  507. $sql = "
  508. select root.{$rootPrimaryKeyField} as PRIMARY_KEY
  509. , child.{$childPrimaryKeyField} as REMOTE_PRIMARY_KEY
  510. , '' as REMOTE_TYPENAME
  511. , 'WAITING' as A_STATUS
  512. , 0 as TRANSACTION_ID
  513. , {$lastActionDateField} as A_LAST_ACTION_DATE
  514. from `{$rootTableName}` root
  515. join `{$childTableName}` child on(child.{$sqlChildFieldName} = root.{$appInfoChildFieldName})
  516. where {$sqlWhereFromRestrictions}
  517. ";
  518. DBG::log($sql, 'sql', "generateRefSelectSqlByFlatRelationCache");
  519. return $sql;
  520. }
  521. static function remove(Type_RefConfig $refConfig) {
  522. DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $refConfig->id, [
  523. 'A_STATUS' => 'DELETED',
  524. 'A_LAST_ACTION_DATE' => 'NOW()',
  525. ]);
  526. }
  527. static function reactivate(Type_RefConfig $refConfig) {
  528. DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $refConfig->id, [ // TODO: update ref table, update source -- fixed below by RefConfig::update
  529. 'A_STATUS' => 'WAITING',
  530. 'A_LAST_ACTION_DATE' => 'NOW()',
  531. ]);
  532. }
  533. static function getRefEventLogTable($objectNamespace, $childTypeName) {
  534. $refConfig = self::fetch($objectNamespace, $childTypeName);
  535. return "CRM__#REF_LOG__{$refConfig->id}";
  536. }
  537. static function installEventLogTable($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
  538. // $refConfig->id
  539. // $refConfig->source
  540. // $refConfig->version
  541. // $refConfig->tableName
  542. $sqlLogTableName = self::getRefEventLogTable($objectNamespace, $childTypeName);
  543. DB::getPDO()->execSql("
  544. CREATE TABLE IF NOT EXISTS `{$sqlLogTableName}` (
  545. `PRIMARY_KEY` int(11) NOT NULL
  546. , `REMOTE_PRIMARY_KEY` int(11) NOT NULL
  547. , `REMOTE_TYPENAME` varchar(255) NOT NULL DEFAULT ''
  548. , `A_STATUS` enum('WAITING', 'NORMAL', 'DELETED') NOT NULL DEFAULT 'WAITING'
  549. , `TRANSACTION_ID` int(11) NOT NULL
  550. , `A_ACTION_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
  551. , KEY `PRIMARY_KEY` (`PRIMARY_KEY`)
  552. , KEY `REMOTE_PRIMARY_KEY` (`REMOTE_PRIMARY_KEY`)
  553. , KEY `TRANSACTION_ID` (`TRANSACTION_ID`)
  554. ) ENGINE=MyISAM DEFAULT CHARSET=latin2 COMMENT='{$objectNamespace} #REF {$childTypeName}';
  555. ");
  556. }
  557. }