ACL.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. <?php
  2. Lib::loadClass('Core_AclHelper');
  3. Lib::loadClass('AntAclBase');
  4. class ACL {
  5. public static $REF_TABLE_VERSION = 2;
  6. /**
  7. * Ids List of Proces Init for given tabel (skip filters)
  8. */
  9. public static function getTableProcesInitIds($idTable) {
  10. $procesInitList = self::getTableProcesInitList($idTable);
  11. return array_keys($procesInitList);
  12. }
  13. /**
  14. * List of Proces Init for given table (skip filters)
  15. */
  16. public static function getTableProcesInitList($idTable) {
  17. $tableProcesInitList = array();
  18. $sqlIdProcesListSql = "
  19. select tpv.`ID_PROCES`
  20. from `CRM_PROCES_idx_TABLE_TO_PROCES_VIEW` tpv
  21. where tpv.`ID_TABLE`='{$idTable}'
  22. ";
  23. $fetchTableProcesInitListSql = "
  24. -- time ~0.07 -- no goto and return
  25. select p.`ID`, p.`DESC`
  26. from `CRM_PROCES` p
  27. where p.`ID` in(
  28. select i.`idx_PROCES_INIT_ID`
  29. from `CRM_PROCES_idx` i
  30. where i.`ID_PROCES` in({$sqlIdProcesListSql})
  31. )
  32. and p.`TYPE`='PROCES_INIT'
  33. order by p.`SORT_PRIO`
  34. ";
  35. /*
  36. SELECT p.`ID` , p.`DESC`
  37. FROM `CRM_PROCES` p
  38. WHERE p.`ID`
  39. IN (
  40. SELECT i.`idx_PROCES_INIT_ID`
  41. FROM `CRM_PROCES_idx` i
  42. WHERE i.`ID_PROCES`
  43. IN (
  44. SELECT tpv.`ID_PROCES`
  45. FROM `CRM_PROCES_idx_TABLE_TO_PROCES_VIEW` tpv
  46. WHERE tpv.`ID_TABLE` = '13051'
  47. )
  48. )
  49. AND p.`TYPE` = 'PROCES_INIT'
  50. order by p.`SORT_PRIO`
  51. */
  52. $fetchTableProcesInitListSql = "
  53. -- time ~0.15s
  54. select p.`ID`, p.`DESC`
  55. from `CRM_PROCES` p
  56. where p.`ID` in(
  57. select i.`idx_PROCES_INIT_ID`
  58. from `CRM_PROCES_idx` i
  59. where i.`ID_PROCES` in({$sqlIdProcesListSql})
  60. union
  61. select ig.`idx_PROCES_INIT_ID`
  62. from `CRM_PROCES_idx` i
  63. join `CRM_PROCES_idx` ig on(ig.`ID_PROCES`=i.`idx_PROCES_WITH_GROUPS_ID`)
  64. where i.`ID_PROCES` in({$sqlIdProcesListSql})
  65. )
  66. and p.`TYPE`='PROCES_INIT'
  67. order by p.`SORT_PRIO`
  68. ";
  69. $fetchTableProcesInitListSql = "
  70. -- time ~0.14
  71. select p.`ID`, p.`DESC`
  72. from `CRM_PROCES` p
  73. where p.`ID` in(
  74. select i.`idx_PROCES_INIT_ID`
  75. from `CRM_PROCES_idx` i
  76. where i.`ID_PROCES` in({$sqlIdProcesListSql})
  77. or i.`ID_PROCES` in(
  78. select ig.`idx_PROCES_WITH_GROUPS_ID`
  79. from `CRM_PROCES_idx` ig
  80. where ig.`ID_PROCES` in({$sqlIdProcesListSql})
  81. )
  82. )
  83. and p.`TYPE`='PROCES_INIT'
  84. order by p.`SORT_PRIO`
  85. ";
  86. return array_map(function ($row) {
  87. return $row['DESC'];
  88. }, DB::getPDO()->fetchAllByKey($fetchTableProcesInitListSql, 'ID'));
  89. }
  90. public static function getProcesInitMapTreeOnlyIds($ids) {
  91. $mapTree = array();
  92. $map = self::getProcesInitMapOnlyIds($ids);
  93. foreach ($map as $row) {
  94. if ('PROCES_INIT' == $row['TYPE']) {
  95. $mapTree[ $row['ID_PROCES'] ] = array();
  96. }
  97. }
  98. foreach ($map as $row) {
  99. if ('GOTO_AND_RETURN' == $row['TYPE']) {
  100. $mapTree[ $row['idx_MAIN_PROCES_INIT_ID'] ][ $row['ID_PROCES'] ] = array();
  101. }
  102. }
  103. foreach ($map as $row) {
  104. if ('GOTO_AND_RETURN_LVL2' == $row['TYPE']) {
  105. $mapTree[ $row['idx_MAIN_PROCES_INIT_ID'] ][ $row['idx_GOTO_LVL2_INIT_ID'] ][ $row['ID_PROCES'] ] = true;
  106. }
  107. }
  108. return $mapTree;
  109. }
  110. public static function getProcesInitMapOnlyIds($ids) {
  111. $map = array();
  112. $sqlIds = V::filter($ids, array('V', 'filterPositiveInteger'));
  113. $sqlIds = implode(',', $sqlIds);
  114. if (empty($sqlIds)) return $map;
  115. $sql = "
  116. select i.`ID_PROCES`
  117. , i.`PARENT_ID`
  118. , i.`TYPE`
  119. , i.`idx_PROCES_INIT_ID`
  120. , i.`idx_MAIN_PROCES_INIT_ID`
  121. , i.`idx_PROCES_WITH_GROUPS_ID`
  122. , IF(i.`TYPE`='GOTO_AND_RETURN_LVL2'
  123. , (select ig.`idx_PROCES_INIT_ID`
  124. from `CRM_PROCES_idx` ig
  125. where ig.`ID_PROCES`=i.`PARENT_ID`
  126. limit 1)
  127. , 0
  128. ) as idx_GOTO_LVL2_INIT_ID
  129. from `CRM_PROCES_idx` i
  130. where i.`ID_PROCES` in({$sqlIds})
  131. and i.`idx_MAIN_PROCES_INIT_ID` in({$sqlIds})
  132. ";
  133. return DB::getPDO()->fetchAll($sql);
  134. }
  135. public static function canGroupViewProces($idGroup, $idProcesInit) {
  136. $isAllowed = false;
  137. $idProcesInit = (int)$idProcesInit;
  138. if (!$idProcesInit) return false;
  139. $checkProcesAccessSql = "
  140. select count(*) as cnt
  141. from `CRM_PROCES_idx_GROUP_to_INIT_VIEW` giv
  142. where giv.`ID_GROUP` = '{$idGroup}'
  143. and giv.`ID_PROCES_INIT` = '{$idProcesInit}'
  144. ";
  145. return ( DB::getPDO()->fetchValue($checkProcesAccessSql) > 0 );
  146. }
  147. public static function getStorageByNamespace($namespace, $forceTblAclInit = false) {
  148. Lib::loadClass('Core_AclHelper');
  149. Lib::loadClass('SchemaFactory');
  150. $ns = Core_AclHelper::parseNamespaceUrl($namespace);
  151. DBG::log($ns, 'array', "parseNamespaceUrl({$namespace})");
  152. if ('default_db' == $ns['prefix']) {
  153. $acl = User::getAcl()->getObjectAcl($ns['prefix'], $ns['name']);
  154. } else if ('objects' == $ns['prefix']) {
  155. $acl = SchemaFactory::loadDefaultObject($ns['name']);
  156. } else if ('default_objects' == $ns['prefix']) {
  157. $acl = SchemaFactory::loadDefaultObject($ns['name']);
  158. } else if ('default_db__x3A__' == substr($ns['prefix'], 0, 17)) {
  159. $rootTableName = strtolower(substr($ns['prefix'], 17));
  160. $acl = SchemaFactory::loadTableObject($rootTableName, $ns['name']);
  161. } else {
  162. throw new HttpException("Not Implemented", 501);
  163. }
  164. $acl->init($forceTblAclInit);
  165. return $acl;
  166. }
  167. public static function getBaseNamespace($namespace) {
  168. // map SystemObjects__x3A__{parent}/{name} to default_objects/{name}
  169. if ('SystemObjects/' === substr($namespace, 0, strlen('SystemObjects/'))) {
  170. $exNs = explode('/', $namespace);
  171. if (3 === count($exNs)) {
  172. return "default_objects/{$exNs[2]}";
  173. }
  174. }
  175. return $namespace;
  176. }
  177. public static function getAclByNamespace($namespace, $forceTblAclInit = false) {
  178. $namespace = ACL::getBaseNamespace($namespace);
  179. return Core_AclHelper::getAclByNamespace($namespace, $forceTblAclInit);
  180. }
  181. public static function getAclByTypeName($typeName, $forceTblAclInit = false) {
  182. return Core_AclHelper::getAclByNamespace(str_replace(':', '/', $typeName), $forceTblAclInit);
  183. }
  184. public static function getNamespaceFromId($idZasob) {
  185. $sqlIdZasob = DB::getPDO()->quote($idZasob, PDO::PARAM_INT);
  186. $zasob = DB::getPDO()->fetchFirst("
  187. select z.ID, z.DESC, z.PARENT_ID
  188. from CRM_LISTA_ZASOBOW z
  189. where z.ID = {$sqlIdZasob}
  190. and z.`TYPE` = 'TABELA'
  191. and z.A_STATUS != 'DELETED'
  192. ");
  193. if (!$zasob) throw new Exception("Object not exists '{$idZasob}'");
  194. if ($zasob['PARENT_ID'] != DB::getPDO()->getZasobId()) {
  195. throw new Exception("TODO: getNamespaceFromId for remote database");
  196. }
  197. return ('default_db/' === substr($zasob['DESC'], 0, strlen('default_db/')))
  198. ? $zasob['DESC']
  199. : "default_db/{$zasob['DESC']}"
  200. ;
  201. }
  202. public static function parseNamespaceUrl($namespace) {// returns assoc array: [ 'name', 'url', 'prefix', 'sourceName' ]
  203. return Core_AclHelper::parseNamespaceUrl($namespace);
  204. }
  205. public static function getRefTable($rootObjectNamespace, $childName) { // CRM_REF_CONFIG
  206. static $cacheRefTables = array();
  207. DBG::log("DBG get ref table ({$rootObjectNamespace}, {$childName}) ...");
  208. $rootObjectNamespace = ACL::getBaseNamespace($rootObjectNamespace);
  209. $cacheKey = "{$rootObjectNamespace}/{$childName}";
  210. if (array_key_exists($cacheKey, $cacheRefTables)) return $cacheRefTables[$cacheKey];
  211. $rootAcl = self::getAclByNamespace($rootObjectNamespace);
  212. $childXsdType = $rootAcl->getXsdFieldType($childName);
  213. list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
  214. DBG::log(['$childXsdType' => $childXsdType, '$typePrefix' => $typePrefix, '$childNamespace' => $childNamespace], 'array', "DBG get ref table ...");
  215. switch ($typePrefix) {
  216. case 'ref_uri': $childAcl = self::getAclByNamespace($childNamespace); break;
  217. case 'ref': $childAcl = self::getAclByTypeName($childNamespace); break;
  218. default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
  219. }
  220. $refInfo = self::getRefConfig($rootObjectNamespace, $childName, $childNamespace);
  221. if ('view' === $refInfo['SOURCE']) {
  222. $refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}_VIEW"; // view created by ACL::generateRefSelectSqlByFlatRelationCache
  223. } else if ('backRef' === $refInfo['SOURCE']) {
  224. $refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}_VIEW"; // view created by ACL::generateRefSelectSqlByFlatRelationCache
  225. } else if ('table' === $refInfo['SOURCE']) {
  226. $refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}";
  227. if ('WAITING' == $refInfo['A_STATUS']) {
  228. DB::getPDO()->execSql("
  229. CREATE TABLE IF NOT EXISTS `{$refTableName}` (
  230. `PRIMARY_KEY` int(11) NOT NULL
  231. , `REMOTE_PRIMARY_KEY` int(11) NOT NULL
  232. , `REMOTE_TYPENAME` varchar(255) NOT NULL DEFAULT ''
  233. , `A_STATUS` enum('WAITING', 'NORMAL', 'DELETED') NOT NULL DEFAULT 'WAITING'
  234. , `TRANSACTION_ID` int(11) NOT NULL
  235. , `A_LAST_ACTION_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
  236. , KEY `PRIMARY_KEY` (`PRIMARY_KEY`)
  237. , KEY `REMOTE_PRIMARY_KEY` (`REMOTE_PRIMARY_KEY`)
  238. , KEY `TRANSACTION_ID` (`TRANSACTION_ID`)
  239. ) ENGINE=MyISAM DEFAULT CHARSET=latin2 COMMENT='{$rootObjectNamespace} #REF $childName ({$childNamespace})';
  240. ");
  241. $refInfo['A_STATUS'] = "NORMAL";
  242. $refInfo['VERSION'] = self::$REF_TABLE_VERSION;
  243. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refInfo['ID'], [
  244. 'A_STATUS' => $refInfo['A_STATUS'],
  245. 'VERSION' => $refInfo['VERSION']
  246. ]);
  247. }
  248. } else {
  249. throw new Exception("Not Implemented ref SOURCE = '{$refInfo['SOURCE']}'");
  250. }
  251. DBG::log($refInfo, 'array', "DBG XXXX");
  252. if ($refInfo['VERSION'] < self::$REF_TABLE_VERSION) {
  253. if (1 == $refInfo['VERSION']) $refInfo = self::upgradeRefConfigFrom1to2($refInfo);
  254. }
  255. if ($refInfo['VERSION'] < self::$REF_TABLE_VERSION) throw new Exception("TODO: ref table {$refInfo['ID']} require upgrade - field '{$childName}' in object '{$rootObjectNamespace}'");
  256. $cacheRefTables[$cacheKey] = $refTableName;
  257. return $refTableName;
  258. }
  259. public static function getRefSource($rootObjectNamespace, $childName) { // CRM_REF_CONFIG
  260. $rootObjectNamespace = ACL::getBaseNamespace($rootObjectNamespace);
  261. $refInfo = self::getRefConfig($rootObjectNamespace, $childName);
  262. return V::get('SOURCE', 'table', $refInfo);
  263. }
  264. public static function decodeAppInfoJson($appInfoJsonString) {
  265. $appInfo = @json_decode($appInfoJsonString, $assoc = true);
  266. if (null == $appInfo && 0 !== json_last_error()) throw new Exception("Parsing Json failed: " . json_last_error());
  267. return $appInfo;
  268. }
  269. public static function generateRefSelectSqlByBackRef($rootObjectNamespace, $childName) { // TODO: mv to generateRefSelectSqlByFlatRelationCache
  270. // TODO: generate view which is select from {replaced(pk, remote pk) on ref table from backRef}
  271. // {
  272. // DBG::nicePrint($refInfo, "\$refInfo");
  273. // DBG::nicePrint($rootObjectNamespace, "\$rootObjectNamespace");
  274. // DBG::nicePrint($childName, "\$childName");
  275. // DBG::nicePrint($childNamespace, "\$childNamespace");
  276. // $replacedObjNs = Api_WfsNs::namespaceFromTypeName($childName);
  277. // $replacedChildName = Api_WfsNs::typeName($rootObjectNamespace);
  278. // DBG::nicePrint($replacedObjNs, "\$replacedObjNs");
  279. // DBG::nicePrint($replacedChildName, "\$replacedChildName");
  280. // return ACL::getRefTable($replacedObjNs, $replacedChildName, 1);
  281. // throw new Exception("Not Implemented ref SOURCE = '{$refInfo['SOURCE']}'");
  282. // }
  283. $childNs = Api_WfsNs::namespaceFromTypeName($childName);
  284. $rootTypeName = Api_WfsNs::typeName($rootObjectNamespace);
  285. $backRefTable = ACL::getRefTable($childNs, $rootTypeName);
  286. DBG::nicePrint($backRefTable, "ACL::getRefTable({$childNs}, {$rootTypeName})");
  287. // TODO: check if ref_config is not backRef to avoid loop // $refInfo = self::getRefConfig($fieldNs, $item['typeName'], $item['typeName']);
  288. $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
  289. $sql = "
  290. select backRef.REMOTE_PRIMARY_KEY as PRIMARY_KEY
  291. , backRef.PRIMARY_KEY as REMOTE_PRIMARY_KEY
  292. , backRef.REMOTE_TYPENAME as REMOTE_TYPENAME
  293. , backRef.A_STATUS as A_STATUS
  294. , 0 as TRANSACTION_ID
  295. , {$lastActionDateField} as A_LAST_ACTION_DATE
  296. from `{$backRefTable}` backRef
  297. ";
  298. DBG::log($sql, 'sql', "generateRefSelectSqlByBackRef");
  299. return $sql;
  300. }
  301. public static function generateRefSelectSqlByFlatRelationCache($rootObjectNamespace, $childName) { // CRM_REF_CONFIG
  302. $appInfo = DB::getPDO()->fetchValue("
  303. select f.appInfo
  304. from `CRM_#CACHE_ACL_OBJECT_FIELD` f
  305. where f.objectNamespace = '{$rootObjectNamespace}'
  306. and f.fieldNamespace = '{$childName}'
  307. ");
  308. if (!$appInfo) throw new Exception("Missing app:info for field '{$rootObjectNamespace}/{$childName}'");
  309. $appInfo = ACL::decodeAppInfoJson($appInfo);
  310. if (empty($appInfo)) throw new Exception("Empty app:info for field '{$rootObjectNamespace}/{$childName}'");
  311. DBG::log(['$appInfo'=>$appInfo, '$rootObjectNamespace'=>$rootObjectNamespace, '$childName'=>$childName], 'array', "\$appInfo");
  312. $rootAcl = self::getAclByNamespace($rootObjectNamespace);
  313. $childXsdType = $rootAcl->getXsdFieldType($childName);
  314. list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
  315. switch ($typePrefix) {
  316. case 'ref_uri': $childAcl = self::getAclByNamespace($childNamespace); break;
  317. case 'ref': $childAcl = self::getAclByTypeName($childNamespace); break;
  318. default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
  319. }
  320. $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
  321. $rootPrimaryKeyField = $rootAcl->getPrimaryKeyField();
  322. $childPrimaryKeyField = $childAcl->getPrimaryKeyField();
  323. $rootTableName = $rootAcl->getRootTableName();
  324. $childTableName = $childAcl->getRootTableName();
  325. // '$appInfo' => [
  326. // 'flat_relation_cache' => [
  327. // 'source' => [
  328. // '@name' => 'ID',
  329. // '@xpath' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK/ID_PROCES',
  330. // ),
  331. // ),
  332. // ),
  333. // '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
  334. // '$childName' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK',
  335. // '$appInfo' => [
  336. // 'flat_relation_cache' => [
  337. // 'source' => [
  338. // '@name' => 'ID',
  339. // '@xpath' => 'default_db__x3A__CRM_PROCES:PROCES/PARENT_ID',
  340. // ),
  341. // ),
  342. // ),
  343. // '$rootObjectNamespace' => 'default_db/CRM_PROCES/PROCES',
  344. // '$childName' => 'default_db__x3A__CRM_PROCES:PROCES',
  345. $appInfoRootFieldName = null;
  346. $appInfoChildFieldName = null;
  347. {
  348. if (empty($appInfo['flat_relation_cache']['source']['@name'])) throw new Exception("Missing flat_relation_cache/source/@name");
  349. if (empty($appInfo['flat_relation_cache']['source']['@xpath'])) throw new Exception("Missing flat_relation_cache/source/@xpath");
  350. $appInfoName = $appInfo['flat_relation_cache']['source']['@name'];
  351. $appInfoXpath = $appInfo['flat_relation_cache']['source']['@xpath'];
  352. // $rootNs = $rootAcl->getNamespace()
  353. if ("{$childName}/" === substr($appInfoXpath, 0, strlen("{$childName}/"))) {
  354. $appInfoRootFieldName = substr($appInfoXpath, strlen("{$childName}/"));
  355. $appInfoChildFieldName = $appInfoName;
  356. } else {
  357. throw new Exception("TODO parse flat_relation_cache");
  358. }
  359. }
  360. if (!$appInfoRootFieldName || !$appInfoChildFieldName) throw new Exception("Error Processing flat_relation_cache");
  361. $sqlWhereFromRestrictions = [];
  362. DBG::log(['root'=>$rootAcl->getFields(), 'child'=>$childAcl->getFields()], 'array', "rootAcl and childAcl fields - xsdRestrictions");
  363. if ($rootAcl instanceof AntAclBase && $childAcl instanceof AntAclBase) {
  364. $rootLocalFieldsWithRestrictions = array_filter($rootAcl->getFields(), function ($field) {
  365. if (!$field['isLocal']) return false;
  366. if (empty($field['xsdRestrictions'])) return false;
  367. if ('[]' == $field['xsdRestrictions']) return false;
  368. return true;
  369. });
  370. $childLocalFieldsWithRestrictions = array_filter($childAcl->getFields(), function ($field) {
  371. if (!$field['isLocal']) return false;
  372. if (empty($field['xsdRestrictions'])) return false;
  373. if ('[]' == $field['xsdRestrictions']) return false;
  374. return true;
  375. });
  376. DBG::log(['root'=>$rootLocalFieldsWithRestrictions, 'child'=>$childLocalFieldsWithRestrictions], 'array', "root and child fields with xsdRestrictions");
  377. if (!empty($rootLocalFieldsWithRestrictions)) {
  378. $sqlTablePrefix = 'root';
  379. $sqlWhereFromRestrictions = array_reduce(
  380. array_map(function ($field) use ($sqlTablePrefix) {
  381. $sqlRestrictions = [];
  382. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  383. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  384. if (!empty($restrictions)) {
  385. if (!empty($restrictions['enumeration'])) {
  386. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
  387. }
  388. }
  389. return $sqlRestrictions;
  390. }, $rootLocalFieldsWithRestrictions),
  391. function ($ret, $cur) {
  392. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  393. },
  394. $sqlWhereFromRestrictions
  395. );
  396. }
  397. if (!empty($childLocalFieldsWithRestrictions)) {
  398. $sqlTablePrefix = 'child';
  399. $sqlWhereFromRestrictions = array_reduce(
  400. array_map(function ($field) use ($sqlTablePrefix) {
  401. $sqlRestrictions = [];
  402. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  403. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  404. if (!empty($restrictions)) {
  405. if (!empty($restrictions['enumeration'])) {
  406. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
  407. }
  408. }
  409. return $sqlRestrictions;
  410. }, $childLocalFieldsWithRestrictions),
  411. function ($ret, $cur) {
  412. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  413. },
  414. $sqlWhereFromRestrictions
  415. );
  416. }
  417. }
  418. $sqlWhereFromRestrictions = (!empty($sqlWhereFromRestrictions)) ? implode(" and ", $sqlWhereFromRestrictions) : "1=1";
  419. $sqlChildFieldName = $childAcl->getSqlFieldName($appInfoRootFieldName);
  420. $sql = "
  421. select root.{$rootPrimaryKeyField} as PRIMARY_KEY
  422. , child.{$childPrimaryKeyField} as REMOTE_PRIMARY_KEY
  423. , '' as REMOTE_TYPENAME
  424. , 'WAITING' as A_STATUS
  425. , 0 as TRANSACTION_ID
  426. , {$lastActionDateField} as A_LAST_ACTION_DATE
  427. from `{$rootTableName}` root
  428. join `{$childTableName}` child on(child.{$sqlChildFieldName} = root.{$appInfoChildFieldName})
  429. where {$sqlWhereFromRestrictions}
  430. ";
  431. DBG::log($sql, 'sql', "generateRefSelectSqlByFlatRelationCache");
  432. return $sql;
  433. }
  434. public static function setRefSource($rootObjectNamespace, $childName, $source, $viewSelectSql = null) { // CRM_REF_CONFIG
  435. if (!in_array($source, ['view', 'table', 'backRef'])) throw new Exception("Wrong param source - expected 'table', 'view' or 'backRef'");
  436. if ('view' === $source && !$viewSelectSql) throw new Exception("Missing create view sql");
  437. $refInfo = self::getRefConfig($rootObjectNamespace, $childName);
  438. if (true) { // if ($source != $refInfo['SOURCE']) {
  439. if ('view' === $source) {
  440. $refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}_VIEW";
  441. DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
  442. }
  443. if ('backRef' === $source) {
  444. $refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}_VIEW";
  445. DB::getPDO()->execSql(" CREATE OR REPLACE DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `{$refTableName}` AS {$viewSelectSql} ");
  446. }
  447. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refInfo['ID'], [
  448. 'SOURCE' => $source,
  449. ]);
  450. }
  451. }
  452. public static function getRefConfig($rootObjectNamespace, $childName, $childNamespace = null) { // CRM_REF_CONFIG
  453. $rootObjectNamespace = ACL::getBaseNamespace($rootObjectNamespace);
  454. if (!$childNamespace) {
  455. $rootAcl = self::getAclByNamespace($rootObjectNamespace);
  456. $childXsdType = $rootAcl->getXsdFieldType($childName);
  457. list($typePrefix, $childNamespace) = explode(':', $childXsdType, 2);
  458. DBG::log(['$childXsdType' => $childXsdType, '$typePrefix' => $typePrefix, '$childNamespace' => $childNamespace], 'array', "DBG get ref table ...");
  459. switch ($typePrefix) {
  460. case 'ref_uri': $childAcl = self::getAclByNamespace($childNamespace); break;
  461. case 'ref': $childAcl = self::getAclByTypeName($childNamespace); break;
  462. default: throw new Exception("Expected ref type for field '{$childName}' in object '{$rootObjectNamespace}'");
  463. }
  464. }
  465. $refInfo = [];// define $refInfo = [ ID, A_STATUS, VERSION ]
  466. try {// check that ref config table exists
  467. $sqlRootTableNs = DB::getPDO()->quote($rootObjectNamespace, PDO::PARAM_STR);
  468. $sqlChildName = DB::getPDO()->quote($childName, PDO::PARAM_STR);
  469. $sqlChildNamespace = DB::getPDO()->quote($childNamespace, PDO::PARAM_STR);
  470. $refInfo = DB::getPDO()->fetchFirst("
  471. select c.ID, c.A_STATUS, c.VERSION, c.SOURCE
  472. from `CRM_REF_CONFIG` c
  473. where c.ROOT_OBJECT_NS = {$sqlRootTableNs}
  474. and c.CHILD_NAME = {$sqlChildName}
  475. and c.CHILD_NS = {$sqlChildNamespace}
  476. ");
  477. } catch (Exception $e) {
  478. DB::getPDO()->execSql("
  479. CREATE TABLE IF NOT EXISTS `CRM_REF_CONFIG` (
  480. `ID` INT NOT NULL AUTO_INCREMENT
  481. , `ROOT_OBJECT_NS` VARCHAR(255) NOT NULL
  482. , `CHILD_NAME` VARCHAR(255) NOT NULL
  483. , `CHILD_NS` VARCHAR(255) NOT NULL
  484. , `A_STATUS` enum('WAITING', 'NORMAL', 'DELETED') NOT NULL DEFAULT 'WAITING'
  485. , `VERSION` int(11) NOT NULL DEFAULT 0
  486. , `A_LAST_ACTION_DATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
  487. , PRIMARY KEY (`ID`)
  488. ) ENGINE = MyISAM DEFAULT CHARSET=latin2;
  489. ");
  490. try {
  491. DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` ADD `SOURCE` enum('table', 'view') not null default 'table' ");
  492. } catch (Exception $e) {
  493. DBG::log($e);
  494. }
  495. }
  496. try {
  497. DB::getPDO()->execSql(" ALTER TABLE `CRM_REF_CONFIG` CHANGE `SOURCE` `SOURCE` enum('table', 'view', 'backRef') not null default 'table' ");
  498. } catch (Exception $e) {
  499. DBG::log($e);
  500. }
  501. if (empty($refInfo)) {
  502. $refInfo = [ 'ID' => 0, 'A_STATUS' => 'WAITING', 'VERSION' => 0, 'SOURCE' => 'table' ];
  503. $refInfo['ID'] = DB::getPDO()->insert("CRM_REF_CONFIG", [
  504. 'ROOT_OBJECT_NS' => $rootObjectNamespace,
  505. 'CHILD_NAME' => $childName,
  506. 'CHILD_NS' => $childNamespace
  507. ]);
  508. }
  509. // { // TODO: fix source if ref for `SystemObjects__x3A__*` and defined flat_relation_cache - move to AclReinstall?
  510. // if ('SystemObjects__x3A__' === substr($childNamespace, 0, strlen('SystemObjects__x3A__'))) {
  511. // if ('table' === $refInfo['SOURCE']) {
  512. // $toUpdate = [
  513. // 'SOURCE' => 'view',
  514. // 'A_STATUS' => 'WAITING',
  515. // ];
  516. // DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $refInfo['ID'], $toUpdate);
  517. // $refInfo = array_merge($refInfo, $toUpdate);
  518. // }
  519. // }
  520. // }
  521. if (!$refInfo['ID']) throw new Exception("Ref table not found in ref config table for field '{$childName}' in object '{$rootObjectNamespace}'");
  522. return $refInfo;
  523. }
  524. public static function upgradeRefConfigFrom1to2($refInfo) {
  525. if (1 == $refInfo['VERSION']) {
  526. if ('table' === $refInfo['SOURCE'] && 'NORMAL' == $refInfo['A_STATUS']) {
  527. $refTableName = "CRM__#REF_TABLE__{$refInfo['ID']}";
  528. try {
  529. DB::getPDO()->execSql(" CREATE INDEX `TRANSACTION_ID` ON `{$refTableName}` (`TRANSACTION_ID`) ");
  530. } catch (Exception $e) {
  531. DBG::log($e);
  532. }
  533. }
  534. $affected = DB::getPDO()->update("CRM_REF_CONFIG", 'ID', $refInfo['ID'], [
  535. 'VERSION' => 2
  536. ]);
  537. }
  538. return array_merge($refInfo, [
  539. 'VERSION' => 2
  540. ]);
  541. }
  542. public static function getBackRefList($namespace) {
  543. if (!$namespace) throw new Exception("Missing namespace");
  544. $nsParts = explode('/', $namespace);
  545. $typeName = array_pop($nsParts);
  546. $typeName = implode("__x3A__", $nsParts) . ":{$typeName}";
  547. return DB::getPDO()->fetchAll("
  548. select c.ROOT_OBJECT_NS as namespace
  549. , i.id as idInstance
  550. from CRM_REF_CONFIG c
  551. join CRM_INSTANCE_CONFIG i on ( i.namespace = c.ROOT_OBJECT_NS )
  552. where ( c.CHILD_NAME = :type_name or c.CHILD_NAME = :namespace )
  553. and c.A_STATUS = 'NORMAL'
  554. ", [
  555. ':type_name' => $typeName,
  556. ':namespace' => $namespace,
  557. ]);
  558. }
  559. public static function getChildRefFullList($namespace) {
  560. $namespace = ACL::getBaseNamespace($namespace);
  561. if (!$namespace) throw new Exception("Missing namespace");
  562. return DB::getPDO()->fetchAll("
  563. select c.CHILD_NAME as namespace
  564. , c.A_STATUS
  565. , c.ID
  566. , c.SOURCE
  567. from CRM_REF_CONFIG c
  568. where c.ROOT_OBJECT_NS = :namespace
  569. -- and c.A_STATUS = 'NORMAL'
  570. ", [
  571. ':namespace' => $namespace,
  572. ]);
  573. }
  574. public static function updateChildRefSource($id, $source) {
  575. if (!$id) throw new Exception("Missing id");
  576. if (!in_array($source, ['view', 'table', 'backRef'])) throw new Exception("Wrong source");
  577. return DB::getPDO()->update('CRM_REF_CONFIG', 'ID', $id, [
  578. 'SOURCE' => $source
  579. ]);
  580. }
  581. public static function fetchRefs($namespace, $childNamespace, $primaryKey, $params = []) { // TODO: $params: limit, total
  582. if (!$namespace) throw new Exception("Missing namespace");
  583. if (!$childNamespace) throw new Exception("Missing child namespace");
  584. if (!$primaryKey) throw new Exception("Missing primary key");
  585. throw new Exception("TODO: fetch refs from '{$namespace}' where primaryKey = '{$primaryKey}'");
  586. }
  587. public static function fetchBackRefs($namespace, $primaryKey, $parentNamespace, $params = []) { // TODO: $params: limit, total
  588. if (!$namespace) throw new Exception("Missing namespace");
  589. if (!$parentNamespace) throw new Exception("Missing parent namespace");
  590. if (!$primaryKey) throw new Exception("Missing primary key");
  591. $typeName = Api_WfsNs::typeName($namespace);
  592. $refTable = ACL::getRefTable($parentNamespace, $typeName);
  593. if (V::get('total', false, $params)) {
  594. return DB::getPDO()->fetchValue(" select count(*) as cnt from `{$refTable}` where REMOTE_PRIMARY_KEY = :primary_key and A_STATUS not in ('DELETED') ", [ ':primary_key' => $primaryKey ]);
  595. }
  596. throw new Exception("TODO: fetch back refs from '{$namespace}' where primaryKey({$primaryKey}) by refTable({$refTable})");
  597. }
  598. public static function generateIsInstanceFunctionBody($namespace, $item = null) {
  599. if (!$item) $item = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, [ 'propertyName' => '*,field' ]);
  600. if (!in_array( $item['_type'], [ 'AntAcl' ] )) return null;
  601. $sqlFunBody = " RETURN 1; ";
  602. $localFieldsWithRestrictions = array_filter($item['field'], function ($field) {
  603. if (!$field['isLocal']) return false;
  604. if (empty($field['xsdRestrictions'])) return false;
  605. if ('[]' == $field['xsdRestrictions']) return false;
  606. return true;
  607. });
  608. // TODO: get fields with minOccurs > 1 (may require select by ref)
  609. $sqlTablePrefix = 'root';
  610. $sqlWhereFromRestrictions = (!empty($localFieldsWithRestrictions))
  611. ? array_reduce(
  612. array_map(function ($field) use ($sqlTablePrefix) {
  613. $sqlRestrictions = [];
  614. // 'xsdRestrictions' => '{"enumeration":{"PROCES":"PROCES"}}',
  615. $restrictions = @json_decode($field['xsdRestrictions'], $assoc = true);
  616. if (!empty($restrictions)) {
  617. if (!empty($restrictions['enumeration'])) {
  618. $sqlRestrictions[] = "{$sqlTablePrefix}.`{$field['fieldNamespace']}` in (" . implode(",", array_map([DB::getPDO(), 'quote'], array_keys($restrictions['enumeration']))) . ")";
  619. }
  620. }
  621. return $sqlRestrictions;
  622. }, $localFieldsWithRestrictions),
  623. function ($ret, $cur) {
  624. return array_merge($ret, array_filter($cur, ['V', 'filterNotEmpty']));
  625. },
  626. []
  627. )
  628. : '';
  629. DBG::nicePrint($localFieldsWithRestrictions, "\$localFieldsWithRestrictions");
  630. DBG::nicePrint($sqlWhereFromRestrictions, "\$sqlWhereFromRestrictions");
  631. $sqlWhereFromRestrictions = (!empty($sqlWhereFromRestrictions)) ? implode(" and ", $sqlWhereFromRestrictions) : "1=1";
  632. $pkField = 'ID'; // TODO: primaryKeyField into SystemObject structure
  633. $rootTableName = $item['_rootTableName'];
  634. $sqlFunBody = (!empty($sqlWhereFromRestrictions))
  635. ? " RETURN IF(
  636. (select count(1) as cnt from `{$rootTableName}` root where root.`{$pkField}` = pk and {$sqlWhereFromRestrictions}) > 0
  637. , 1, 0)
  638. "
  639. : " RETURN 1; ";
  640. return $sqlFunBody;
  641. }
  642. public static function getInstanceId($namespace) {
  643. $conf = self::getInstanceConfig($namespace);
  644. return $conf['id'];
  645. }
  646. public static function getInstanceConfig($namespace) { // @returns { id, namespace, rootNamespace, idInstanceBase, _createdAt }
  647. try {
  648. $conf = self::fetchInstanceConfig($namespace);
  649. } catch (Exception $e) {
  650. DB::getPDO()->execSql("
  651. create table if not exists `CRM_INSTANCE_CONFIG` (
  652. `id` int(11) not null AUTO_INCREMENT,
  653. `namespace` varchar(255) NOT NULL DEFAULT '',
  654. `rootNamespace` varchar(255) NOT NULL DEFAULT '',
  655. `idInstanceBase` int(11) NOT NULL DEFAULT 0,
  656. `_createdAt` datetime NOT NULL,
  657. UNIQUE KEY `namespace` (`namespace`),
  658. KEY `rootNamespace` (`rootNamespace`),
  659. PRIMARY KEY (`id`)
  660. ) ENGINE=MyISAM DEFAULT CHARSET=latin2
  661. ");
  662. // TODO:?: `_tableInstalled` tinyint(1) not null default 0,
  663. $conf = self::fetchInstanceConfig($namespace);
  664. }
  665. if (!$conf) {
  666. $id = DB::getPDO()->insert("CRM_INSTANCE_CONFIG", [
  667. 'namespace' => $namespace,
  668. 'rootNamespace' => self::getRootNamespace($namespace),
  669. '_createdAt' => 'NOW()',
  670. ]);
  671. $conf = self::fetchInstanceConfig($namespace);
  672. }
  673. if (!$conf) throw new Exception("Instance not found in config table '{$namespace}'");
  674. return $conf;
  675. }
  676. public static function fetchInstanceConfig($namespace) {
  677. return DB::getPDO()->fetchFirst("
  678. select c.*
  679. from `CRM_INSTANCE_CONFIG` c
  680. where c.namespace = '{$namespace}'
  681. ");
  682. }
  683. public static function getRootNamespace($namespace) { // TODO: works only for relative urls! - mv to Acl->getRootNamespace
  684. Lib::loadClass('SchemaFactory');
  685. try {
  686. $objectItem = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace);
  687. } catch (Exception $e) {
  688. throw new Exception("Object not installed '{$namespace}'");
  689. }
  690. if (!$objectItem['isStructInstalled']) throw new Exception("Object structure not installed '{$namespace}'");
  691. if ($objectItem['idDatabase'] != DB::getPDO()->getZasobId()) {
  692. if ('StorageAcl' === $objectItem['_type']) {
  693. DBG::log("getRootNamespace...");
  694. return $objectItem['namespace'];
  695. }
  696. else {
  697. throw new Exception("Only default_db supported"); // TODO: support more Sources
  698. }
  699. }
  700. return "default_db/{$objectItem['_rootTableName']}";
  701. }
  702. public static function getNamespaceSiblings($namespace) {
  703. return array_map(function ($row) {
  704. return $row['namespace'];
  705. }, DB::getPDO()->fetchAll("
  706. select s.namespace
  707. from CRM_INSTANCE_CONFIG c
  708. join CRM_INSTANCE_CONFIG s on ( s.rootNamespace = c.rootNamespace and s.namespace != c.rootNamespace )
  709. where c.namespace = :namespace
  710. ", [
  711. 'namespace' => $namespace
  712. ]));
  713. }
  714. public static function getFeatureNamespaces($namespace, $pk) {
  715. $instanceTable = self::getInstanceTable($namespace);
  716. return array_map(function ($row) {
  717. return $row['namespace'];
  718. }, DB::getPDO()->fetchAll("
  719. select c.namespace
  720. from `{$instanceTable}` i
  721. join `CRM_INSTANCE_CONFIG` c on ( c.id = i.idInstance )
  722. where i.pk = :pk
  723. ", [
  724. 'pk' => $pk,
  725. ]));
  726. }
  727. public static function getInstanceTable($namespace) { // @returns tableName with struct { pk, idInstance, _createdAt }
  728. $conf = self::getInstanceConfig($namespace);
  729. if (!empty($conf['idInstanceBase'])) return "CRM__#INSTANCE_TABLE__{$conf['idInstanceBase']}";
  730. $rootNs = $conf['rootNamespace'];
  731. $rootConf = self::getInstanceConfig($rootNs);
  732. $instanceTableName = "CRM__#INSTANCE_TABLE__{$rootConf['id']}";
  733. if (!empty($rootConf['idInstance'])) {
  734. $affected = DB::getPDO()->update("CRM_INSTANCE_CONFIG", 'rootNamespace', $rootNs, [
  735. 'idInstanceBase' => $rootConf['id']
  736. ]);
  737. return $instanceTableName;
  738. }
  739. // TODO: fetch primaryKeyType - TODO: store primaryKey and primaryKeyType in SystemObject item
  740. $pkType = 'int';
  741. DB::getPDO()->exec("
  742. CREATE TABLE IF NOT EXISTS `{$instanceTableName}` (
  743. `pk` int(11) NOT NULL COMMENT 'primary key'
  744. , `idInstance` int(11) NOT NULL
  745. , `_createdAt` datetime NOT NULL
  746. , KEY `pk` (`pk`)
  747. , KEY `idInstance` (`idInstance`)
  748. ) ENGINE=MyISAM DEFAULT CHARSET=latin2 COMMENT='{$rootNs} #INSTANCE';
  749. ");
  750. $affected = DB::getPDO()->update("CRM_INSTANCE_CONFIG", 'rootNamespace', $rootNs, [
  751. 'idInstanceBase' => $rootConf['id']
  752. ]);
  753. return $instanceTableName;
  754. }
  755. // @params $from - ( ACL | tableName | namespace | etc... - only ACL)
  756. public static function query($from, $prefix = 't') {
  757. Lib::loadClass('AclQueryBuilder');
  758. $query = new AclQueryBuilder();
  759. $query->from($from, $prefix);
  760. return $query;
  761. }
  762. /**
  763. * @param mixed $object - Core_AclBase or string - namespace
  764. * @return Core_AclFields
  765. */
  766. public static function getObjectFields($object) {
  767. // TODO: try to get structure from `CRM_#CACHE_ACL_OBJECT_FIELD`
  768. // if ($object is instance Core_AclBase) {
  769. // if ($object->isStructInstalled) then get structure from `CRM_#CACHE_ACL_OBJECT_FIELD` and put into Core_AclFields
  770. // else get from $object->getFields() and put into Core_AclFields
  771. }
  772. public static function canUserReadObject($idUser, $aclOrIdZasob) {
  773. throw new Exception("TODO: canUserReadObjec({$idUser}, {$aclOrIdZasob})");
  774. }
  775. public static function canUserCreateObject($idUser, $aclOrIdZasob) {
  776. throw new Exception("TODO: canUserCreateObjec({$idUser}, {$aclOrIdZasob})");
  777. }
  778. public static function canUserWriteObject($idUser, $aclOrIdZasob) {
  779. throw new Exception("TODO: canUserWriteObjec({$idUser}, {$aclOrIdZasob})");
  780. }
  781. public static function canUserReadObjectField($idUser, $aclOrIdZasob, $fieldNameOrXPath) {
  782. throw new Exception("TODO: canUserReadObjectFiel({$idUser}, {$aclOrIdZasob}, {$fieldNameOrXPath})");
  783. }
  784. public static function canUserCreateObjectField($idUser, $aclOrIdZasob, $fieldNameOrXPath) {
  785. throw new Exception("TODO: canUserCreateObjectFiel({$idUser}, {$aclOrIdZasob}, {$fieldNameOrXPath})");
  786. }
  787. public static function canUserWriteObjectField($idUser, $aclOrIdZasob, $fieldNameOrXPath) {
  788. throw new Exception("TODO: canUserWriteObjectFiel({$idUser}, {$aclOrIdZasob}, {$fieldNameOrXPath})");
  789. }
  790. // TODO: replace below:
  791. // AclBase->canCreateField
  792. // AclBase->canReadField
  793. // AclBase->canReadObjectField
  794. // AclBase->canWriteField
  795. // AclBase->canWriteObjectField
  796. // AclBase->canWriteRecord
  797. // AclBase->canReadRecord
  798. }