ACL.php 28 KB

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