Ver código fonte

added support for AntAcl - wfs, xlinks, nested objects

Piotr Labudda 8 anos atrás
pai
commit
a0142a5783

+ 49 - 23
SE/se-lib/AclQueryFeatures.php

@@ -459,13 +459,13 @@ class AclQueryFeatures {
       $acl = $this->_acl;
       $cols = array_filter($cols, function ($fieldQuery) use ($acl) {
         list($fieldName, $subFieldQuery) = explode('/', $fieldQuery, 2);
-        if (empty($subFieldQuery)) return false;
+        // 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] = [];
-        $this->_selectRemote[$fieldName][] = $subFieldQuery;
+        if (!empty($subFieldQuery)) $this->_selectRemote[$fieldName][] = $subFieldQuery;
       }
     }
     return $this->_selectRemote;
@@ -482,27 +482,53 @@ class AclQueryFeatures {
     return $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");
-    foreach ($this->getSelectRemote() as $fieldName => $cols) {
-      DBG::log($cols, 'array', "add select remote '{$fieldName}' \$cols");
-      $items = ACL::getAclByTypeName($fieldName)->buildQuery([
-        'cols' => $cols,
-        '__backRef' => [
-          'namespace' => $this->_acl->getNamespace(),
-          'primaryKey' => $primaryKey,
-          'fieldName' => $fieldName,
-        ]
-      ])->getItems();
-      DBG::log($items, 'array', "TODO: add remote items '{$fieldName}' \$items");
-      $row[$fieldName] = $items;
-    }
-    return $row;
-  }
+	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");
+		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)) { // only xlink's
+				DBG::log("TODO: add remote xlink's '{$fieldName}' \$items[{$primaryKey}]");
+				$refTable = ACL::getRefTable($this->_acl->getNamespace(), $fieldName);
+				DBG::log($refTable, 'string', "TODO: add remote xlink's '{$fieldName}' \$items[{$primaryKey}] from refTable");
+				if ($refTable) {
+					$xlinks = DB::getPDO()->fetchAll("
+						select r.REMOTE_PRIMARY_KEY
+						from `{$refTable}` r
+						where r.PRIMARY_KEY = '{$primaryKey}'
+					");
+					DBG::log($xlinks, 'array', "TODO: add remote xlink's '{$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);
+					DBG::log($row[$fieldName], 'array', "TODO: remote xlinks: \$items[{$primaryKey}][{$fieldName}]");
+				}
+			} else if ('ref:' === substr($xsdType, 0, 4)) { // cols
+				$items = ACL::getAclByTypeName($fieldName)->buildQuery([
+					'cols' => $cols,
+					'__backRef' => [
+						'namespace' => $this->_acl->getNamespace(),
+						'primaryKey' => $primaryKey,
+						'fieldName' => $fieldName,
+					]
+				])->getItems();
+				DBG::log($items, 'array', "TODO: add remote items '{$fieldName}' \$items");
+				$row[$fieldName] = $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 getAclSqlPrimaryKeyField() {
     return ($this->_acl instanceof Core_AclBase)

+ 110 - 21
SE/se-lib/Api/WfsDataServer.php

@@ -4,6 +4,7 @@ Lib::loadClass('Api_WfsServerBase');
 Lib::loadClass('Api_WfsException');
 Lib::loadClass('Api_WfsGeomTypeConverter');
 Lib::loadClass('Api_WfsNs');
+Lib::loadClass('Core_XmlWriter');
 Lib::loadClass('DBG');
 
 class Api_WfsDataServer extends Api_WfsServerBase {
@@ -172,7 +173,7 @@ class Api_WfsDataServer extends Api_WfsServerBase {
 		$rootWfsNs = 'p5';
 		$rootWfsNsUri = "{$baseNsUri}";
 		$wfsNs = $args['typePrefix'];
-		$wfsNsUri = "{$baseNsUri}/" . substr($args['typePrefix'], 3);
+		$wfsNsUri = "{$baseNsUri}/" . ('p5_' === substr($args['typePrefix'], 0, 3) ? substr($args['typePrefix'], 3) : $args['typePrefix']);
 		$featureTypeUri = $this->getBaseUri() . "?SERVICE=WFS&VERSION=1.0.0&TYPENAME={$args['xsd:type']}&REQUEST=DescribeFeatureType";
 
 		// get BBox from geom_field (only one geom fld is allowed)
@@ -199,14 +200,74 @@ class Api_WfsDataServer extends Api_WfsServerBase {
 		if (!empty($args['filterFields'])) $searchParams['cols'] = $args['filterFields'];// PropertyName
 		if (!empty($args['primaryKey'])) $searchParams['primaryKey'] = $args['primaryKey'];// featureID
 		if (!empty($args['bbox'])) $searchParams['f_the_geom'] = "BBOX:{$args['bbox']}";
+
+		$contextFieldList = []; // convert $args['filterFields'] to field list
+		$schemaCache = array();
+		try {
+			$acl__getAllFieldNames = function ($listFields) {
+				return array_map(function ($field) {
+					return $field['fieldNamespace'];
+				}, $listFields);
+			};
+			$acl__getLocalFieldNames = function ($listFields) {
+				return array_map(function ($field) {
+					return $field['fieldNamespace'];
+				}, array_filter($listFields, function ($field) {
+					return $field['isLocal'];
+				}));
+			};
+			DBG::log($acl->getFields(), 'array', "\$contextFieldList ACL fields");
+			if (empty($args['filterFields'])) { // get all local fields
+				// $contextFieldList = $acl__getLocalFieldNames($acl->getFields());
+				$contextFieldList = $acl__getAllFieldNames($acl->getFields());
+			} else {
+				foreach ($args['filterFields'] as $fieldXPath) {
+					if ('*' === $fieldXPath) {
+						$contextFieldList = array_merge($contextFieldList, $acl__getLocalFieldNames($acl->getFields()));
+					} else if (false === strpos($fieldXPath, '/') && false === strpos($fieldXPath, ':')) {
+						$contextFieldList[] = $fieldXPath;
+					} else if (false === strpos($fieldXPath, '/') && false !== strpos($fieldXPath, ':')) {
+						$contextFieldList[] = $fieldXPath;
+						$fieldNs = str_replace(['__x3A__', ':'], '/', $fieldXPath);
+						$schemaCache[$fieldNs] = SchemaFactory::loadDefaultObject('SystemObject')->getItem($fieldNs, [ 'propertyName' => '*,field' ]);
+						DBG::log($schemaCache[$fieldNs], 'array', "\$schemaCache[{$fieldNs}]");
+					} else if ('/*' === substr($fieldXPath, -2) && false === strpos(substr($fieldXPath, 0, -2), '/')) {
+						$fieldName = substr($fieldXPath, 0, -2);
+						$contextFieldList[] = $fieldName;
+						$xsdType = $acl->getXsdFieldType($fieldName);
+						if ('ref:' !== substr($xsdType, 0, 4)) throw new Exception("Error Processing Request - field '{$fieldXPath}' type is not ref '/*' is not allowed");
+						$fieldNs = str_replace(['__x3A__', ':'], '/', substr($xsdType, 4));
+						if (!array_key_exists($fieldNs, $schemaCache)) {
+							$schemaCache[$fieldNs] = SchemaFactory::loadDefaultObject('SystemObject')->getItem($fieldNs, [ 'propertyName' => '*,field' ]);
+							DBG::log($schemaCache[$fieldNs], 'array', "\$schemaCache[{$fieldNs}]");
+						}
+						$fieldPrefix = "{$fieldName}";
+						$contextFieldList = array_merge($contextFieldList, array_map(function ($fieldName) use ($fieldPrefix) {
+							return "{$fieldPrefix}/{$fieldName}";
+						}, $acl__getLocalFieldNames($schemaCache[$fieldNs]['field'])));
+					} else {
+						$fieldName = trim($fieldXPath, '*/');
+						DBG::log(['$fieldXPath'=>$fieldXPath, '$fieldName'=>$fieldName], 'array', "\$contextFieldList TODO");
+					}
+				}
+			}
+			DBG::log($contextFieldList, 'array', "\$contextFieldList");
+
+			$searchParams['cols'] = $contextFieldList;
+		} catch (Exception $e) {
+			DBG::log($e);
+			throw $e;
+		}
+
 		DBG::log([ 'msg'=>'getItems - $searchParams', '$searchParams'=>$searchParams ]);
 		if($DBG){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">get_total (F.' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($jsonData->total);echo'</pre>';}
 		$queryFeatures = $acl->buildQuery($searchParams);
 		$items = $queryFeatures->getItems();
 		DBG::log([ 'msg'=>'getItems - $items', '$items'=>$items ]);
 
+
 		header('Content-type: application/xml; charset=utf-8');
-		$xmlWriter = new XMLWriter();
+		$xmlWriter = new Core_XmlWriter();
 		$xmlWriter->openUri('php://output');
 		// $xmlWriter->openMemory();// DBG
 		$xmlWriter->setIndent(true);
@@ -220,9 +281,12 @@ class Api_WfsDataServer extends Api_WfsServerBase {
 		$xmlWriter->writeAttribute('xmlns:gml', 'http://www.opengis.net/gml');
 		$xmlWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
 		$xmlWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
-		$xmlWriter->writeAttribute("xmlns:{$wfsNs}", $wfsNsUri);
+		$xmlWriter->writeAttribute("xmlns:{$wfsNs}", $wfsNsUri); // TODO: BUG $wfsNsUri
 		if (!$simple) $xmlWriter->writeAttribute("xmlns:{$rootWfsNs}", $rootWfsNsUri);
-		$xmlWriter->writeAttribute('xsi:schemaLocation', "{$wfsNsUri} {$featureTypeUri}");
+		foreach ($schemaCache as $childSchema) {
+			$xmlWriter->writeAttribute("xmlns:{$childSchema['nsPrefix']}", "{$rootWfsNsUri}/" . str_replace('__x3A__', '/', $childSchema['nsPrefix']));
+		}
+		$xmlWriter->writeAttribute('xsi:schemaLocation', "{$wfsNsUri} {$featureTypeUri}"); // TODO: BUG $wfsNsUri
 		$xmlWriter->writeAttribute('numberMatched', 'unknown'); // TODO: return total items if simple query (without prefix, small total number, maxFeatures set, etc.)
 		// NOTE: for client: if numberMatched == 'unknown' then request with resultType = 'hits'
 		$xmlWriter->writeAttribute('numberReturned', count($items));
@@ -235,7 +299,6 @@ class Api_WfsDataServer extends Api_WfsServerBase {
 
 			if (!empty($geomFld)) DBG::log(['msg'=>"item[{$itemKey}] ({$geomFld})isEmpty(".empty($item[$geomFld])."):", '$item['.$geomFld.']'=>$item[$geomFld]]);
 			DBG::log([ 'msg'=>">>> loop({$itemKey})", '$item'=>$item ]);
-
 			$xmlWriter->startElement('gml:featureMember');
 				$xmlWriter->startElement("{$wfsNs}:{$type}");
 					$xmlWriter->writeAttribute('fid', "{$type}.{$itemKey}");
@@ -257,23 +320,49 @@ class Api_WfsDataServer extends Api_WfsServerBase {
 							$xmlWriter->endElement();// {$wfsNs}:{$fldName}
 						} else if (is_array($item[$fldName])) {// TODO: by struct - REF field
 							DBG::log([ 'msg'=>">>> loop({$itemKey}) REF item[{$itemKey}][{$fldName}]", '$item'=>$item[$fldName] ]);
-							if (1 == count($item[$fldName])) {
-								$xlink = $item[$fldName][0]['xlink'];
-								$xlinkParts = explode(':', $xlink);
-								if (2 != count($xlinkParts)) throw new Exception("Error Processing Request - wrong xlink format for ".$acl->getName().".{$itemKey}/{$fldName}");
-								$xlinkParts[0] = Api_WfsNs::getNsUri($xlinkParts[0]);
-								$xlink = implode('#', $xlinkParts);
-								$xmlWriter->startElement("{$wfsNs}:{$fldName}");
-									if (!$simple && !$acl->canReadObjectField($fldName, (object)$item)) {
-										$xmlWriter->writeAttribute("{$rootWfsNs}:allow_read", "false");
-									}
-									if (!$simple && $acl->canWriteObjectField($fldName, (object)$item)) {
-										$xmlWriter->writeAttribute("{$rootWfsNs}:allow_write", "true");
-									}
-									$xmlWriter->writeAttribute('xlink:href', $xlink);
-								$xmlWriter->endElement();// {$wfsNs}:{$fldName}
+							if (empty($item[$fldName])) {
+								$xmlWriter->h($fldName);
+							// } else if (1 == count($item[$fldName]) && !empty($item[$fldName][0]['xlink'])) {
+							// 	$xlink = $item[$fldName][0]['xlink'];
+							// 	$xlinkParts = explode(':', $xlink);
+							// 	if (2 != count($xlinkParts)) throw new Exception("Error Processing Request - wrong xlink format for ".$acl->getName().".{$itemKey}/{$fldName}");
+							// 	$xlinkParts[0] = Api_WfsNs::getNsUri($xlinkParts[0]);
+							// 	$xlink = implode('#', $xlinkParts);
+							// 	$xmlWriter->startElement("{$wfsNs}:{$fldName}");
+							// 		if (!$simple && !$acl->canReadObjectField($fldName, (object)$item)) {
+							// 			$xmlWriter->writeAttribute("{$rootWfsNs}:allow_read", "false");
+							// 		}
+							// 		if (!$simple && $acl->canWriteObjectField($fldName, (object)$item)) {
+							// 			$xmlWriter->writeAttribute("{$rootWfsNs}:allow_write", "true");
+							// 		}
+							// 		$xmlWriter->writeAttribute('xlink:href', $xlink);
+							// 	$xmlWriter->endElement();// {$wfsNs}:{$fldName}
 							} else {
-								throw new Exception("Error Processing Request - too many refs for ".$acl->getName().".{$itemKey}/{$fldName}");
+								// $xmlWriter->writeComment("TODO: ".$acl->getName().".{$itemKey}/{$fldName} ...");
+								$fieldNs = str_replace(['__x3A__', ':'], '/', $fldName); // substr($xsdType, 4));
+								if (!array_key_exists($fieldNs, $schemaCache)) {
+									$xmlWriter->writeComment("Error Processing Request - field is not ref ".$acl->getName().".{$itemKey}/{$fldName}");
+								} else {
+									foreach ($item[$fldName] as $refItem) {
+										DBG::log($refItem, 'array', "\$refItem fld({$fldName})");
+										if (1 == count($refItem) && !empty($refItem['xlink'])) {
+											$xmlWriter->startElement($schemaCache[$fieldNs]['typeName']);
+											$xmlWriter->writeAttribute("xlink:href", $refItem['xlink']);
+											$xmlWriter->endElement();
+										} else {
+											$xmlWriter->startElement($schemaCache[$fieldNs]['typeName']);
+											foreach ($schemaCache[$fieldNs]['field'] as $field) {
+												// $xmlWriter->writeComment("REF field ({$field['fieldNamespace']}) value({$refItem[$field['fieldNamespace']]})");
+												if (array_key_exists($field['fieldNamespace'], $refItem)) {
+													$xmlWriter->startElement("{$schemaCache[$fieldNs]['nsPrefix']}:{$field['fieldNamespace']}");
+													$xmlWriter->text($refItem[$field['fieldNamespace']]);
+													$xmlWriter->endElement();
+												}
+											}
+											$xmlWriter->endElement();
+										}
+									}
+								}
 							}
 						} else if ('xsd:base64Binary' === $acl->getXsdFieldType($fldName)) {
 							if (empty($item[$fldName]) && '0' !== $item[$fldName]) continue;

+ 121 - 53
SE/se-lib/Api/WfsServerBase.php

@@ -5,7 +5,7 @@ Lib::loadClass('Api_WfsGeomTypeConverter');
 Lib::loadClass('Api_WfsNs');
 Lib::loadClass('Request');
 Lib::loadClass('Core_AclHelper');
-Lib::loadClass('Core_XMLWriter');
+Lib::loadClass('Core_XmlWriter');
 
 class Api_WfsServerBase {
 
@@ -52,6 +52,29 @@ class Api_WfsServerBase {
 			}
 		}
 
+		try { // TODO: use object cache `CRM_#CACHE_ACL_OBJECT`
+			$namespace = str_replace([':', '__x3A__'], '/', $typeName);
+			Lib::loadClass('SchemaFactory');
+			$objItem = SchemaFactory::loadDefaultObject('SystemObject')->getItem($namespace, ['propertyName'=>"*,field"]);
+			DBG::log($objItem, 'array', "DBG objItem({$namespace})");
+			if (!$objItem['idZasob']) throw new Exception("Missing idZasob for namespace '{$namespace}'");
+			if (!in_array($objItem['_type'], [
+				// 'TableAcl', // TODO: TEST - to replace TableAcl by AntAcl or use object with namespace + '/tableName'?
+				'AntAcl',
+			])) throw new Exception("Not Implemented acl type '{$objItem['_type']}'");
+			if (!$objItem['isObjectActive']) {
+				if (!$objItem['hasStruct']) throw new Exception("namespace has no structure '{$namespace}'");
+				if (!$objItem['isStructInstalled']) throw new Exception("namespace structure not installed '{$namespace}'");
+				throw new Exception("namespace is not activated '{$namespace}'");
+			}
+
+			Lib::loadClass('AntAclBase');
+			$acl = AntAclBase::buildInstance($objItem['idZasob'], $objItem);
+			return $acl;
+		} catch (Exception $e) {
+		  DBG::log($e);
+		}
+
 		$typeEx = explode(':', $typeName);
 		$sourceName = $typeEx[0];
 		$objName = $typeEx[1];
@@ -107,8 +130,14 @@ class Api_WfsServerBase {
 		if (empty($args['ogc:filter'])) {// read ogc:Filter from POST body if not defined in GET
 			$reqBody = Request::getRequestBody();
 			if (!empty($reqBody)) {
-				$args['ogc:filter'] = $this->convertOgcFilterFromRequestBody($reqBody);
-				if (empty($args['ogc:filter'])) throw new Api_WfsException("Error Processing ogc:Filter", 501);
+				$parsedRequest = $this->parseOgcFilterRequest($reqBody);
+				DBG::log($parsedRequest, 'array', 'parsed ogc query request');
+				if (!empty($parsedRequest['ogc:Filter'])) $args['ogc:filter'] = $parsedRequest['ogc:Filter'];
+				if (!empty($parsedRequest['wfs:PropertyName'])) {
+					foreach ($parsedRequest['wfs:PropertyName'] as $fieldXpath) {
+						$args['filterFields'][] = $fieldXpath;
+					}
+				}
 			}
 		}
 
@@ -116,7 +145,6 @@ class Api_WfsServerBase {
 
 		$args['wfs:propertyName'] = trim(V::get('propertyname', '', $lowerArgs));// TODO: fields to show - split by ','
 		if (strlen($args['wfs:propertyName']) > 0) {
-			$args['filterFields'] = array();
 			$exFields = explode(',', $args['wfs:propertyName']);
 			foreach ($exFields as $fieldName) {
 				$fieldName = trim($fieldName);
@@ -169,11 +197,11 @@ class Api_WfsServerBase {
 	public function _getCapabilities($wfsServerUrl, $serviceTitle, $serviceDescription) {
 		if (V::get('DBG_ACL', '', $_GET)) {
 			{
-				echo "Core_AclHelper::getAclList = [" . "\n";
-				foreach (Core_AclHelper::getAclList() as $typeName) {
+				echo "Core_AclHelper::getCustomAclList = [" . "\n";
+				foreach (Core_AclHelper::getCustomAclList() as $typeName) {
 					echo "|\t{$typeName}" . "\n";
 				}
-				echo "]// .EOF Core_AclHelper::getAclList" . "\n";
+				echo "]// .EOF Core_AclHelper::getCustomAclList" . "\n";
 			}
 
 			$fullTblAclList = $this->_usrAcl->getTablesAcl();
@@ -181,7 +209,7 @@ class Api_WfsServerBase {
 			die("\n" . '.EOF - DBG_ACL');
 		}
 		header('Content-type: application/xml; charset=utf-8');
-		$xmlWriter = new Core_XMLWriter();
+		$xmlWriter = new Core_XmlWriter();
 		if (!$xmlWriter) throw new HttpException("Error no XMLWriter", 404);
 		$xmlWriter->openUri('php://output');
 		$xmlWriter->setIndent(true);
@@ -299,10 +327,12 @@ class Api_WfsServerBase {
 
 		$featureTypeNodes = [];
 		foreach ($this->_getTableAclList() as $tblAcl) {
+			$ns = Core_AclHelper::parseNamespaceUrl($tblAcl->getNamespace());
+			DBG::log(['ns'=>$ns, 'acl_ns'=>$tblAcl->getNamespace(), 'cls'=>get_class($tblAcl)], 'array', "tblAcl [".$tblAcl->getID()."]");
 			$dataSourceName = 'default_db';// TODO: $tblAcl->getSourceName()
 			$prefix = "p5_{$dataSourceName}";
 			$featureTypeNodes[] = [ 'FeatureType', [ "xmlns:{$prefix}" => Api_WfsNs::getNsUri($prefix) ], [
-				[ 'Name', $prefix . ':' . $tblAcl->getName() ],
+				[ 'Name', "p5_" . "{$ns['prefix']}:{$ns['name']}" ], // TODO: remove 'p5_' prefix
 				[ 'Title', $tblAcl->getRawLabel() ],
 				[ 'Abstract', $tblAcl->getRawOpis() ],
 				[ 'Keywords', implode(', ', [ $tblAcl->getID(), $tblAcl->getName(), $tblAcl->getRawLabel() ]) ],
@@ -312,20 +342,25 @@ class Api_WfsServerBase {
 					'miny' => "38.8575126897477",
 					'maxx' => "9.838674658246807",
 					'maxy' => "41.31378404137082"], null ]
-			]];
+			] ];
 		}
-		foreach (Core_AclHelper::getAclList() as $typeName) {
+		foreach (Core_AclHelper::getCustomAclList() as $typeName) {
 			list($prefix, $name) = explode(':', $typeName);
 			$featureTypeNodes[] = [ 'FeatureType', [ "xmlns:{$prefix}" => Api_WfsNs::getNsUri($prefix) ], [
-				[ 'Name', $name ],
+				[ 'Name', "{$prefix}:{$name}" ],
 				[ 'Title', $name ],
 				[ 'Abstract', $name ],
 				[ 'Keywords', $name ],
 				[ 'SRS', 'EPSG:4326' ],
-			]];
+			] ];
 		}
 		$xmlWriter->startElement('FeatureTypeList');
 		$xmlWriter->h('Operations', ['Query', 'Insert', 'Update', 'Delete', 'Lock']);
+		if (DBG::isActive()) {
+			DBG::log(array_map(function ($feature) {
+				return "{$feature[2][0][1]}, ".array_values($feature[1])[0];
+			}, $featureTypeNodes), 'array', "\$featureTypeNodes");
+		}
 		foreach ($featureTypeNodes as $node) {
 			$xmlWriter->h($node);
 		}
@@ -1263,7 +1298,7 @@ if($DBG){echo 'L.' . __LINE__ . ' $validateConvertedTransactionXsdString:';print
 		$featureTypeUri = $this->getBaseUri() . "?SERVICE=WFS&VERSION=1.0.0&TYPENAME={$args['xsd:type']}&REQUEST=DescribeFeatureType";
 
 		header('Content-type: application/xml; charset=utf-8');
-		$xmlWriter = new Core_XMLWriter();
+		$xmlWriter = new Core_XmlWriter();
 		if (!$xmlWriter) throw new HttpException("Error no XMLWriter", 404);
 		$xmlWriter->openUri('php://output');
 		$xmlWriter->setIndent(true);
@@ -1290,7 +1325,7 @@ if($DBG){echo 'L.' . __LINE__ . ' $validateConvertedTransactionXsdString:';print
 		$acl = $this->getAclFromTypeName("{$nsPrefix}:{$name}");
 
 		header('Content-type: application/xml; charset=utf-8');
-		$xmlWriter = new Core_XMLWriter();
+		$xmlWriter = new Core_XmlWriter();
 		if (!$xmlWriter) throw new HttpException("Error no XMLWriter", 404);
 		$xmlWriter->openUri('php://output');
 		$xmlWriter->setIndent(true);
@@ -1414,7 +1449,7 @@ if($DBG){echo 'L.' . __LINE__ . ' $validateConvertedTransactionXsdString:';print
 			if (!array_key_exists($aclNamespaceUri, $nsMap)) $nsMap[$aclNamespaceUri] = $acl->getSourceName();
 		}
 
-		$xmlWriter = new Core_XMLWriter();
+		$xmlWriter = new Core_XmlWriter();
 		if (!$xmlWriter) throw new HttpException("Error no XMLWriter", 404);
 		$xmlWriter->openMemory();// openUri('php://output');
 		$xmlWriter->setIndent(true);
@@ -1677,15 +1712,15 @@ if($DBG){echo 'L.' . __LINE__ . ' $validateConvertedTransactionXsdString:';print
 			if ($idDefaultDB != $tblAcl->getDB()) {// hide non default_db tables
 				continue;
 			}
-			try {
-				$acl = $this->getAclFromTypeName($typeName = "p5_{$dataSourceName}:{$tblName}");
-			} catch (Exception $e) {
-				// echo "Error for table({$tblName}): " . $e->getMessage() . "\n";
-			}
-			if (!$acl) {
-				// TODO: error log msg
-				continue;
-			}
+			// try {
+			// 	$acl = $this->getAclFromTypeName($typeName = "p5_{$dataSourceName}:{$tblName}");
+			// } catch (Exception $e) {
+			// 	// echo "Error for table({$tblName}): " . $e->getMessage() . "\n";
+			// }
+			// if (!$acl) {
+			// 	// TODO: error log msg
+			// 	continue;
+			// }
 			$tblAclList[] = $tblAcl;
 		}
 		return $tblAclList;
@@ -1705,38 +1740,71 @@ if($DBG){echo 'L.' . __LINE__ . ' $validateConvertedTransactionXsdString:';print
 		$this->_logger->DBG($reqLog, $lineNr, $funName, $className);
 	}
 
-	public function convertOgcFilterFromRequestBody($requestOgcFilter) {
-		$ogcFilter = '';
-		if (empty($requestOgcFilter)) return '';
-		{
-			$convertOgcFilterXslString .= <<<EOF
-<xsl:transform version="1.0"
-							 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
-							 xmlns:wfs="http://www.opengis.net/wfs"
-							 xmlns:ogc="http://www.opengis.net/ogc"
-							 xmlns:gml="http://www.opengis.net/gml">
-	<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
-	<xsl:template match="/">
-	<xsl:for-each select="//*[local-name() = 'GetFeature']">
-	  <xsl:copy-of select="ogc:Filter"/>
-	</xsl:for-each>
-	</xsl:template>
-</xsl:transform>
-EOF;
+	public function parseOgcFilterRequest($requestOgcFilter) {
+		// GetFeature: @maxFeatures, @traverseXlinkDepth, @traverseXlinkExpiry
+		// \-- (1..) wfs:Query: @typeName
+		//     \--- (0..) ---: wfs:PropertyName
+		//                   : wfs:XlinkPropertyName
+		//                   : ogc:Function
+		//          ogc:Filter
+		//          ogc:SortBy
+		//          \--- (1..) ogc:SortProperty
+		//               \---: ogc:PropertyName
+		//                   : ogc:SortOrder ( DESC / ASC )
+		// <ogc:SortBy>
+		//     <ogc:SortProperty>
+		//         <ogc:PropertyName>ASTA</ogc:PropertyName>
+		//         <ogc:SortOrder>ASC</ogc:SortOrder>
+		//     </ogc:SortProperty>
+		// </ogc:SortBy>
 
-			DBG::_('DBG_XML', '>2', "convertOgcFilterXslString", $convertOgcFilterXslString, __CLASS__, __FUNCTION__, __LINE__);
-			$convertTransactionXsl = new DOMDocument();
-			$convertTransactionXsl->loadXml($convertOgcFilterXslString);
+		if (empty($requestOgcFilter)) return '';
+		$requestXml = new DOMDocument();
+		$requestXml->loadXml($requestOgcFilter);
+		$nodesQuery = [];
+		foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/wfs', 'Query') as $element) {
+			DBG::log($element->nodeName, 'array', "main loop - wfs:Query");
+			$nodesQuery[] = $element;
+		}
 
-			$requestXml = new DOMDocument();
-			$requestXml->loadXml($requestOgcFilter);
+		if (empty($nodesQuery)) { // legacy - try to find 'ogc:Filter'
+			$tagsFilter = [];
+			foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/ogc', 'Filter') as $element) {
+				DBG::log($element->nodeName, 'array', "loop element (0 * wfs:Query)");
+				$tagsFilter[] = $requestXml->saveXML($element);
+			}
+			DBG::log($tagsFilter, 'array', "\$tagsFilter (0 * wfs:Query)");
+			return [
+				'ogc:Filter' => (!empty($tagsFilter))
+					? reset($tagsFilter) // TODO: only one ogc:Filter allowed
+					: ''
+			];
+		}
 
-			$proc = new XSLTProcessor();
-			$proc->importStylesheet($convertTransactionXsl);
-			$ogcFilter = $proc->transformToXML($requestXml);
-			DBG::_('DBG_XML', '>2', "ogcFilter", $ogcFilter, __CLASS__, __FUNCTION__, __LINE__);
+		if (1 === count($nodesQuery)) {
+			$tagsFilter = [];
+			foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/ogc', 'Filter') as $element) {
+				// DBG::log($element->nodeName, 'array', "loop ogc:Filter (1 * wfs:Query)");
+				$tagsFilter[] = $requestXml->saveXML($element);
+			}
+			DBG::log($tagsFilter, 'array', "\$tagsFilter (1 * wfs:Query)");
+			$tagsWfsPropertyName = [];
+			foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/wfs', 'PropertyName') as $element) {
+				// DBG::log($element->nodeValue, 'array', "loop wfs:PropertyName (1 * wfs:Query)");
+				$tagsWfsPropertyName[] = $element->nodeValue;
+			}
+			DBG::log($tagsWfsPropertyName, 'array', "\$tagsWfsPropertyName (1 * wfs:Query)");
+			return array_filter([
+				'ogc:Filter' => (!empty($tagsFilter))
+					? reset($tagsFilter)
+					: '',
+				'wfs:PropertyName' => (!empty($tagsWfsPropertyName))
+					? $tagsWfsPropertyName
+					: '',
+			], function ($value) { return !empty($value); });
 		}
-		return $ogcFilter;
+
+		throw new Exception("Not imeplemented multiple ogc:Query"); // multiple ogc:Query require ogc:Query/@typeName
 	}
 
 }

+ 29 - 3
SE/se-lib/Route/Storage/AclStruct.php

@@ -347,6 +347,12 @@ class Route_Storage_AclStruct extends RouteBase {
 						where t.`namespace` = '{$refNamespace}'
 					"));
 				});
+
+				$previewItems = ACL::getAclByNamespace($namespace)->buildQuery([])->getItems([
+					'limit' => 5
+				]);
+				DBG::nicePrint($previewItems, 'items (default query) - preview (limit 5)');
+
 				$query = [ 'cols' => array_merge(
 					array_map(
 						function ($field) {
@@ -359,12 +365,32 @@ class Route_Storage_AclStruct extends RouteBase {
 						}, $activeRefFields
 					)
 				) ];
-				DBG::nicePrint($query, '$query');
+				DBG::nicePrint($query, 'items with ref/* - query');
 
 				$previewItems = ACL::getAclByNamespace($namespace)->buildQuery($query)->getItems([
-					'limit' => 3
+					'limit' => 5
 				]);
-				DBG::nicePrint($previewItems, 'items with ref limit 3');
+				DBG::nicePrint($previewItems, 'items with ref/* - preview (limit 5)');
+
+				$query = [ 'cols' => array_merge(
+					array_map(
+						function ($field) {
+							return $field['fieldNamespace'];
+						}, $localFields
+					),
+					array_map(
+						function ($field) {
+							return "{$field['fieldNamespace']}";
+						}, $activeRefFields
+					)
+				) ];
+				DBG::nicePrint($query, 'items with ref as xlink - query');
+
+				$previewItems = ACL::getAclByNamespace($namespace)->buildQuery($query)->getItems([
+					'limit' => 5
+				]);
+				DBG::nicePrint($previewItems, 'items with ref as xlink - preview (limit 5)');
+
 			} catch (Exception $e) {
 				DBG::log($e);
 				UI::alert('danger', $e->getMessage());

+ 1 - 1
SE/se-lib/TableAcl.php

@@ -997,7 +997,7 @@ class TableAcl extends Core_AclBase {
 
 		$type = (false !== strpos($tableConfig['name'], '/')) ? 'AntAcl' : 'TableAcl'; // TODO: fix naive check this // $type = DB::getPDO()->fetchValue(" select o._type from `CRM_#CACHE_ACL_OBJECT` o where o.idZasob = '{$idTable}' limit 1 ");
 		if ('AntAcl' === $type) {
-			DBG::log("TODO: AntAcl save config in TableAcl::buildInstance [{$idTable}] '{$tableConfig['name']}'");
+			DBG::log("AntAcl save config in TableAcl::buildInstance [{$idTable}] '{$tableConfig['name']}'");
 			$_SESSION['AntAcl_cache'][$idTable] = true;
 		} else { // TableAcl
 			$obj = new TableAcl($idTable);