AclQueryFeatures.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. <?php
  2. Lib::loadClass('ACL');
  3. Lib::loadClass('SqlQueryWhereBuilder');
  4. Lib::loadClass('ParseOgcFilter');
  5. // usage: (Acl class)::buildQuery($params): return new AclQueryFeatures($this, $params);
  6. // (view): $queryFeatures = $acl->buildQuery($params);
  7. // (view): $total = $queryFeatures->getTotal();
  8. // (view): $items = $queryFeatures->getItems();
  9. // example: @see TableAcl, TableAjax
  10. // Special Filter Access - btns visible only if user don't have super access perms. If has, then will always see all rows.
  11. class AclQueryFeatures {
  12. public $_params;
  13. public $_acl;
  14. public $_query;
  15. public $_total;
  16. public $_legacyMode;
  17. public function __construct($acl, $params, $legacyMode = false) {
  18. $this->_acl = $acl;
  19. $this->_params = $params;
  20. $this->_query = null;
  21. $this->_total = null;
  22. $this->_legacyMode = $legacyMode;
  23. // TODO: _legacyMode = ($from instanceof simple schema or another programmed objects)
  24. }
  25. public function parseQueryValue($fieldName, $searchQuery, $fieldType = 'xsd:string') {
  26. if ('!NULL' === $searchQuery) return ['is not null', null];
  27. if ('IS NOT NULL' === $searchQuery) return ['is not null', null];
  28. if ('NULL' === $searchQuery) return ['is null', null];
  29. if ('IS NULL' === $searchQuery) return ['is null', null];
  30. switch ($fieldType) {
  31. case 'gml:PolygonPropertyType':
  32. case 'gml:PointPropertyType':
  33. case 'gml:LineStringPropertyType':
  34. case 'gml:GeometryPropertyType': return $this->_parseGeomQuery($searchQuery);
  35. // $sqlFilter = $this->_sqlValueForGeomField($fldName, $v, 't');
  36. // if ('_CSV_NUM' == substr($fldName, -8)) { // if ($this->isCsvNumericField($fldName)) { // TODO: xsd type - p5:csv_num
  37. // $sqlFilter = $this->_sqlValueForCsvNumericField($fldName, $v, 't');
  38. // if ($sqlFilter) $sql_where_and[] = $sqlFilter;
  39. // continue;
  40. // }
  41. }
  42. switch (substr($searchQuery, 0, 1)) {
  43. case '=': return ['=', substr($searchQuery, 1)];
  44. case '>':
  45. switch (substr($searchQuery, 1, 1)) {
  46. case '=': return ['>=', substr($searchQuery, 2)];
  47. default: return ['>', substr($searchQuery, 1)];
  48. }
  49. case '<':
  50. switch (substr($searchQuery, 1, 1)) {
  51. case '=': return ['<=', substr($searchQuery, 2)];
  52. case '>': return ['!=', substr($searchQuery, 2)];
  53. default: return ['<', substr($searchQuery, 1)];
  54. }
  55. case '!':
  56. switch (substr($searchQuery, 1, 1)) {
  57. case '=': return ['!=', substr($searchQuery, 2)];
  58. default: return ['not like', substr($searchQuery, 1)];
  59. }
  60. default: {
  61. switch ($fieldType) {
  62. case 'xsd:number':
  63. case 'xsd:integer': {
  64. if (false !== strpos($searchQuery, '%')) return ['like', $searchQuery];
  65. return ['=', $searchQuery];
  66. }
  67. default: {
  68. if (false !== strpos($searchQuery, '%')) return ['like', $searchQuery];
  69. $queryWhereBuilder = new SqlQueryWhereBuilder();
  70. return ['and'
  71. , array_map(function ($word) use ($fieldName) {
  72. return [$fieldName, 'like', "%{$word}%"];
  73. }, $queryWhereBuilder->splitQueryToWords($searchQuery)
  74. )
  75. ];
  76. }
  77. }
  78. return ['=', $searchQuery];
  79. }
  80. }
  81. }
  82. public function _parseGeomQuery($searchQuery) { // _sqlValueForGeomField($fldName, $fltrValue, $tblPrefix = 't')
  83. // example: BBOX:54.40993961633866,18.583889010112824,54.337945760687454,18.397121431987586
  84. DBG::log($searchQuery, 'string', "\$searchQuery");
  85. if ('BBOX:' == substr($searchQuery, 0, 5)) {
  86. $valParts = explode(',', substr($searchQuery, 5));
  87. if (4 !== count($valParts)) throw new Exception("Wrong BBOX query");
  88. $valParts = array_filter($valParts, 'is_numeric');
  89. if (4 !== count($valParts)) throw new Exception("Wrong BBOX query - expected 4 numeric values");
  90. $bounds = "POLYGON((
  91. {$valParts[3]} {$valParts[2]},
  92. {$valParts[3]} {$valParts[0]},
  93. {$valParts[1]} {$valParts[0]},
  94. {$valParts[1]} {$valParts[2]},
  95. {$valParts[3]} {$valParts[2]}
  96. ))";
  97. // for mysql 5.6 use ST_Contains() @see http://dev.mysql.com/doc/refman/5.6/en/spatial-relation-functions.html
  98. return [ 'Intersects', $bounds ];
  99. }
  100. else if ('GeometryType=' == substr($fltrValue, 0, 13)) {
  101. return [ 'GeometryType', substr($fltrValue, 13) ];
  102. }
  103. throw new Exception("Not implemented geometry query string"); // TODO:? return null;
  104. }
  105. public function _sqlValueForCsvNumericField($fldName, $fltrValue, $tblPrefix = 't') {
  106. $sqlFilter = false;
  107. if (is_numeric($fltrValue)) {
  108. $sqlFilter = "FIND_IN_SET('{$fltrValue}', `{$fldName}`)>0";
  109. } else if (false !== strpos($fltrValue, ' ')) {
  110. $sqlGlue = " or ";
  111. $fltrValues = $fltrValue;
  112. if ('&' == substr($fltrValues, 0, 1)) {
  113. $fltrValues = substr($fltrValues, 1);
  114. $sqlGlue = " and ";
  115. }
  116. $fltrValues = explode(' ', $fltrValues);
  117. $sqlNumericValues = array();
  118. foreach ($fltrValues as $fltrVal) {
  119. if (is_numeric($fltrVal)) {
  120. $sqlNumericValues[] = "FIND_IN_SET('{$fltrVal}', `{$fldName}`)>0";
  121. }
  122. }
  123. if (!empty($sqlNumericValues)) {
  124. $sqlFilter = "(" . implode($sqlGlue, $sqlNumericValues) . ")";
  125. }
  126. }
  127. return $sqlFilter;
  128. }
  129. public function parseSpecialFilterMsgs($type) {
  130. $rootTableName = $this->_acl->getRootTableName();
  131. DBG::log($rootTableName, 'string', "parse SpecialFilter Msgs({$type}), \$rootTableName");
  132. $sqlHasFltrMsgs = "
  133. select 1
  134. from `CRM_UI_MSGS` m
  135. where m.`uiTargetName`=CONCAT('{$rootTableName}.', t.`ID`)
  136. and m.`uiTargetType`='default_db_table_record'
  137. and m.`A_STATUS` not in('DELETED')
  138. limit 1
  139. ";
  140. switch ($type) {
  141. case 'HAS_MSGS': return " ({$sqlHasFltrMsgs})=1 ";
  142. case 'NO_MSGS': return " ({$sqlHasFltrMsgs}) is null ";
  143. case 'NEW_MSGS': {
  144. $sqlNewFltrMsgs = "
  145. select 1
  146. from `CRM_UI_MSGS` m
  147. where m.`uiTargetName`=CONCAT('{$rootTableName}.', t.`ID`)
  148. and m.`uiTargetType`='default_db_table_record'
  149. and m.`A_STATUS` in('WAITING')
  150. limit 1
  151. ";
  152. return " ({$sqlNewFltrMsgs})=1 ";
  153. }
  154. }
  155. return null;
  156. }
  157. public function parseSpecialFilterProblemy($type) {
  158. DBG::log($type, 'string', "parse SpecialFilter Problemy");
  159. switch ($type) {
  160. case 'PROBLEM': return ['A_PROBLEM', '!=', ''];
  161. case 'WARNING': return ['A_PROBLEM', '=', 'WARNING'];
  162. case 'NORMAL': return ['A_PROBLEM', '=', 'NORMAL'];
  163. }
  164. return null;
  165. }
  166. public function parseSpecialFilterStatus($type) {
  167. DBG::log($type, 'string', "parse SpecialFilter Status");
  168. switch ($type) {
  169. case 'WAITING': return ['A_STATUS', '=', 'WAITING'];
  170. case 'AKTYWNI': return ['A_STATUS', 'or', [ // `A_STATUS` in('NORMAL', 'WARNING') ";
  171. ['A_STATUS', '=', 'NORMAL'],
  172. ['A_STATUS', '=', 'WARNING'],
  173. ] ];
  174. }
  175. return null;
  176. }
  177. public function parseSpecialFilterSpotkania($type) {
  178. DBG::log($type, 'string', "parse SpecialFilter Spotkania");
  179. switch ($type) {
  180. case 'OLD': return ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN_NOW'];
  181. // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < UNIX_TIMESTAMP()
  182. // and t.`L_APPOITMENT_DATE` != ''
  183. // and t.`L_APPOITMENT_DATE` != '0000-00-00 00:00:00'
  184. case 'NOW': return ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_NOW_3600'];
  185. // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < UNIX_TIMESTAMP()+3600
  186. // and COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) > UNIX_TIMESTAMP()-3600
  187. case 'TODAY': return ['L_APPOITMENT_DATE', 'and', [
  188. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d"), date("Y"))],
  189. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
  190. ] ];
  191. // $start = mktime(0,0,0, date("m"), date("d"), date("Y"));
  192. // $end = mktime(0,0,0, date("m"), date("d") + 1, date("Y"));
  193. // $sqlFltr = "
  194. // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) > '{$start}'
  195. // and COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < '{$end}'
  196. // ";
  197. case 'TOMORROW': return ['L_APPOITMENT_DATE', 'and', [
  198. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
  199. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 2, date("Y"))],
  200. ] ];
  201. case 'YESTERDAY': return ['L_APPOITMENT_DATE', 'and', [
  202. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") - 2, date("Y"))],
  203. ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") - 1, date("Y"))],
  204. ] ];
  205. case 'BRAK': return ['L_APPOITMENT_DATE', 'or', [
  206. ['L_APPOITMENT_DATE', '=', ''],
  207. ['L_APPOITMENT_DATE', '=', '0000-00-00 00:00:00'],
  208. ] ];
  209. }
  210. return null;
  211. }
  212. public function parseSpecialFilterAccess() {
  213. $userLogin = User::getLogin();
  214. $usrAclGroups = User::getLdapGroupsNames();
  215. DBG::log(['login'=>$userLogin, 'groups'=>$usrAclGroups, 'acl'=>$this->_acl], 'array', "parse SpecialFilter Access");
  216. $orWhere = [];
  217. if ($this->_acl->hasField('A_ADM_COMPANY')) {
  218. $orWhere[] = ['A_ADM_COMPANY', '=', ''];// TODO: allow empty for everyone?
  219. foreach ($usrAclGroups as $group) $orWhere[] = ['A_ADM_COMPANY', '=', $group];
  220. }
  221. if ($this->_acl->hasField('A_CLASSIFIED')) {
  222. $orWhere[] = ['A_CLASSIFIED', '=', ''];// TODO: allow empty for everyone?
  223. foreach ($usrAclGroups as $group) $orWhere[] = ['A_CLASSIFIED', '=', $group];
  224. }
  225. if (!empty($orWhere) && $this->_acl->hasField('L_APPOITMENT_USER')) {
  226. $orWhere[] = ['L_APPOITMENT_USER', '=', $userLogin];
  227. }
  228. return (!empty($orWhere)) ? [null, 'or', $orWhere] : null;
  229. }
  230. public function parseOgcFilter($ogcFilter) {
  231. $parser = new ParseOgcFilter();
  232. $parser->loadOgcFilter($ogcFilter);
  233. $queryWhereBuilder = $parser->convertToSqlQueryWhereBuilder();
  234. return $queryWhereBuilder->getQueryWhere($this->_fromPrefix);
  235. }
  236. public function getQuery() {
  237. if ($this->_query) return $this->_query;
  238. // $ds = $this->_acl->getDataSource(); // TODO: only for TableAcl // TODO: move _parseSqlWhere to this class
  239. $filtrIsInstance = [];
  240. $filtrIsNotInstance = [];
  241. $this->_query = ACL::query($this->_acl)
  242. ->isInstance($filtrIsInstance)
  243. ->isNotInstance($filtrIsNotInstance);
  244. // ->join($instanceTable, 'i', [ 'rawJoin' => "i.pk = t.{$sqlPrimaryKey} and i.idInstance = {$idInstance}" ])
  245. // $this->_query->where($ds->_parseSqlWhere($params))
  246. DBG::log($this->_params, 'array', "\$this->_params");
  247. foreach ($this->_params as $k => $v) {
  248. if ('f_' === substr($k, 0, 2) && strlen($k) > 3) {
  249. $fieldName = substr($k, 2);
  250. $fieldType = $this->_acl->getXsdFieldType($fieldName);
  251. list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
  252. DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
  253. $this->_query->where([$fieldName, $comparisonSign, $value]);
  254. } else if ('sf_' === substr($k, 0, 3) && strlen($k) > 4) {
  255. switch (substr($k, 3)) {
  256. case 'Msgs': $this->_query->where($this->parseSpecialFilterMsgs($v)); break;
  257. case 'Problemy': $this->_query->where($this->parseSpecialFilterProblemy($v)); break;
  258. case 'Status': $this->_query->where($this->parseSpecialFilterStatus($v)); break;
  259. case 'Spotkania': $this->_query->where($this->parseSpecialFilterSpotkania($v)); break;
  260. case 'Access': break; // SKIP - used below
  261. default: throw new Exception("Not Implemented special filter '".substr($k, 3)."'");
  262. }
  263. } else if ('ogc:Filter' == $k) {
  264. $this->_query->where($this->parseOgcFilter($v));
  265. } else if ('primaryKey' == $k) {
  266. $fieldName = $this->_acl->getPrimaryKeyField();
  267. $fieldType = $this->_acl->getXsdFieldType($fieldName);
  268. list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
  269. DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
  270. $this->_query->where([$fieldName, $comparisonSign, $value]);
  271. }
  272. }
  273. // sf_Access: if 'SHOW' then show all rows, but data with ***
  274. if ('SHOW' !== V::get('sf_Access', '', $this->_params)) $this->_query->where($this->parseSpecialFilterAccess());
  275. return $this->_query;
  276. }
  277. public function getTotal() {
  278. if ($this->_legacyMode) return $this->_acl->getTotal($this->_params);
  279. if (null !== $this->_total) return $this->_total;
  280. $this->_total = $this->getQuery()
  281. ->select([ 'rawSelect' => "count(1) as cnt" ])
  282. ->fetchValue();
  283. return $this->_total;
  284. }
  285. public function hasParam($key) { return !empty($this->_params[$key]); }
  286. public function getParam($key) { return V::get($key, '', $this->_params); }
  287. public function getItems() {
  288. if ($this->_legacyMode) return $this->_acl->getItems($this->_params);
  289. // 'limit' => 10,
  290. // 'limitstart' => 0,
  291. // 'order_by' => 'ID',
  292. // 'order_dir' => 'desc',
  293. // TODO: sortBy from wfs query
  294. $sortBy = $this->hasParam('order_by')
  295. ? ( $this->hasParam('order_dir')
  296. ? $this->getParam('order_by') . " " . $this->getParam('order_dir')
  297. : $this->getParam('order_by')
  298. )
  299. : '';
  300. $limit = V::get('limit', 10, $this->_params, 'int');
  301. $offset = V::get('limitstart', 0, $this->_params, 'int');
  302. $ds = $this->_acl->getDataSource(); // TODO: only for TableAcl // TODO: move _parseSqlWhere to this class
  303. DBG::log(['params' => $this->_params, 'sortBy' => $sortBy, 'limit' => $limit, 'offset' => $offset], 'array', '$this->_params');
  304. return $this->getQuery()
  305. ->select([
  306. 'rawSelect' => $ds->_getSqlCols()
  307. ]) // TODO: fields
  308. ->select(!empty($this->_params['@instances']) ? '@instances' : null)
  309. ->limit($limit)
  310. ->offset($offset)
  311. ->orderBy($sortBy)
  312. ->fetchAll();
  313. }
  314. public function getItem($primaryKey) { throw new HttpException("Acl function " . __FUNCTION__ . " Not implemented", 501); }
  315. }