| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727 |
- <?php
- Lib::loadClass('ACL');
- Lib::loadClass('SqlQueryWhereBuilder');
- Lib::loadClass('ParseOgcFilter');
- Lib::loadClass('TableAcl');
- // usage: (Acl class)::buildQuery($params): return new AclQueryFeatures($this, $params);
- // (view): $queryFeatures = $acl->buildQuery($params);
- // (view): $total = $queryFeatures->getTotal();
- // (view): $items = $queryFeatures->getItems();
- // example: @see TableAcl, TableAjax
- // Special Filter Access - btns visible only if user don't have super access perms. If has, then will always see all rows.
- class AclQueryFeatures {
- public $_params;
- public $_acl;
- public $_query;
- public $_total;
- public $_legacyMode;
- public $_selectLocalFields; // TODO: leave here or move to AclQueryBuilder? use join for perf?
- public $_selectRemote; // TODO: ...
- public $_dbgExecTime;
- public $_instanceID;
- public $_foundFeatures;
- public function __construct($acl, $params, $legacyMode = false) {
- $this->_acl = $acl;
- $this->_params = $params;
- $this->_query = null;
- $this->_total = null;
- $this->_legacyMode = $legacyMode;
- // TODO: _legacyMode = ($from instanceof simple schema or another programmed objects)
- $this->_selectLocalFields = null;
- $this->_selectRemote = null;
- $this->_foundFeatures = [];
- // 'skipFeaturesAsXlink' => $this->_foundFeatures
- if (array_key_exists('skipFeaturesAsXlink', $this->_params)) {
- $this->_foundFeatures = V::get('skipFeaturesAsXlink', [], $this->_params, 'array');
- unset($this->_params['skipFeaturesAsXlink']);
- }
- $DBG_LOG_TO_FILE = true; // TODO: read from ENV or REQUEST
- if ($DBG_LOG_TO_FILE) {
- Lib::loadClass('DebugExecutionTime');
- $this->_dbgExecTime = new DebugExecutionTime();
- $this->_dbgExecTime->setLogFile('/tmp/se-dbg-exec-time--AclQueryFeatures.csv', $format = 'csv'); // $format: 'csv', 'json'
- $this->_logToFile("from: '" . $this->_acl->getNamespace() . "'");
- }
- }
- public function _logToFile($msg) {
- if ($this->_dbgExecTime) {
- $idInstance = $this->_getInstanceID();
- $this->_dbgExecTime->logToFile("#{$idInstance}: {$msg}");
- }
- }
- public function _getInstanceID() {
- static $_INSTANCE_ID = 0;
- if (!$this->_instanceID) {
- $_INSTANCE_ID += 1;
- $this->_instanceID = $_INSTANCE_ID;
- }
- return $this->_instanceID;
- }
- public function parseQueryValue($fieldName, $searchQuery, $fieldType = 'xsd:string') {
- if ('!NULL' === $searchQuery) return ['is not null', null];
- if ('IS NOT NULL' === $searchQuery) return ['is not null', null];
- if ('NULL' === $searchQuery) return ['is null', null];
- if ('IS NULL' === $searchQuery) return ['is null', null];
- switch ($fieldType) {
- case 'gml:PolygonPropertyType':
- case 'gml:PointPropertyType':
- case 'gml:LineStringPropertyType':
- case 'gml:GeometryPropertyType': return $this->_parseGeomQuery($searchQuery);
- // $sqlFilter = $this->_sqlValueForGeomField($fldName, $v, 't');
- // if ('_CSV_NUM' == substr($fldName, -8)) { // if ($this->isCsvNumericField($fldName)) { // TODO: xsd type - p5:csv_num
- // $sqlFilter = $this->_sqlValueForCsvNumericField($fldName, $v, 't');
- // if ($sqlFilter) $sql_where_and[] = $sqlFilter;
- // continue;
- // }
- }
- switch (substr($searchQuery, 0, 1)) {
- case '=': return ['=', substr($searchQuery, 1)];
- case '>':
- switch (substr($searchQuery, 1, 1)) {
- case '=': return ['>=', substr($searchQuery, 2)];
- default: return ['>', substr($searchQuery, 1)];
- }
- case '<':
- switch (substr($searchQuery, 1, 1)) {
- case '=': return ['<=', substr($searchQuery, 2)];
- case '>': return ['!=', substr($searchQuery, 2)];
- default: return ['<', substr($searchQuery, 1)];
- }
- case '!':
- switch (substr($searchQuery, 1, 1)) {
- case '=': return ['!=', substr($searchQuery, 2)];
- default: return ['not like', substr($searchQuery, 1)];
- }
- default: {
- switch ($fieldType) {
- case 'xsd:long':
- case 'xsd:number':
- case 'xsd:int':
- case 'xsd:integer': {
- if (false !== strpos($searchQuery, '%')) return ['like', $searchQuery];
- return ['=', $searchQuery];
- }
- default: {
- if (false !== strpos($searchQuery, '%')) return ['like', $searchQuery];
- $queryWhereBuilder = new SqlQueryWhereBuilder();
- return ['and'
- , array_map(function ($word) use ($fieldName) {
- return [$fieldName, 'like', "%{$word}%"];
- }, $queryWhereBuilder->splitQueryToWords($searchQuery)
- )
- ];
- }
- }
- return ['=', $searchQuery];
- }
- }
- }
- public function _parseGeomQuery($searchQuery) { // _sqlValueForGeomField($fldName, $fltrValue, $tblPrefix = 't')
- // example: BBOX:54.40993961633866,18.583889010112824,54.337945760687454,18.397121431987586
- DBG::log($searchQuery, 'string', "\$searchQuery");
- if ('BBOX:' == substr($searchQuery, 0, 5)) {
- $valParts = explode(',', substr($searchQuery, 5));
- if (4 !== count($valParts)) throw new Exception("Wrong BBOX query");
- $valParts = array_filter($valParts, 'is_numeric');
- if (4 !== count($valParts)) throw new Exception("Wrong BBOX query - expected 4 numeric values");
- $bounds = "POLYGON((
- {$valParts[3]} {$valParts[2]},
- {$valParts[3]} {$valParts[0]},
- {$valParts[1]} {$valParts[0]},
- {$valParts[1]} {$valParts[2]},
- {$valParts[3]} {$valParts[2]}
- ))";
- // for mysql 5.6 use ST_Contains() @see http://dev.mysql.com/doc/refman/5.6/en/spatial-relation-functions.html
- return [ 'Intersects', $bounds ];
- }
- else if ('GeometryType=' == substr($fltrValue, 0, 13)) {
- return [ 'GeometryType', substr($fltrValue, 13) ];
- }
- throw new Exception("Not implemented geometry query string"); // TODO:? return null;
- }
- public function _sqlValueForCsvNumericField($fldName, $fltrValue, $tblPrefix = 't') {
- $sqlFilter = false;
- if (is_numeric($fltrValue)) {
- $sqlFilter = "FIND_IN_SET('{$fltrValue}', `{$fldName}`)>0";
- } else if (false !== strpos($fltrValue, ' ')) {
- $sqlGlue = " or ";
- $fltrValues = $fltrValue;
- if ('&' == substr($fltrValues, 0, 1)) {
- $fltrValues = substr($fltrValues, 1);
- $sqlGlue = " and ";
- }
- $fltrValues = explode(' ', $fltrValues);
- $sqlNumericValues = array();
- foreach ($fltrValues as $fltrVal) {
- if (is_numeric($fltrVal)) {
- $sqlNumericValues[] = "FIND_IN_SET('{$fltrVal}', `{$fldName}`)>0";
- }
- }
- if (!empty($sqlNumericValues)) {
- $sqlFilter = "(" . implode($sqlGlue, $sqlNumericValues) . ")";
- }
- }
- return $sqlFilter;
- }
- public function parseSpecialFilterMsgs($type) {
- $rootTableName = $this->_acl->getRootTableName();
- DBG::log($rootTableName, 'string', "parse SpecialFilter Msgs({$type}), \$rootTableName");
- $sqlHasFltrMsgs = "
- select 1
- from `CRM_UI_MSGS` m
- where m.`uiTargetName`=CONCAT('{$rootTableName}.', t.`ID`)
- and m.`uiTargetType`='default_db_table_record'
- and m.`A_STATUS` not in('DELETED')
- limit 1
- ";
- switch ($type) {
- case 'HAS_MSGS': return " ({$sqlHasFltrMsgs})=1 ";
- case 'NO_MSGS': return " ({$sqlHasFltrMsgs}) is null ";
- case 'NEW_MSGS': {
- $sqlNewFltrMsgs = "
- select 1
- from `CRM_UI_MSGS` m
- where m.`uiTargetName`=CONCAT('{$rootTableName}.', t.`ID`)
- and m.`uiTargetType`='default_db_table_record'
- and m.`A_STATUS` in('WAITING')
- limit 1
- ";
- return " ({$sqlNewFltrMsgs})=1 ";
- }
- }
- return null;
- }
- public function parseSpecialFilterProblemy($type) {
- DBG::log($type, 'string', "parse SpecialFilter Problemy");
- switch ($type) {
- case 'PROBLEM': return ['A_PROBLEM', '!=', ''];
- case 'WARNING': return ['A_PROBLEM', '=', 'WARNING'];
- case 'NORMAL': return ['A_PROBLEM', '=', 'NORMAL'];
- }
- return null;
- }
- public function parseSpecialFilterStatus($type) {
- DBG::log($type, 'string', "parse SpecialFilter Status");
- switch ($type) {
- case 'WAITING': return ['A_STATUS', '=', 'WAITING'];
- case 'AKTYWNI': return ['A_STATUS', 'or', [ // `A_STATUS` in('NORMAL', 'WARNING') ";
- ['A_STATUS', '=', 'NORMAL'],
- ['A_STATUS', '=', 'WARNING'],
- ] ];
- }
- return null;
- }
- public function parseSpecialFilterSpotkania($type) {
- DBG::log($type, 'string', "parse SpecialFilter Spotkania");
- switch ($type) {
- case 'OLD': return ['L_APPOITMENT_DATE', 'and', [
- ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN_NOW'],
- ] ];
- // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < UNIX_TIMESTAMP()
- // and t.`L_APPOITMENT_DATE` != ''
- // and t.`L_APPOITMENT_DATE` != '0000-00-00 00:00:00'
- case 'NOW': return ['L_APPOITMENT_DATE', 'and', [
- [ 'L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_NOW_3600'],
- ] ];
- // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < UNIX_TIMESTAMP()+3600
- // and COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) > UNIX_TIMESTAMP()-3600
- case 'TODAY': return ['L_APPOITMENT_DATE', 'and', [
- ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d"), date("Y"))],
- ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
- ] ];
- // $start = mktime(0,0,0, date("m"), date("d"), date("Y"));
- // $end = mktime(0,0,0, date("m"), date("d") + 1, date("Y"));
- // $sqlFltr = "
- // COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) > '{$start}'
- // and COALESCE(UNIX_TIMESTAMP(t.`L_APPOITMENT_DATE`), 0) < '{$end}'
- // ";
- case 'TOMORROW': return ['L_APPOITMENT_DATE', 'and', [
- ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") + 1, date("Y"))],
- ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") + 2, date("Y"))],
- ] ];
- case 'YESTERDAY': return ['L_APPOITMENT_DATE', 'and', [
- ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_GREATER_THAN', mktime(0,0,0, date("m"), date("d") - 2, date("Y"))],
- ['L_APPOITMENT_DATE', 'UNIX_TIMESTAMP_LESS_THAN', mktime(0,0,0, date("m"), date("d") - 1, date("Y"))],
- ] ];
- case 'BRAK': return ['L_APPOITMENT_DATE', 'or', [
- ['L_APPOITMENT_DATE', '=', ''],
- ['L_APPOITMENT_DATE', '=', '0000-00-00 00:00:00'],
- ] ];
- }
- return null;
- }
- public function parseSpecialFilterAccess() {
- $userLogin = User::getLogin();
- $usrAclGroups = User::getLdapGroupsNames();
- 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");
- $orWhere = [];
- if ($this->_acl->hasField('A_ADM_COMPANY')) {
- $orWhere[] = ['A_ADM_COMPANY', '=', ''];// TODO: allow empty for everyone?
- foreach ($usrAclGroups as $group) $orWhere[] = ['A_ADM_COMPANY', '=', $group];
- }
- if ($this->_acl->hasField('A_CLASSIFIED')) {
- $orWhere[] = ['A_CLASSIFIED', '=', ''];// TODO: allow empty for everyone?
- foreach ($usrAclGroups as $group) $orWhere[] = ['A_CLASSIFIED', '=', $group];
- }
- if (!empty($orWhere) && $this->_acl->hasField('L_APPOITMENT_USER')) {
- $orWhere[] = ['L_APPOITMENT_USER', '=', $userLogin];
- }
- return (!empty($orWhere)) ? [null, 'or', $orWhere] : null;
- }
- public function parseOgcFilter($ogcFilter) {
- $parser = new ParseOgcFilter();
- $parser->loadOgcFilter($ogcFilter);
- $queryWhereBuilder = $parser->convertToSqlQueryWhereBuilder();
- return $queryWhereBuilder->getQueryWhere('t'); // TODO: $this->_fromPrefix
- }
- public function getQuery() {
- if ($this->_query) return clone($this->_query);
- // $ds = $this->_acl->getDataSource(); // TODO: only for TableAcl // TODO: move _parseSqlWhere to this class
- $filtrIsInstance = []; // $filtrIsInstance = [ $this->_acl->getNamespace() ];
- $filtrIsNotInstance = [];
- if (!empty($this->_params['f_is_instance'])) $filtrIsInstance = $this->_params['f_is_instance'];
- if (!empty($this->_params['f_is_not_instance'])) $filtrIsNotInstance = $this->_params['f_is_not_instance'];
- $this->_query = ACL::query($this->_acl)
- ->isInstance($filtrIsInstance)
- ->isNotInstance($filtrIsNotInstance);
- // ->join($instanceTable, 'i', [ 'rawJoin' => "i.pk = t.{$sqlPrimaryKey} and i.idInstance = {$idInstance}" ])
- // $this->_query->where($ds->_parseSqlWhere($params))
- DBG::log($this->_params, 'array', "AclQueryFeatures::getQuery \$this->_params");
- foreach ($this->_params as $k => $v) {
- // 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}]");
- if (is_int($k) && is_array($v)) {
- $this->_query->where($v); // TODO: check format [$fieldName, $comparisonSign, $value]
- } else if (is_int($k) && null === $v) { // skip NULL
- } else if ('f_is_instance' === $k) { // parsed before
- } else if ('f_is_not_instance' === $k) { // parsed before
- } else if ('@instances' === $k) { // skip - select
- } else if ('cols' === $k) { // skip - select
- } else if ('f_' === substr($k, 0, 2) && is_string($v) && strlen($k) > 3) {
- $fieldName = substr($k, 2);
- $fieldType = $this->_acl->getXsdFieldType($fieldName);
- list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
- DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
- $this->_query->where([$fieldName, $comparisonSign, $value]);
- } else if ('sf_' === substr($k, 0, 3) && is_string($v) && strlen($k) > 4) {
- switch (substr($k, 3)) {
- case 'Msgs': $this->_query->where($this->parseSpecialFilterMsgs($v)); break;
- case 'Problemy': $this->_query->where($this->parseSpecialFilterProblemy($v)); break;
- case 'Status': $this->_query->where($this->parseSpecialFilterStatus($v)); break;
- case 'Spotkania': $this->_query->where($this->parseSpecialFilterSpotkania($v)); break;
- case 'Access': break; // SKIP - used below
- default: throw new Exception("Not Implemented special filter '".substr($k, 3)."'");
- }
- } else if ('ogc:Filter' === $k) {
- $this->_query->where($this->parseOgcFilter($v));
- } else if ('primaryKey' === $k) {
- $fieldName = $this->_acl->getPrimaryKeyField();
- $fieldType = $this->_acl->getXsdFieldType($fieldName);
- if (is_array($v)) {
- DBG::log([ $fieldName, $v, $fieldType ], 'array', "parseQueryValue :: primaryKey, value is array");
- $this->_query->where([ $fieldName, 'or', array_map(function ($pk) use ($fieldName) {
- return [ $fieldName, '=', $pk ];
- }, $v) ]);
- } else {
- list($comparisonSign, $value) = $this->parseQueryValue($fieldName, $v, $fieldType);
- DBG::log([ $fieldName, $comparisonSign, $value, $fieldType ], 'array', "parseQueryValue");
- $this->_query->where([$fieldName, $comparisonSign, $value]);
- }
- } else if ('__backRef' === $k) { // skip - parse below
- } else if ('__childRef' === $k) { // skip - parse below
- } else if ('limit' === $k) {
- } else if ('limitstart' === $k) {
- } else if ('order_by' === $k) {
- } else if ('order_dir' === $k) {
- } else if ('sortBy' === $k) {
- } else {
- throw new Exception("Not Implemented param '{$k}' = '{$v}'");
- }
- }
- // sf_Access: if 'SHOW' then show all rows, but data with ***
- if ('SHOW' !== V::get('sf_Access', '', $this->_params)) $this->_query->where($this->parseSpecialFilterAccess());
- if (array_key_exists('__backRef', $this->_params)) {
- $backRef = $this->_params['__backRef'];
- if (!is_array($backRef)) throw new Exception("Wrong back ref structure - expected array");
- if (empty($backRef['namespace'])) throw new Exception("Wrong back ref structure - missing namespace");
- if (empty($backRef['primaryKey'])) throw new Exception("Wrong back ref structure - missing primaryKey");
- if (empty($backRef['fieldName'])) throw new Exception("Wrong back ref structure - missing fieldName");
- // TODO: $this->_query->where([ '__backRef' ]); or $this->_query->join([ '__backRef' ]);
- $refAcl = ACL::getAclByNamespace($backRef['namespace']);
- if ($refAcl->getSourceName() !== $this->_acl->getSourceName()) throw new Exception("Not implemented join with different source");
- $refTable = ACL::getRefTable($refAcl->getNamespace(), $backRef['fieldName']);
- // TODO: 'in' operator? // $this->_query->where($pkField, 'in', "");
- $sqlPk = $this->getAclSqlPrimaryKeyField();
- $sqlBackRefPk = DB::getPDO()->quote($backRef['primaryKey']);
- $limit = V::get('limit', 10, $this->_params, 'int');
- $offset = V::get('limitstart', 0, $this->_params, 'int');
- DBG::log(['limit' => $limit, 'offset' => $offset], 'array', "DBG: limit with __backRef \$this->_params");
- $sqlLimit = "limit {$limit} offset {$offset}";
- // $this->_query->where("
- // t.{$sqlPk} in (
- // select refTable.REMOTE_PRIMARY_KEY
- // from `{$refTable}` refTable
- // where refTable.PRIMARY_KEY = {$sqlBackRefPk}
- // order by refTable.REMOTE_PRIMARY_KEY DESC -- TODO refTable.SORT_PRIO
- // -- {$sqlLimit} -- BUG MariaDB not support limit in subquery
- // )
- // ");
- // TODO: convert `where t.ID in ( ... )` into `join` and `order by`
- // join `CRM__#REF_TABLE__120_VIEW` refTable on (refTable.REMOTE_PRIMARY_KEY = t.ID and refTable.PRIMARY_KEY = '37' )
- $this->_query->join($refTable, 'refTable', [ 'rawJoin' => "t.{$sqlPk} = refTable.REMOTE_PRIMARY_KEY and refTable.PRIMARY_KEY = {$sqlBackRefPk}" ]);
- // moved to getItems: $this->_query->orderBy("refTable.REMOTE_PRIMARY_KEY DESC"); // TODO: order by refTable.SORT_PRIO
- }
- if (array_key_exists('__childRef', $this->_params)) {
- $childRef = $this->_params['__childRef'];
- if (!is_array($childRef)) throw new Exception("Wrong child ref structure - expected array");
- if (empty($childRef['namespace'])) throw new Exception("Wrong child ref structure - missing namespace");
- if (empty($childRef['primaryKey'])) throw new Exception("Wrong child ref structure - missing primaryKey");
- $refAcl = ACL::getAclByNamespace($childRef['namespace']);
- if ($refAcl->getSourceName() !== $this->_acl->getSourceName()) throw new Exception("Not implemented join with different source");
- $refTypeName = Api_WfsNs::typeName($childRef['namespace']);
- DBG::log("\$refTypeName({$refTypeName})");
- $refTable = ACL::getRefTable($this->_acl->getNamespace(), $refTypeName);
- // TODO: 'in' operator? // $this->_query->where($pkField, 'in', "");
- $sqlPk = $this->getAclSqlPrimaryKeyField();
- $sqlChildRefPk = DB::getPDO()->quote($childRef['primaryKey']);
- $this->_query->where("
- t.{$sqlPk} in (
- select refTable.PRIMARY_KEY
- from `{$refTable}` refTable
- where refTable.REMOTE_PRIMARY_KEY = {$sqlChildRefPk}
- )
- ");
- }
- DBG::log($this->_query, 'array', "TODO: optimize \$this->_query");
- $this->_USE_TEMPORARY_TABLE = false; // TODO: ...
- if ($this->_USE_TEMPORARY_TABLE) {
- $sortBy = ($this->hasParam('sortBy')) ? $this->getParam('sortBy') : null;
- if (!$sortBy) {
- $sortBy = $this->hasParam('order_by')
- ? ( $this->hasParam('order_dir')
- ? $this->getParam('order_by') . " " . $this->getParam('order_dir')
- : $this->getParam('order_by')
- )
- : '';
- }
- $TMP_TABLE_NAME = $this->_acl->getRootTableName() . "__#TEMPORARY";
- DBG::log("\$TMP_TABLE_NAME='{$TMP_TABLE_NAME}'");
- Lib::loadClass('AntAclBase');
- if ($this->_acl instanceof AntAclBase) {
- $baseSql = $this->_query->generateSql();
- DB::getPDO()->execSql("
- create temporary table `{$TMP_TABLE_NAME}`
- {$baseSql}
- ");
- DBG::log(DB::getPDO()->fetchValue("select count(*) as cnt from `{$TMP_TABLE_NAME}`"), 'array', "select count(*) cnt from '{$TMP_TABLE_NAME}'");
- $tmpAclFrom = clone($this->_acl);
- $tmpAclFrom->_rootTableName = $TMP_TABLE_NAME;
- // $tmpAclFrom = AntAclBase::buildInstance($this->_acl->getID(), [
- // 'name' => $this->_acl->getName(),
- // '_rootTableName' => $TMP_TABLE_NAME,
- // 'idDatabase' => $this->_acl->getDatabaseID(),
- // 'namespace' => $this->_acl->getNamespace(),
- // 'primaryKey' => $this->_acl->getPrimaryKeyField(),
- // 'field' => $this->_acl->getFields(),
- // ]);
- Lib::loadClass('AclQueryBuilder');
- $query = new AclQueryBuilder();
- $query->from($tmpAclFrom, $prefix = 't');
- DBG::log($query, 'array', "TODO: \$query by temporary table");
- // $this->_query = $query;
- // return $this->_query;
- }
- }
- return clone($this->_query);
- }
- public function getTotal() {
- if ($this->_legacyMode) return $this->_acl->getTotal($this->_params);
- if (null !== $this->_total) return $this->_total;
- $this->_total = $this->getQuery()->fetchTotal();
- return $this->_total;
- }
- public function hasParam($key) { return !empty($this->_params[$key]); }
- public function getParam($key) { return V::get($key, '', $this->_params); }
- public function getItems() {
- $this->_logToFile('getItems() ...');
- if ($this->_legacyMode) return $this->_acl->getItems($this->_params);// TODO: array_map( $r => (array)$r )
- // 'limit' => 10,
- // 'limitstart' => 0,
- // 'order_by' => 'ID',
- // 'order_dir' => 'desc',
- // TODO: sortBy from wfs query
- $sortBy = ($this->hasParam('sortBy')) ? $this->getParam('sortBy') : null;
- if (!$sortBy && array_key_exists('__backRef', $this->_params)) {
- $sortBy = "refTable.REMOTE_PRIMARY_KEY DESC"; // TODO: order by refTable.SORT_PRIO
- }
- if (!$sortBy) {
- $sortBy = $this->hasParam('order_by')
- ? ( $this->hasParam('order_dir')
- ? $this->getParam('order_by') . " " . $this->getParam('order_dir')
- : $this->getParam('order_by')
- )
- : '';
- }
- $limit = V::get('limit', 10, $this->_params, 'int');
- $offset = V::get('limitstart', 0, $this->_params, 'int');
- DBG::log(['params' => $this->_params, 'sortBy' => $sortBy, 'limit' => $limit, 'offset' => $offset], 'array', '$this->_params');
- $select = $this->prepareSelect();
- DBG::log($select, 'array', "\$select is(TableAcl)=(".($this->_acl instanceof TableAcl).")");
- // DBG::log($this->getQuery(), 'array', "\$select is(TableAcl)=(".($this->_acl instanceof TableAcl).") \$this->getQuery()");
- $items = $this->fetchRowsRefs(
- $this->getQuery()
- ->select($select)
- ->limit($limit)
- ->offset($offset)
- ->orderBy($sortBy)
- ->fetchAll()
- );
- $this->_logToFile('getItems() fetched.');
- return $items;
- }
- public function getItem($primaryKey) { // TODO: throw exception if not found?
- if ($this->_legacyMode) return (array)$this->_acl->getItem($primaryKey, $this->_params);
- $select = $this->prepareSelect();
- $pkField = $this->_acl->getPrimaryKeyField();
- return $this->fetchRowRefs(
- $this->getQuery()
- ->select($select)
- ->where([$pkField, '=', $primaryKey])
- ->fetchFirst()
- );
- }
- public function prepareSelect() { // TODO: replace with getSelectLocal
- // TODO: select from params: 'cols' => [ fieldName, ... ]
- // TODO: select from params: '@instances' => 1
- // TODO: if no fields set, then '*'
- // TODO: select must contain primaryKey
- return $this->getSelectLocal();
- }
- public function getSelectLocal() { // @returns [ $fieldName, ... ]
- // TODO: rawSelect!
- if (null !== $this->_selectLocalFields) return $this->_selectLocalFields;
- $this->_selectLocalFields = [];
- $todoFetchAllCols = false; // select t.*
- if (!empty($this->_params['cols'])) {
- if (in_array('*', $this->_params['cols'])) $todoFetchAllCols = true;
- $acl = $this->_acl;
- $this->_selectLocalFields = array_filter($this->_params['cols'], function ($fieldQuery) use ($acl) {
- if ('*' === $fieldQuery) return false;
- list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
- if (!empty($subFieldQuery)) return false;
- return $acl->isLocalField($fieldName);
- });
- }
- if (!empty($this->_params['@instances'])) $this->_selectLocalFields[] = '@instances';
- if (empty($this->_selectLocalFields)) $todoFetchAllCols = true;
- if (1 === count($this->_selectLocalFields) && in_array('@instances', $this->_selectLocalFields)) $todoFetchAllCols = true;
- // if ($this->_acl instanceof TableAcl) {
- // $rawSelect = $this->_acl->getDataSource()->_getSqlCols();
- // DBG::log($rawSelect, 'string', "DBG raw select");
- // if ('*' !== $rawSelect && 't.*' !== $rawSelect) {
- // $this->_selectLocalFields['rawSelect'] = $rawSelect;
- // }
- // }
- if (!empty($this->_params['cols'])) DBG::log($this->_params['cols'], 'array', '$this->_params[cols]');
- if ($todoFetchAllCols) {
- // $this->_selectLocalFields[] = '*'; // TODO: select all $this->from local fields
- DBG::log($this->_acl->getLocalFieldList(), 'array', "\$this->_acl->getLocalFieldList()");
- foreach ($this->_acl->getLocalFieldList() as $localFieldName) {
- $this->_selectLocalFields[] = $localFieldName;
- }
- }
- $primaryKey = $this->getAclSqlPrimaryKeyField();
- if (!in_array($primaryKey, $this->_selectLocalFields)) {
- $this->_selectLocalFields[] = $primaryKey;
- }
- // TODO: always add A_ADM_COMPANY, A_CLASSIFIED, L_APPOITMENT_USER ?
- DBG::log($this->_selectLocalFields, 'array', '$this->_selectLocalFields');
- return $this->_selectLocalFields;
- }
- public function getSelectRemote() { // @returns [ $fieldName => [ $fieldName, ... ] ]
- if (null !== $this->_selectRemote) return $this->_selectRemote;
- $this->_selectRemote = [];
- if (!empty($this->_params['cols'])) {
- $cols = $this->_params['cols'];
- if (in_array('*', $cols)) {
- $this->_selectLocalFields[] = '*';
- $cols = array_filter($cols, function ($fieldQuery) { return '*' !== $fieldQuery; });
- }
- $acl = $this->_acl;
- $cols = array_filter($cols, function ($fieldQuery) use ($acl) {
- list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
- // if (empty($subFieldQuery)) return false;
- return !$acl->isLocalField($fieldName);
- });
- foreach ($cols as $fieldQuery) { // group by fieldName
- list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
- if (!array_key_exists($fieldName, $this->_selectRemote)) $this->_selectRemote[$fieldName] = [];
- if (!empty($subFieldQuery)) $this->_selectRemote[$fieldName][] = $subFieldQuery;
- }
- }
- return $this->_selectRemote;
- }
- public function fetchRowsRefs($rows) {
- $pkField = $this->_acl->getPrimaryKeyField();
- $namespace = $this->_acl->getNamespace();
- $this->_foundFeatures = array_merge($this->_foundFeatures, array_map(function ($row) use ($namespace, $pkField) {
- return "{$namespace}." . V::get($pkField, '', $row);
- }, $rows));
- DBG::log($this->_foundFeatures, 'array', "_foundFeatures");
- return array_map([ $this, 'fetchRowRefs' ], $rows);
- }
- public function fetchRowRefs($row) {
- if (!$row) return $row;
- $sqlPk = $this->getAclSqlPrimaryKeyField();
- $primaryKey = $row[$sqlPk];
- DBG::log($row, 'array', "DBG primaryKey '{$primaryKey}'");
- if (!$primaryKey) throw new Exception("Missing primaryKey");
- $defaultRefLimit = 10; // TODO: get from $this->_params and pass to nested buildQuery
- $refLimitPlus1 = $defaultRefLimit + 1;
- foreach ($this->getSelectRemote() as $fieldName => $cols) {
- DBG::log($cols, 'array', "add select remote '{$fieldName}' \$cols");
- $xsdType = $this->_acl->getXsdFieldType($fieldName);
- if ('ref:' === substr($xsdType, 0, 4) && empty($cols)) {
- DBG::log("add remote xlink's '{$fieldName}' \$items[{$primaryKey}] ...");
- $refTable = ACL::getRefTable($this->_acl->getNamespace(), $fieldName);
- if (!$refTable) DBG::log("BUG: Missing refTable in add remote xlink's '{$fieldName}' \$items[{$primaryKey}]");
- if ($refTable) {
- $xlinks = DB::getPDO()->fetchAll("
- select r.REMOTE_PRIMARY_KEY
- from `{$refTable}` r
- where r.PRIMARY_KEY = '{$primaryKey}'
- order by r.REMOTE_PRIMARY_KEY DESC -- TODO r.SORT_PRIO
- limit {$refLimitPlus1}
- ");
- DBG::log($xlinks, 'array', "add remote xlink's for '{$fieldName}' \$items[{$primaryKey}]");
- $row[$fieldName] = array_map(function ($refInfo) use ($fieldName) {
- $ns = Core_AclHelper::parseTypeName($fieldName);
- return [
- // '_ns' => $ns,
- 'xlink' => "{$ns['url']}#{$ns['name']}.{$refInfo['REMOTE_PRIMARY_KEY']}",
- ];
- }, $xlinks);
- if (count($xlinks) > $defaultRefLimit) DBG::log('TODO: xlink FETCH MORE DATA...');
- if (count($xlinks) > $defaultRefLimit) $row[$fieldName][] = [ 'p5:links' => [
- 'p5:next' => [
- '@typeName' => $fieldName,
- '@backRefNS' => $this->_acl->getNamespace(),
- '@backRefPK' => $primaryKey,
- '@startIndex' => $defaultRefLimit,
- '@maxFeatures' => $defaultRefLimit,
- 'value' => Request::getScriptUri() . "?SERVICE=WFS&VERSION=1.0.0&TYPENAME={$fieldName}&REQUEST=GetFeature&backRefNS=".$this->_acl->getNamespace()."&backRefPK={$primaryKey}&backRefField={$fieldName}&maxFeatures={$defaultRefLimit}&startIndex={$defaultRefLimit}",
- ],
- ] ];
- DBG::log($row[$fieldName], 'array', "remote xlinks for \$items[{$primaryKey}][{$fieldName}]");
- }
- } else if ('ref:' === substr($xsdType, 0, 4) && !empty($cols)) {
- $refAcl = ACL::getAclByTypeName($fieldName);
- $refQuery = $refAcl->buildQuery([
- 'cols' => $cols,
- '__backRef' => [
- 'namespace' => $this->_acl->getNamespace(),
- 'primaryKey' => $primaryKey,
- 'fieldName' => $fieldName,
- ],
- 'limit' => $refLimitPlus1,
- 'skipFeaturesAsXlink' => $this->_foundFeatures
- // TODO: add $defaultRefLimit + 1
- ]);
- $totalRefs = $refQuery->getTotal();
- $items = $refQuery->getItems([ 'limit' => $defaultRefLimit ]);
- // TODO: if (count($items) > $defaultRefLimit) // TODO: add item for GUI - has more data + wfs link to fetch more (offset)
- DBG::log($items, 'array', "add remote items '{$fieldName}' \$items[{$primaryKey}][{$fieldName}] total({$totalRefs})");
- if ($totalRefs > $defaultRefLimit) {
- DBG::log('TODO: resolve recurse fetch more data link');
- array_pop($items); // remove last item
- $items[] = [ 'p5:links' => [
- 'p5:next' => [
- '@typeName' => $fieldName,
- '@backRefNS' => $this->_acl->getNamespace(),
- '@backRefPK' => $primaryKey,
- '@startIndex' => $defaultRefLimit,
- '@maxFeatures' => $defaultRefLimit,
- 'value' => Request::getScriptUri() . "?SERVICE=WFS&VERSION=1.0.0&TYPENAME={$fieldName}&REQUEST=GetFeature&backRefNS=".$this->_acl->getNamespace()."&backRefPK={$primaryKey}&backRefField={$fieldName}&maxFeatures={$defaultRefLimit}&startIndex={$defaultRefLimit}",
- ],
- ] ];
- }
- $refNs = $refAcl->getNamespace();
- $refPk = $refAcl->getPrimaryKeyField();
- $this__hasFeatureId = [ $this, 'hasFeatureId' ];
- $this__addFeatureId = [ $this, 'addFeatureId' ];
- $row[$fieldName] = array_map(function ($item) use ($fieldName, $refNs, $refPk, $this__hasFeatureId, $this__addFeatureId) {
- $pk = V::get($refPk, '', $item);
- $featureId = "{$refNs}.{$pk}";
- if (!$this__hasFeatureId($featureId)) {
- $this__addFeatureId($featureId);
- return $item;
- } else {
- $ns = Core_AclHelper::parseTypeName($fieldName);
- return [
- 'xlink' => "{$ns['url']}#{$ns['name']}.{$pk}",
- ];
- }
- }, $items);
- } else { // TODO: field is not ref
- DBG::log($items, 'array', "NotImplemented - add remote items for non ref field \$items[{$primaryKey}][{$fieldName}]?");
- }
- }
- return $row;
- }
- public function hasFeatureId($featureId) {
- return in_array($featureId, $this->_foundFeatures);
- }
- public function addFeatureId($featureId) {
- $this->_foundFeatures[] = $featureId;
- DBG::log($this->_foundFeatures, 'array', "addFeatureId({$featureId})");
- }
- public function getAclSqlPrimaryKeyField() {
- return ($this->_acl instanceof Core_AclBase)
- ? $this->_acl->getSqlPrimaryKeyField()
- : 'ID';
- }
- }
|