AclQueryFeatures.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. <?php
  2. Lib::loadClass('ACL');
  3. Lib::loadClass('SqlQueryWhereBuilder');
  4. Lib::loadClass('ParseOgcFilter');
  5. Lib::loadClass('TableAcl');
  6. // usage: (Acl class)::buildQuery($params): return new AclQueryFeatures($this, $params);
  7. // (view): $queryFeatures = $acl->buildQuery($params);
  8. // (view): $total = $queryFeatures->getTotal();
  9. // (view): $items = $queryFeatures->getItems();
  10. // example: @see TableAcl, TableAjax
  11. // Special Filter Access - btns visible only if user don't have super access perms. If has, then will always see all rows.
  12. class AclQueryFeatures {
  13. public $_params;
  14. public $_acl;
  15. public $_query;
  16. public $_total;
  17. public $_legacyMode;
  18. public $_selectLocalFields; // TODO: leave here or move to AclQueryBuilder? use join for perf?
  19. public $_selectRemote; // TODO: ...
  20. public function __construct($acl, $params, $legacyMode = false) {
  21. $this->_acl = $acl;
  22. $this->_params = $params;
  23. $this->_query = null;
  24. $this->_total = null;
  25. $this->_legacyMode = $legacyMode;
  26. // TODO: _legacyMode = ($from instanceof simple schema or another programmed objects)
  27. $this->_selectLocalFields = null;
  28. $this->_selectRemote = null;
  29. }
  30. public function parseQueryValue($fieldName, $searchQuery, $fieldType = 'xsd:string') {
  31. if ('!NULL' === $searchQuery) return ['is not null', null];
  32. if ('IS NOT NULL' === $searchQuery) return ['is not null', null];
  33. if ('NULL' === $searchQuery) return ['is null', null];
  34. if ('IS NULL' === $searchQuery) return ['is null', null];
  35. switch ($fieldType) {
  36. case 'gml:PolygonPropertyType':
  37. case 'gml:PointPropertyType':
  38. case 'gml:LineStringPropertyType':
  39. case 'gml:GeometryPropertyType': return $this->_parseGeomQuery($searchQuery);
  40. // $sqlFilter = $this->_sqlValueForGeomField($fldName, $v, 't');
  41. // if ('_CSV_NUM' == substr($fldName, -8)) { // if ($this->isCsvNumericField($fldName)) { // TODO: xsd type - p5:csv_num
  42. // $sqlFilter = $this->_sqlValueForCsvNumericField($fldName, $v, 't');
  43. // if ($sqlFilter) $sql_where_and[] = $sqlFilter;
  44. // continue;
  45. // }
  46. }
  47. switch (substr($searchQuery, 0, 1)) {
  48. case '=': return ['=', substr($searchQuery, 1)];
  49. case '>':
  50. switch (substr($searchQuery, 1, 1)) {
  51. case '=': return ['>=', substr($searchQuery, 2)];
  52. default: return ['>', substr($searchQuery, 1)];
  53. }
  54. case '<':
  55. switch (substr($searchQuery, 1, 1)) {
  56. case '=': return ['<=', substr($searchQuery, 2)];
  57. case '>': return ['!=', substr($searchQuery, 2)];
  58. default: return ['<', substr($searchQuery, 1)];
  59. }
  60. case '!':
  61. switch (substr($searchQuery, 1, 1)) {
  62. case '=': return ['!=', substr($searchQuery, 2)];
  63. default: return ['not like', substr($searchQuery, 1)];
  64. }
  65. default: {
  66. switch ($fieldType) {
  67. case 'xsd:number':
  68. case 'xsd:integer': {
  69. if (false !== strpos($searchQuery, '%')) return ['like', $searchQuery];
  70. return ['=', $searchQuery];
  71. }
  72. default: {
  73. if (false !== strpos($searchQuery, '%')) return ['like', $searchQuery];
  74. $queryWhereBuilder = new SqlQueryWhereBuilder();
  75. return ['and'
  76. , array_map(function ($word) use ($fieldName) {
  77. return [$fieldName, 'like', "%{$word}%"];
  78. }, $queryWhereBuilder->splitQueryToWords($searchQuery)
  79. )
  80. ];
  81. }
  82. }
  83. return ['=', $searchQuery];
  84. }
  85. }
  86. }
  87. public function _parseGeomQuery($searchQuery) { // _sqlValueForGeomField($fldName, $fltrValue, $tblPrefix = 't')
  88. // example: BBOX:54.40993961633866,18.583889010112824,54.337945760687454,18.397121431987586
  89. DBG::log($searchQuery, 'string', "\$searchQuery");
  90. if ('BBOX:' == substr($searchQuery, 0, 5)) {
  91. $valParts = explode(',', substr($searchQuery, 5));
  92. if (4 !== count($valParts)) throw new Exception("Wrong BBOX query");
  93. $valParts = array_filter($valParts, 'is_numeric');
  94. if (4 !== count($valParts)) throw new Exception("Wrong BBOX query - expected 4 numeric values");
  95. $bounds = "POLYGON((
  96. {$valParts[3]} {$valParts[2]},
  97. {$valParts[3]} {$valParts[0]},
  98. {$valParts[1]} {$valParts[0]},
  99. {$valParts[1]} {$valParts[2]},
  100. {$valParts[3]} {$valParts[2]}
  101. ))";
  102. // for mysql 5.6 use ST_Contains() @see http://dev.mysql.com/doc/refman/5.6/en/spatial-relation-functions.html
  103. return [ 'Intersects', $bounds ];
  104. }
  105. else if ('GeometryType=' == substr($fltrValue, 0, 13)) {
  106. return [ 'GeometryType', substr($fltrValue, 13) ];
  107. }
  108. throw new Exception("Not implemented geometry query string"); // TODO:? return null;
  109. }
  110. public function _sqlValueForCsvNumericField($fldName, $fltrValue, $tblPrefix = 't') {
  111. $sqlFilter = false;
  112. if (is_numeric($fltrValue)) {
  113. $sqlFilter = "FIND_IN_SET('{$fltrValue}', `{$fldName}`)>0";
  114. } else if (false !== strpos($fltrValue, ' ')) {
  115. $sqlGlue = " or ";
  116. $fltrValues = $fltrValue;
  117. if ('&' == substr($fltrValues, 0, 1)) {
  118. $fltrValues = substr($fltrValues, 1);
  119. $sqlGlue = " and ";
  120. }
  121. $fltrValues = explode(' ', $fltrValues);
  122. $sqlNumericValues = array();
  123. foreach ($fltrValues as $fltrVal) {
  124. if (is_numeric($fltrVal)) {
  125. $sqlNumericValues[] = "FIND_IN_SET('{$fltrVal}', `{$fldName}`)>0";
  126. }
  127. }
  128. if (!empty($sqlNumericValues)) {
  129. $sqlFilter = "(" . implode($sqlGlue, $sqlNumericValues) . ")";
  130. }
  131. }
  132. return $sqlFilter;
  133. }
  134. public function parseSpecialFilterMsgs($type) {
  135. $rootTableName = $this->_acl->getRootTableName();
  136. DBG::log($rootTableName, 'string', "parse SpecialFilter Msgs({$type}), \$rootTableName");
  137. $sqlHasFltrMsgs = "
  138. select 1
  139. from `CRM_UI_MSGS` m
  140. where m.`uiTargetName`=CONCAT('{$rootTableName}.', t.`ID`)
  141. and m.`uiTargetType`='default_db_table_record'
  142. and m.`A_STATUS` not in('DELETED')
  143. limit 1
  144. ";
  145. switch ($type) {
  146. case 'HAS_MSGS': return " ({$sqlHasFltrMsgs})=1 ";
  147. case 'NO_MSGS': return " ({$sqlHasFltrMsgs}) is null ";
  148. case 'NEW_MSGS': {
  149. $sqlNewFltrMsgs = "
  150. select 1
  151. from `CRM_UI_MSGS` m
  152. where m.`uiTargetName`=CONCAT('{$rootTableName}.', t.`ID`)
  153. and m.`uiTargetType`='default_db_table_record'
  154. and m.`A_STATUS` in('WAITING')
  155. limit 1
  156. ";
  157. return " ({$sqlNewFltrMsgs})=1 ";
  158. }
  159. }
  160. return null;
  161. }
  162. public function parseSpecialFilterProblemy($type) {
  163. DBG::log($type, 'string', "parse SpecialFilter Problemy");
  164. switch ($type) {
  165. case 'PROBLEM': return ['A_PROBLEM', '!=', ''];
  166. case 'WARNING': return ['A_PROBLEM', '=', 'WARNING'];
  167. case 'NORMAL': return ['A_PROBLEM', '=', 'NORMAL'];
  168. }
  169. return null;
  170. }
  171. public function parseSpecialFilterStatus($type) {
  172. DBG::log($type, 'string', "parse SpecialFilter Status");
  173. switch ($type) {
  174. case 'WAITING': return ['A_STATUS', '=', 'WAITING'];
  175. case 'AKTYWNI': return ['A_STATUS', 'or', [ // `A_STATUS` in('NORMAL', 'WARNING') ";
  176. ['A_STATUS', '=', 'NORMAL'],
  177. ['A_STATUS', '=', 'WARNING'],
  178. ] ];
  179. }
  180. return null;
  181. }
  182. public function parseSpecialFilterSpotkania($type) {
  183. DBG::log($type, 'string', "parse SpecialFilter Spotkania");
  184. switch ($type) {
  185. case 'OLD': return ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN_NOW'];
  186. // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < UNIX_TIMESTAMP()
  187. // and t.`L_APPOITMENT_DATE` != ''
  188. // and t.`L_APPOITMENT_DATE` != '0000-00-00 00:00:00'
  189. case 'NOW': return ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_NOW_3600'];
  190. // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < UNIX_TIMESTAMP()+3600
  191. // and COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) > UNIX_TIMESTAMP()-3600
  192. case 'TODAY': return ['L_APPOITMENT_DATE', 'and', [
  193. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d"), date("Y"))],
  194. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
  195. ] ];
  196. // $start = mktime(0,0,0, date("m"), date("d"), date("Y"));
  197. // $end = mktime(0,0,0, date("m"), date("d") + 1, date("Y"));
  198. // $sqlFltr = "
  199. // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) > '{$start}'
  200. // and COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < '{$end}'
  201. // ";
  202. case 'TOMORROW': return ['L_APPOITMENT_DATE', 'and', [
  203. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
  204. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 2, date("Y"))],
  205. ] ];
  206. case 'YESTERDAY': return ['L_APPOITMENT_DATE', 'and', [
  207. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") - 2, date("Y"))],
  208. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") - 1, date("Y"))],
  209. ] ];
  210. case 'BRAK': return ['L_APPOITMENT_DATE', 'or', [
  211. ['L_APPOITMENT_DATE', '=', ''],
  212. ['L_APPOITMENT_DATE', '=', '0000-00-00 00:00:00'],
  213. ] ];
  214. }
  215. return null;
  216. }
  217. public function parseSpecialFilterAccess() {
  218. $userLogin = User::getLogin();
  219. $usrAclGroups = User::getLdapGroupsNames();
  220. DBG::log(['login'=>$userLogin, 'groups'=>$usrAclGroups, 'hasFieldWrite'=>$this->_acl->hasField('A_ADM_COMPANY'), 'hasFieldRead'=>$this->_acl->hasField('A_CLASSIFIED'), 'acl'=>$this->_acl], 'array', "parse SpecialFilter Access");
  221. $orWhere = [];
  222. if ($this->_acl->hasField('A_ADM_COMPANY')) {
  223. $orWhere[] = ['A_ADM_COMPANY', '=', ''];// TODO: allow empty for everyone?
  224. foreach ($usrAclGroups as $group) $orWhere[] = ['A_ADM_COMPANY', '=', $group];
  225. }
  226. if ($this->_acl->hasField('A_CLASSIFIED')) {
  227. $orWhere[] = ['A_CLASSIFIED', '=', ''];// TODO: allow empty for everyone?
  228. foreach ($usrAclGroups as $group) $orWhere[] = ['A_CLASSIFIED', '=', $group];
  229. }
  230. if (!empty($orWhere) && $this->_acl->hasField('L_APPOITMENT_USER')) {
  231. $orWhere[] = ['L_APPOITMENT_USER', '=', $userLogin];
  232. }
  233. return (!empty($orWhere)) ? [null, 'or', $orWhere] : null;
  234. }
  235. public function parseOgcFilter($ogcFilter) {
  236. $parser = new ParseOgcFilter();
  237. $parser->loadOgcFilter($ogcFilter);
  238. $queryWhereBuilder = $parser->convertToSqlQueryWhereBuilder();
  239. return $queryWhereBuilder->getQueryWhere('t'); // TODO: $this->_fromPrefix
  240. }
  241. public function getQuery() {
  242. if ($this->_query) return clone($this->_query);
  243. // $ds = $this->_acl->getDataSource(); // TODO: only for TableAcl // TODO: move _parseSqlWhere to this class
  244. $filtrIsInstance = []; // $filtrIsInstance = [ $this->_acl->getNamespace() ];
  245. $filtrIsNotInstance = [];
  246. if (!empty($this->_params['f_is_instance'])) $filtrIsInstance = $this->_params['f_is_instance'];
  247. if (!empty($this->_params['f_is_not_instance'])) $filtrIsNotInstance = $this->_params['f_is_not_instance'];
  248. $this->_query = ACL::query($this->_acl)
  249. ->isInstance($filtrIsInstance)
  250. ->isNotInstance($filtrIsNotInstance);
  251. // ->join($instanceTable, 'i', [ 'rawJoin' => "i.pk = t.{$sqlPrimaryKey} and i.idInstance = {$idInstance}" ])
  252. // $this->_query->where($ds->_parseSqlWhere($params))
  253. DBG::log($this->_params, 'array', "AclQueryFeatures::getQuery \$this->_params");
  254. foreach ($this->_params as $k => $v) {
  255. // DBG::log(['v'=>$v, 'is_numeric' => is_numeric($k), 'is_int' => is_int($k), 'is_array' => is_array($v)], 'array', "AclQueryFeatures::getQuery \$this->_params[{$k}]");
  256. if (is_int($k) && is_array($v)) {
  257. $this->_query->where($v); // TODO: check format [$fieldName, $comparisonSign, $value]
  258. } else if (is_int($k) && null === $v) { // skip NULL
  259. } else if ('f_is_instance' === $k) { // parsed before
  260. } else if ('f_is_not_instance' === $k) { // parsed before
  261. } else if ('@instances' === $k) { // skip - select
  262. } else if ('cols' === $k) { // skip - select
  263. } else if ('f_' === substr($k, 0, 2) && is_string($v) && strlen($k) > 3) {
  264. $fieldName = substr($k, 2);
  265. $fieldType = $this->_acl->getXsdFieldType($fieldName);
  266. list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
  267. DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
  268. $this->_query->where([$fieldName, $comparisonSign, $value]);
  269. } else if ('sf_' === substr($k, 0, 3) && is_string($v) && strlen($k) > 4) {
  270. switch (substr($k, 3)) {
  271. case 'Msgs': $this->_query->where($this->parseSpecialFilterMsgs($v)); break;
  272. case 'Problemy': $this->_query->where($this->parseSpecialFilterProblemy($v)); break;
  273. case 'Status': $this->_query->where($this->parseSpecialFilterStatus($v)); break;
  274. case 'Spotkania': $this->_query->where($this->parseSpecialFilterSpotkania($v)); break;
  275. case 'Access': break; // SKIP - used below
  276. default: throw new Exception("Not Implemented special filter '".substr($k, 3)."'");
  277. }
  278. } else if ('ogc:Filter' === $k) {
  279. $this->_query->where($this->parseOgcFilter($v));
  280. } else if ('primaryKey' === $k) {
  281. $fieldName = $this->_acl->getPrimaryKeyField();
  282. $fieldType = $this->_acl->getXsdFieldType($fieldName);
  283. list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
  284. DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
  285. $this->_query->where([$fieldName, $comparisonSign, $value]);
  286. } else if ('__backRef' === $k) { // skip - parse below
  287. } else if ('limit' === $k) {
  288. } else if ('limitstart' === $k) {
  289. } else if ('order_by' === $k) {
  290. } else if ('order_dir' === $k) {
  291. } else if ('sortBy' === $k) {
  292. } else {
  293. throw new Exception("Not Implemented param '{$k}' = '{$v}'");
  294. }
  295. }
  296. // sf_Access: if 'SHOW' then show all rows, but data with ***
  297. if ('SHOW' !== V::get('sf_Access', '', $this->_params)) $this->_query->where($this->parseSpecialFilterAccess());
  298. if (array_key_exists('__backRef', $this->_params)) {
  299. $backRef = $this->_params['__backRef'];
  300. if (!is_array($backRef)) throw new Exception("Wrong back ref structure - expected array");
  301. if (empty($backRef['namespace'])) throw new Exception("Wrong back ref structure - missing namespace");
  302. if (empty($backRef['primaryKey'])) throw new Exception("Wrong back ref structure - missing primaryKey");
  303. if (empty($backRef['fieldName'])) throw new Exception("Wrong back ref structure - missing fieldName");
  304. // TODO: $this->_query->where([ '__backRef' ]); or $this->_query->join([ '__backRef' ]);
  305. $refAcl = ACL::getAclByNamespace($backRef['namespace']);
  306. if ($refAcl->getSourceName() !== $this->_acl->getSourceName()) throw new Exception("Not implemented join with different source");
  307. $refTable = ACL::getRefTable($refAcl->getNamespace(), $backRef['fieldName']);
  308. // TODO: 'in' operator? // $this->_query->where($pkField, 'in', "");
  309. $sqlPk = $this->getAclSqlPrimaryKeyField();
  310. $sqlBackRefPk = DB::getPDO()->quote($backRef['primaryKey']);
  311. $this->_query->where("
  312. t.{$sqlPk} in (
  313. select refTable.REMOTE_PRIMARY_KEY
  314. from `{$refTable}` refTable
  315. where refTable.PRIMARY_KEY = {$sqlBackRefPk}
  316. )
  317. ");
  318. }
  319. return clone($this->_query);
  320. }
  321. public function getTotal() {
  322. if ($this->_legacyMode) return $this->_acl->getTotal($this->_params);
  323. if (null !== $this->_total) return $this->_total;
  324. $this->_total = $this->getQuery()->fetchTotal();
  325. return $this->_total;
  326. }
  327. public function hasParam($key) { return !empty($this->_params[$key]); }
  328. public function getParam($key) { return V::get($key, '', $this->_params); }
  329. public function getItems() {
  330. if ($this->_legacyMode) return $this->_acl->getItems($this->_params);// TODO: array_map( $r => (array)$r )
  331. // 'limit' => 10,
  332. // 'limitstart' => 0,
  333. // 'order_by' => 'ID',
  334. // 'order_dir' => 'desc',
  335. // TODO: sortBy from wfs query
  336. $sortBy = ($this->hasParam('sortBy')) ? $this->getParam('sortBy') : null;
  337. if (!$sortBy) {
  338. $sortBy = $this->hasParam('order_by')
  339. ? ( $this->hasParam('order_dir')
  340. ? $this->getParam('order_by') . " " . $this->getParam('order_dir')
  341. : $this->getParam('order_by')
  342. )
  343. : '';
  344. }
  345. $limit = V::get('limit', 10, $this->_params, 'int');
  346. $offset = V::get('limitstart', 0, $this->_params, 'int');
  347. DBG::log(['params' => $this->_params, 'sortBy' => $sortBy, 'limit' => $limit, 'offset' => $offset], 'array', '$this->_params');
  348. $select = $this->prepareSelect();
  349. DBG::log($select, 'array', "\$select is(TableAcl)=(".($this->_acl instanceof TableAcl).")");
  350. DBG::log($this->getQuery(), 'array', "\$select is(TableAcl)=(".($this->_acl instanceof TableAcl).") \$this->getQuery()");
  351. return $this->fetchRowsRefs(
  352. $this->getQuery()
  353. ->select($select)
  354. ->limit($limit)
  355. ->offset($offset)
  356. ->orderBy($sortBy)
  357. ->fetchAll()
  358. );
  359. }
  360. public function getItem($primaryKey) { // TODO: throw exception if not found?
  361. if ($this->_legacyMode) return (array)$this->_acl->getItem($primaryKey, $this->_params);
  362. $select = $this->prepareSelect();
  363. $pkField = $this->_acl->getPrimaryKeyField();
  364. return $this->fetchRowRefs(
  365. $this->getQuery()
  366. ->select($select)
  367. ->where([$pkField, '=', $primaryKey])
  368. ->fetchFirst()
  369. );
  370. }
  371. public function prepareSelect() { // TODO: replace with getSelectLocal
  372. // TODO: select from params: 'cols' => [ fieldName, ... ]
  373. // TODO: select from params: '@instances' => 1
  374. // TODO: if no fields set, then '*'
  375. // TODO: select must contain primaryKey
  376. return $this->getSelectLocal();
  377. }
  378. public function getSelectLocal() { // @returns [ $fieldName, ... ]
  379. // TODO: rawSelect!
  380. if (null !== $this->_selectLocalFields) return $this->_selectLocalFields;
  381. $this->_selectLocalFields = [];
  382. $todoFetchAllCols = false; // select t.*
  383. if (!empty($this->_params['cols'])) {
  384. if (in_array('*', $this->_params['cols'])) $todoFetchAllCols = true;
  385. $acl = $this->_acl;
  386. $this->_selectLocalFields = array_filter($this->_params['cols'], function ($fieldQuery) use ($acl) {
  387. if ('*' === $fieldQuery) return false;
  388. list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
  389. if (!empty($subFieldQuery)) return false;
  390. return $acl->isLocalField($fieldName);
  391. });
  392. }
  393. if (!empty($this->_params['@instances'])) $this->_selectLocalFields[] = '@instances';
  394. if (empty($this->_selectLocalFields)) $todoFetchAllCols = true;
  395. if (1 === count($this->_selectLocalFields) && in_array('@instances', $this->_selectLocalFields)) $todoFetchAllCols = true;
  396. // if ($this->_acl instanceof TableAcl) {
  397. // $rawSelect = $this->_acl->getDataSource()->_getSqlCols();
  398. // DBG::log($rawSelect, 'string', "DBG raw select");
  399. // if ('*' !== $rawSelect && 't.*' !== $rawSelect) {
  400. // $this->_selectLocalFields['rawSelect'] = $rawSelect;
  401. // }
  402. // }
  403. if (!empty($this->_params['cols'])) DBG::log($this->_params['cols'], 'array', '$this->_params[cols]');
  404. if ($todoFetchAllCols) {
  405. // $this->_selectLocalFields[] = '*'; // TODO: select all $this->from local fields
  406. DBG::log($this->_acl->getLocalFieldList(), 'array', "\$this->_acl->getLocalFieldList()");
  407. foreach ($this->_acl->getLocalFieldList() as $localFieldName) {
  408. $this->_selectLocalFields[] = $localFieldName;
  409. }
  410. }
  411. $primaryKey = $this->getAclSqlPrimaryKeyField();
  412. if (!in_array($primaryKey, $this->_selectLocalFields)) {
  413. $this->_selectLocalFields[] = $primaryKey;
  414. }
  415. DBG::log($this->_selectLocalFields, 'array', '$this->_selectLocalFields');
  416. return $this->_selectLocalFields;
  417. }
  418. public function getSelectRemote() { // @returns [ $fieldName => [ $fieldName, ... ] ]
  419. if (null !== $this->_selectRemote) return $this->_selectRemote;
  420. $this->_selectRemote = [];
  421. if (!empty($this->_params['cols'])) {
  422. $cols = $this->_params['cols'];
  423. if (in_array('*', $cols)) {
  424. $this->_selectLocalFields[] = '*';
  425. $cols = array_filter($cols, function ($fieldQuery) { return '*' !== $fieldQuery; });
  426. }
  427. $acl = $this->_acl;
  428. $cols = array_filter($cols, function ($fieldQuery) use ($acl) {
  429. list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
  430. if (empty($subFieldQuery)) return false;
  431. return !$acl->isLocalField($fieldName);
  432. });
  433. foreach ($cols as $fieldQuery) { // group by fieldName
  434. list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
  435. if (!array_key_exists($fieldName, $this->_selectRemote)) $this->_selectRemote[$fieldName] = [];
  436. $this->_selectRemote[$fieldName][] = $subFieldQuery;
  437. }
  438. }
  439. return $this->_selectRemote;
  440. }
  441. public function fetchRowsRefs($rows) {
  442. return array_map([ $this, 'fetchRowRefs' ], $rows);
  443. // if (!empty($rows) && !empty($rows[0])) {
  444. // $rows[0]['default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK'] = [
  445. // [ 'xlink' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK.999' ],
  446. // [ 'xlink' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK.998' ],
  447. // ];
  448. // }
  449. return $rows;
  450. }
  451. public function fetchRowRefs($row) {
  452. if (!$row) return $row;
  453. $sqlPk = $this->getAclSqlPrimaryKeyField();
  454. $primaryKey = $row[$sqlPk];
  455. DBG::log($row, 'array', "DBG primaryKey '{$primaryKey}'");
  456. if (!$primaryKey) throw new Exception("Missing primaryKey");
  457. foreach ($this->getSelectRemote() as $fieldName => $cols) {
  458. DBG::log($cols, 'array', "add select remote '{$fieldName}' \$cols");
  459. $items = ACL::getAclByTypeName($fieldName)->buildQuery([
  460. 'cols' => $cols,
  461. '__backRef' => [
  462. 'namespace' => $this->_acl->getNamespace(),
  463. 'primaryKey' => $primaryKey,
  464. 'fieldName' => $fieldName,
  465. ]
  466. ])->getItems();
  467. DBG::log($items, 'array', "TODO: add remote items '{$fieldName}' \$items");
  468. $row[$fieldName] = $items;
  469. }
  470. return $row;
  471. }
  472. public function getAclSqlPrimaryKeyField() {
  473. return ($this->_acl instanceof Core_AclBase)
  474. ? $this->_acl->getSqlPrimaryKeyField()
  475. : 'ID';
  476. }
  477. }