RefConfig.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <?php
  2. Lib::loadClass('Type_Field');
  3. Lib::loadClass('Type_RefConfig');
  4. Lib::loadClass('Api_WfsNs');
  5. Lib::loadClass('ACL');
  6. /*
  7. RefStorage - for ref data manipulation
  8. RefConfig - for ref config
  9. `CRM_REF_CONFIG`:
  10. - `ID`: primaryKey (used to generate **Storage** name for data)
  11. - `ROOT_OBJECT_NS`: root object namespace
  12. - `CHILD_NAME`: child typeName (or alias name - not supported yet)
  13. - `CHILD_NS`: child namespace (required when use `CHILD_NAME` as alias)
  14. - `A_STATUS`: status
  15. - 'WAITING': not Active, 'NORMAL': Active, 'DELETED': removed
  16. - `VERSION`: **Storage** version
  17. - `A_LAST_ACTION_DATE`: last action timestamp
  18. - `SOURCE`: place where data is stored
  19. - 'table': table with data
  20. - 'view': view by flat relation cache
  21. - 'backRef': view on ref table with replaced fields
  22. **Storage** - table or view - place where data is stored
  23. RefConfig::isActive($objectNamespace, $childTypeName) // Ref is active when: CRM_REF_CONFIG.A_STATUS = NORMAL
  24. RefConfig::getRefTable($objectNamespace, $childTypeName) // ref table - TODO not needed?
  25. */
  26. class RefConfig {
  27. static function isActive($objectNamespace, $childTypeName) {
  28. $refInfo = self::fetch($objectNamespace, $childTypeName);
  29. return ('NORMAL' === $refInfo->status);
  30. }
  31. static function fetch($rootObjectNamespace, $childName, $childNamespace = null) {
  32. if (!$childTypeName) $childTypeName = Api_WfsNs::namespaceFromTypeName($fieldName);
  33. $rootObjectNamespace = ACL::getBaseNamespace($rootObjectNamespace);
  34. if (!$childNamespace) {
  35. $rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
  36. $childXsdType = $rootAcl->getXsdFieldType($childName);
  37. list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
  38. DBG::log(['$childXsdType' => $childXsdType, '$typePrefix' => $typePrefix, '$childNamespace' => $childNamespace], 'array', "DBG get ref table ...");
  39. switch ($typePrefix) {
  40. case 'ref_uri': $childAcl = ACL::getAclByNamespace($childNamespace); break;
  41. case 'ref': $childAcl = ACL::getAclByTypeName($childNamespace); break;
  42. default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
  43. }
  44. }
  45. $refInfo = [];// define $refInfo = [ ID, A_STATUS, VERSION ]
  46. try {// check that ref config table exists
  47. $sqlRootTableNs = DB::getPDO()->quote($rootObjectNamespace, PDO::PARAM_STR);
  48. $sqlChildName = DB::getPDO()->quote($childName, PDO::PARAM_STR);
  49. $sqlChildNamespace = DB::getPDO()->quote($childNamespace, PDO::PARAM_STR);
  50. $refInfo = DB::getPDO()->fetchFirst("
  51. select c.ID, c.A_STATUS, c.VERSION, c.SOURCE
  52. from `CRM_REF_CONFIG` c
  53. where c.ROOT_OBJECT_NS = {$sqlRootTableNs}
  54. and c.CHILD_NAME = {$sqlChildName}
  55. and c.CHILD_NS = {$sqlChildNamespace}
  56. ");
  57. } catch (Exception $e) {
  58. DB::getPDO()->execSql("
  59. CREATE TABLE IF NOT EXISTS `CRM_REF_CONFIG` (
  60. `ID` INT NOT NULL AUTO_INCREMENT
  61. , `ROOT_OBJECT_NS` VARCHAR(255) NOT NULL
  62. , `CHILD_NAME` VARCHAR(255) NOT NULL
  63. , `CHILD_NS` VARCHAR(255) NOT NULL
  64. , `A_STATUS` enum('WAITING', 'NORMAL', 'DELETED') NOT NULL DEFAULT 'WAITING'
  65. , `VERSION` int(11) NOT NULL DEFAULT 0
  66. , `A_LAST_ACTION_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
  67. , PRIMARY KEY (`ID`)
  68. ) ENGINE = MyISAM DEFAULT CHARSET=latin2;
  69. ");
  70. try {
  71. DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` ADD `SOURCE` enum('table', 'view') not null default 'table' ");
  72. } catch (Exception $e) {
  73. DBG::log($e);
  74. }
  75. }
  76. try {
  77. DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` CHANGE `SOURCE` `SOURCE` enum('table', 'view', 'backRef') not null default 'table' ");
  78. } catch (Exception $e) {
  79. DBG::log($e);
  80. }
  81. if (empty($refInfo)) {
  82. $refInfo = [ 'ID' => 0, 'A_STATUS' => 'WAITING', 'VERSION' => 0, 'SOURCE' => 'table' ];
  83. $refInfo['ID'] = DB::getPDO()->insert("CRM_REF_CONFIG", [
  84. 'ROOT_OBJECT_NS' => $rootObjectNamespace,
  85. 'CHILD_NAME' => $childName,
  86. 'CHILD_NS' => $childNamespace
  87. ]);
  88. }
  89. // { // TODO: fix source if ref for `SystemObjects__x3A__*` and defined flat_relation_cache - move to AclReinstall?
  90. // if ('SystemObjects__x3A__' === substr($childNamespace, 0, strlen('SystemObjects__x3A__'))) {
  91. // if ('table' === $refInfo['SOURCE']) {
  92. // $toUpdate = [
  93. // 'SOURCE' => 'view',
  94. // 'A_STATUS' => 'WAITING',
  95. // ];
  96. // DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $refInfo['ID'], $toUpdate);
  97. // $refInfo = array_merge($refInfo, $toUpdate);
  98. // }
  99. // }
  100. // }
  101. if (!$refInfo['ID']) throw new Exception("Ref table not found in ref config table for field '{$childName}' in object '{$rootObjectNamespace}'");
  102. return Type_RefConfig::build($refInfo);
  103. }
  104. static function getChildRefFullList($namespace) {
  105. $namespace = ACL::getBaseNamespace($namespace);
  106. if (!$namespace) throw new Exception("Missing namespace");
  107. return array_map('Type_RefConfig::build', DB::getPDO()->fetchAll("
  108. select c.ID
  109. , c.A_STATUS
  110. , c.SOURCE
  111. , c.CHILD_NAME
  112. from CRM_REF_CONFIG c
  113. where c.ROOT_OBJECT_NS = :namespace
  114. -- and c.A_STATUS = 'NORMAL'
  115. ", [
  116. ':namespace' => $namespace,
  117. ]));
  118. }
  119. static function isRefField($objectNamespace, $fieldName) {
  120. return (false !== strpos($fieldName, ':'));
  121. }
  122. /*
  123. @param $appInfo = [
  124. [type] => ref:default_db__x3A__BI_audit_CEIDG:BI_audit_CEIDG
  125. [minOccurs] => 0
  126. [maxOccurs] => unbounded
  127. [restrictions] => []
  128. [appInfo] => []
  129. ]
  130. */
  131. static function needUpdate($objectNamespace, $childTypeName, Type_Field $newField, Type_Field $oldField = null) {
  132. DBG::log(['objectNamespace' => $objectNamespace, 'childTypeName' => $childTypeName, 'newField' => $newField, 'oldField' => $oldField], 'array', "RefConfig::needUpdate...");
  133. if (!$oldField) throw new Exception("Missig oldField in RefConfig::needUpdate({$objectNamespace}, {$childTypeName}, ...) - TODO: fetch from #acl cache");
  134. if (!($newField instanceof Type_Field_Ref)) return false;
  135. if (!($oldField instanceof Type_Field_Ref)) return false;
  136. $oldRefActive = self::isActive($objectNamespace, $childTypeName);
  137. if (!$oldRefActive) return false; // if old not installed / adtivated then just fix struct and install
  138. $newRefSource = $newField->source;
  139. $oldRefConf = self::fetch($objectNamespace, $childTypeName);
  140. $oldRefSource = $oldRefConf->source;
  141. if ($newRefSource !== $oldRefSource) DBG::log("RefConfig::needUpdate Change ref source from '{$oldRefSource}' to '{$newRefSource}'");
  142. if ($newRefSource !== $oldRefSource) return true;
  143. return false;
  144. }
  145. static function update($objectNamespace, $childTypeName, Type_Field $newField) {
  146. if (!($newField instanceof Type_Field_Ref)) return;
  147. // $oldRefActive = self::isActive($objectNamespace, $childTypeName);
  148. // if (!$oldRefActive) return; // if old not installed / adtivated then just fix struct and install
  149. $newRefSource = $newField->source;
  150. $oldRefConf = self::fetch($objectNamespace, $childTypeName);
  151. $oldRefSource = $oldRefConf->source;
  152. if ($newRefSource !== $oldRefSource) DBG::log("RefConfig::update Change ref source from '{$oldRefSource}' to '{$newRefSource}'");
  153. { // always update ref config at reinstall - drop / create ref tables (table or view)
  154. switch ($newRefSource) {
  155. case 'table': return self::installRefTable($objectNamespace, $childTypeName, $newField, $oldRefConf);
  156. case 'view': return self::installRefView($objectNamespace, $childTypeName, $newField, $oldRefConf);
  157. case 'backRef': return self::installBackRef($objectNamespace, $childTypeName, $newField, $oldRefConf);
  158. }
  159. }
  160. if ($newRefSource !== $oldRefSource) {
  161. if ('table' === $oldRefSource) {
  162. // TODO: check if table has data
  163. // TODO: if no data in table then DROP ?
  164. }
  165. throw new Exception("TODO: RefConfig::update Change ref source from '{$oldRefSource}' to '{$newRefSource}' in '{$objectNamespace}'-&gt;'{$childTypeName}'");
  166. }
  167. }
  168. static function installRefTable($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
  169. if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
  170. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  171. 'SOURCE' => 'table',
  172. ]);
  173. }
  174. static function installRefView($objectNamespace, $childTypeName, Type_Field $typeField, Type_RefConfig $refConfig = null) {
  175. if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
  176. $viewSelectSql = RefConfig::generateRefSelectSqlByFlatRelationCache($objectNamespace, $childTypeName, $typeField);
  177. $refTableName = "CRM__#REF_TABLE__{$refConfig->id}_VIEW";
  178. DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
  179. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  180. 'SOURCE' => 'view',
  181. ]);
  182. }
  183. static function installBackRef($objectNamespace, $childTypeName, Type_Field $newField, Type_RefConfig $refConfig = null) {
  184. $viewSelectSql = self::generateRefSelectSqlByBackRef($objectNamespace, $childTypeName);
  185. if (!$refConfig) $refConfig = self::fetch($objectNamespace, $childTypeName);
  186. $refTableName = "CRM__#REF_TABLE__{$refConfig->id}_VIEW";
  187. DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
  188. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refConfig->id, [
  189. 'SOURCE' => 'backRef',
  190. ]);
  191. }
  192. static function generateRefSelectSqlByBackRef($rootObjectNamespace, $childName) {
  193. // generate view which is select from {replaced(pk, remote pk) on ref table from backRef}
  194. // {
  195. // DBG::nicePrint($refInfo, "\$refInfo");
  196. // DBG::nicePrint($rootObjectNamespace, "\$rootObjectNamespace");
  197. // DBG::nicePrint($childName, "\$childName");
  198. // DBG::nicePrint($childNamespace, "\$childNamespace");
  199. // $replacedObjNs = Api_WfsNs::namespaceFromTypeName($childName);
  200. // $replacedChildName = Api_WfsNs::typeName($rootObjectNamespace);
  201. // DBG::nicePrint($replacedObjNs, "\$replacedObjNs");
  202. // DBG::nicePrint($replacedChildName, "\$replacedChildName");
  203. // return ACL::getRefTable($replacedObjNs, $replacedChildName, 1);
  204. // throw new Exception("Not Implemented ref SOURCE = '{$refInfo['SOURCE']}'");
  205. // }
  206. $childNs = Api_WfsNs::namespaceFromTypeName($childName);
  207. $rootTypeName = Api_WfsNs::typeName($rootObjectNamespace);
  208. $backRefTable = ACL::getRefTable($childNs, $rootTypeName);
  209. DBG::nicePrint($backRefTable, "ACL::getRefTable({$childNs}, {$rootTypeName})");
  210. // TODO: check if ref_config is not backRef to avoid loop // $refInfo = self::getRefConfig($fieldNs, $item['typeName'], $item['typeName']);
  211. $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
  212. $sql = "
  213. select backRef.REMOTE_PRIMARY_KEY as PRIMARY_KEY
  214. , backRef.PRIMARY_KEY as REMOTE_PRIMARY_KEY
  215. , backRef.REMOTE_TYPENAME as REMOTE_TYPENAME
  216. , backRef.A_STATUS as A_STATUS
  217. , 0 as TRANSACTION_ID
  218. , {$lastActionDateField} as A_LAST_ACTION_DATE
  219. from `{$backRefTable}` backRef
  220. ";
  221. DBG::log($sql, 'sql', "generateRefSelectSqlByBackRef");
  222. return $sql;
  223. }
  224. static function generateRefSelectSqlByFlatRelationCache($rootObjectNamespace, $childName, Type_Field $typeField) { // CRM_REF_CONFIG
  225. $appInfo = $typeField->appInfo;
  226. if (empty($appInfo)) throw new Exception("Empty app:info for field '{$rootObjectNamespace}/{$childName}'");
  227. DBG::log(['$appInfo'=>$appInfo, '$rootObjectNamespace'=>$rootObjectNamespace, '$childName'=>$childName], 'array', "\$appInfo");
  228. $rootAcl = ACL::getAclByNamespace($rootObjectNamespace);
  229. $childXsdType = $rootAcl->getXsdFieldType($childName);
  230. list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
  231. switch ($typePrefix) {
  232. case 'ref_uri': $childAcl = ACL::getAclByNamespace($childNamespace); break;
  233. case 'ref': $childAcl = ACL::getAclByTypeName($childNamespace); break;
  234. default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
  235. }
  236. $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
  237. $rootPrimaryKeyField = $rootAcl->getPrimaryKeyField();
  238. $childPrimaryKeyField = $childAcl->getPrimaryKeyField();
  239. $rootTableName = $rootAcl->getRootTableName();
  240. $childTableName = $childAcl->getRootTableName();
  241. // '$appInfo' => [
  242. // 'flat_relation_cache' => [
  243. // 'source' => [
  244. // '@name' => 'ID',
  245. // '@xpath' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK/ID_PROCES',
  246. // ),
  247. // ),
  248. // ),
  249. // '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
  250. // '$childName' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK',
  251. // '$appInfo' => [
  252. // 'flat_relation_cache' => [
  253. // 'source' => [
  254. // '@name' => 'ID',
  255. // '@xpath' => 'default_db__x3A__CRM_PROCES:PROCES/PARENT_ID',
  256. // ),
  257. // ),
  258. // ),
  259. // '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
  260. // '$childName' => 'default_db__x3A__CRM_PROCES:PROCES',
  261. $appInfoRootFieldName = null;
  262. $appInfoChildFieldName = null;
  263. {
  264. if (empty($appInfo['flat_relation_cache']['source']['@name'])) throw new Exception("Missing flat_relation_cache/source/@name");
  265. if (empty($appInfo['flat_relation_cache']['source']['@xpath'])) throw new Exception("Missing flat_relation_cache/source/@xpath");
  266. $appInfoName = $appInfo['flat_relation_cache']['source']['@name'];
  267. $appInfoXpath = $appInfo['flat_relation_cache']['source']['@xpath'];
  268. // $rootNs = $rootAcl->getNamespace()
  269. if ("{$childName}/" === substr($appInfoXpath, 0, strlen("{$childName}/"))) {
  270. $appInfoRootFieldName = substr($appInfoXpath, strlen("{$childName}/"));
  271. $appInfoChildFieldName = $appInfoName;
  272. } else {
  273. throw new Exception("TODO parse flat_relation_cache");
  274. }
  275. }
  276. if (!$appInfoRootFieldName || !$appInfoChildFieldName) throw new Exception("Error Processing flat_relation_cache");
  277. $sqlWhereFromRestrictions = [];
  278. DBG::log(['root'=>$rootAcl->getFields(), 'child'=>$childAcl->getFields()], 'array', "rootAcl and childAcl fields - xsdRestrictions");
  279. if ($rootAcl instanceof AntAclBase && $childAcl instanceof AntAclBase) {
  280. $rootLocalFieldsWithRestrictions = array_filter($rootAcl->getFields(), function ($field) {
  281. if (!$field['isLocal']) return false;
  282. if (empty($field['xsdRestrictions'])) return false;
  283. if ('[]' == $field['xsdRestrictions']) return false;
  284. return true;
  285. });
  286. $childLocalFieldsWithRestrictions = array_filter($childAcl->getFields(), function ($field) {
  287. if (!$field['isLocal']) return false;
  288. if (empty($field['xsdRestrictions'])) return false;
  289. if ('[]' == $field['xsdRestrictions']) return false;
  290. return true;
  291. });
  292. DBG::log(['root'=>$rootLocalFieldsWithRestrictions, 'child'=>$childLocalFieldsWithRestrictions], 'array', "root and child fields with xsdRestrictions");
  293. if (!empty($rootLocalFieldsWithRestrictions)) {
  294. $sqlTablePrefix = 'root';
  295. $sqlWhereFromRestrictions = array_reduce(
  296. array_map(function ($field) use ($sqlTablePrefix) {
  297. $sqlRestrictions = [];
  298. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  299. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  300. if (!empty($restrictions)) {
  301. if (!empty($restrictions['enumeration'])) {
  302. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
  303. }
  304. }
  305. return $sqlRestrictions;
  306. }, $rootLocalFieldsWithRestrictions),
  307. function ($ret, $cur) {
  308. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  309. },
  310. $sqlWhereFromRestrictions
  311. );
  312. }
  313. if (!empty($childLocalFieldsWithRestrictions)) {
  314. $sqlTablePrefix = 'child';
  315. $sqlWhereFromRestrictions = array_reduce(
  316. array_map(function ($field) use ($sqlTablePrefix) {
  317. $sqlRestrictions = [];
  318. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  319. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  320. if (!empty($restrictions)) {
  321. if (!empty($restrictions['enumeration'])) {
  322. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
  323. }
  324. }
  325. return $sqlRestrictions;
  326. }, $childLocalFieldsWithRestrictions),
  327. function ($ret, $cur) {
  328. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  329. },
  330. $sqlWhereFromRestrictions
  331. );
  332. }
  333. }
  334. $sqlWhereFromRestrictions = (!empty($sqlWhereFromRestrictions)) ? implode(" and ", $sqlWhereFromRestrictions) : "1=1";
  335. $sqlChildFieldName = $childAcl->getSqlFieldName($appInfoRootFieldName);
  336. $sql = "
  337. select root.{$rootPrimaryKeyField} as PRIMARY_KEY
  338. , child.{$childPrimaryKeyField} as REMOTE_PRIMARY_KEY
  339. , '' as REMOTE_TYPENAME
  340. , 'WAITING' as A_STATUS
  341. , 0 as TRANSACTION_ID
  342. , {$lastActionDateField} as A_LAST_ACTION_DATE
  343. from `{$rootTableName}` root
  344. join `{$childTableName}` child on(child.{$sqlChildFieldName} = root.{$appInfoChildFieldName})
  345. where {$sqlWhereFromRestrictions}
  346. ";
  347. DBG::log($sql, 'sql', "generateRefSelectSqlByFlatRelationCache");
  348. return $sql;
  349. }
  350. }