GetFeature.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. <?php
  2. Lib::loadClass('Api_WfsGeomTypeConverter');
  3. Lib::loadClass('Core_AclHelper');
  4. class Api_Wfs_GetFeature {
  5. public static function convertOgcPropertyListToFeatureQueryCols(&$schemaCache, $ogcPropertyList, $acl) {
  6. return self::convertOgcPropsRecurse($schemaCache, $ogcPropertyList, $acl, $dbgLoopNr = 0, $dbgXpath = $acl->getNamespace());
  7. }
  8. public static function convertOgcPropsRecurse(&$schemaCache, $ogcPropertyList, $aclOrSchema, $dbgLoopNr = 0, $dbgXpath = '') {
  9. if ($dbgLoopNr > 10) {
  10. DBG::log($ogcPropertyList, 'array', 'convertOgcPropsRecurse: LOOP LIMIT 10! - return []');
  11. return [];
  12. }
  13. $acl = null;
  14. if (is_array($aclOrSchema) && !empty($aclOrSchema['namespace'])) {
  15. $acl = Core_AclHelper::getAclByNamespace($aclOrSchema['namespace'], false, $aclOrSchema);
  16. } else if ($aclOrSchema instanceof Core_AclBase) {
  17. $acl = $aclOrSchema;
  18. } else throw new Exception("Missing acl");
  19. if (empty($ogcPropertyList)) {
  20. $ogcPropertyList = array_values($acl->getFieldListByIdZasob());
  21. $ogcPropertyList = array_filter($ogcPropertyList, function ($fieldName) use ($acl) {
  22. return $acl->canReadField($fieldName);
  23. });
  24. }
  25. $aclFields = array_filter($ogcPropertyList, function ($prop) { return ( false === strpos($prop, '/') ); });
  26. $nestedFields = array_reduce(
  27. array_filter($ogcPropertyList, function ($prop) { return ( false !== strpos($prop, '/') ); }),
  28. function ($ret, $propNested) {
  29. list($childName, $nestedPart) = explode('/', $propNested, 2);
  30. if (!array_key_exists($childName, $ret)) $ret[ $childName ] = [];
  31. $ret[ $childName ][] = $nestedPart;
  32. return $ret;
  33. },
  34. []
  35. );
  36. if (!empty($nestedFields)) {
  37. if (array_key_exists('*', $nestedFields)) { // add missing local fields if xpath like '*/...'
  38. $aclFields[] = '*';
  39. }
  40. }
  41. if (!empty($nestedFields)) { // add nested part to refs if xpath like '*/...'
  42. if (array_key_exists('*', $nestedFields)) {
  43. $nestedParts = $nestedFields['*'];
  44. unset($nestedFields['*']);
  45. DBG::log($nestedParts, 'array', "convertOgcPropsRecurse: TODO: '*' in \$nestedFields - \$nestedParts ns({$dbgXpath})");
  46. $canReadRefs = array_values($acl->getFieldListByIdZasob());
  47. $canReadRefs = array_filter($canReadRefs, function ($fieldName) use ($acl) {
  48. return (false !== strpos($fieldName, ":") && $acl->canReadField($fieldName));
  49. });
  50. foreach ($canReadRefs as $refField) {
  51. foreach ($nestedParts as $nestedPart) {
  52. $nestedFields[ $refField ][] = $nestedPart;
  53. }
  54. }
  55. }
  56. }
  57. if (!empty($nestedFields)) {
  58. $aclFields = array_unique(array_merge($aclFields, array_keys($nestedFields)));
  59. }
  60. if (in_array('*', $aclFields)) {
  61. $aclFields = array_filter($aclFields, function ($fieldName) { return ( '*' !== $fieldName); });
  62. $allAclFields = array_values($acl->getFieldListByIdZasob());
  63. $allAclFields = array_filter($allAclFields, function ($fieldName) use ($acl) {
  64. return $acl->canReadField($fieldName);
  65. });
  66. if ($allAclFields) $aclFields = array_unique(array_merge($aclFields, $allAclFields));
  67. }
  68. DBG::log([$aclFields, $nestedFields], 'array', "convertOgcPropsRecurse: splited to acl fields and nested fields ns({$dbgXpath})");
  69. foreach ($aclFields as $fieldName) {
  70. if (!$acl->canReadField($fieldName)) throw new Exception("Access Denied to read field '{$fieldName}' from '" . $acl->getNamespace() . "'");
  71. }
  72. foreach ($aclFields as $fieldName) {
  73. if (false !== strpos($fieldName, ':')) {
  74. $childNs = str_replace(['__x3A__', ':'], '/', $fieldName);
  75. $schemaCache[$childNs] = SchemaFactory::loadDefaultObject('SystemObject')->getItem($childNs, [ 'propertyName' => '*,field' ]);
  76. }
  77. }
  78. $contextFieldList = $aclFields;
  79. foreach ($nestedFields as $childName => $nestedProps) {
  80. $childNs = str_replace(['__x3A__', ':'], '/', $childName);
  81. if (!array_key_exists($childNs, $schemaCache)) {
  82. $schemaCache[$childNs] = SchemaFactory::loadDefaultObject('SystemObject')->getItem($childNs, [ 'propertyName' => '*,field' ]);
  83. }
  84. $childCtxFields = array_map(function ($childCtxFld) use ($childName) {
  85. return "{$childName}/{$childCtxFld}";
  86. }, self::convertOgcPropsRecurse($schemaCache, $nestedProps, $schemaCache[$childNs], $dbgLoopNr + 1, "{$dbgXpath}/{$childName}"));
  87. DBG::log([ $contextFieldList, $childCtxFields ], 'array', "convertOgcPropsRecurse: array merge ns({$dbgXpath})");
  88. $contextFieldList = array_merge( $contextFieldList, $childCtxFields );
  89. DBG::log($contextFieldList, 'array', "convertOgcPropsRecurse: array merged ns({$dbgXpath})");
  90. }
  91. DBG::log($contextFieldList, 'array', "convertOgcPropsRecurse: return \$contextFieldList ns({$dbgXpath})");
  92. return $contextFieldList;
  93. }
  94. // TODO: add $contextAcl and context xpath to check for special perms by contextAcl
  95. /** @example:
  96. * Api_Wfs_GetFeature::printXmlFeatureRecurse($xmlWriter, $acl, $item, [
  97. * 'fields' => $searchParams['cols'],
  98. * 'tagName' => "{$wfsNs}:{$type}",
  99. * 'showAdvancedAttrs' => !$simple,
  100. * 'xsdAttributes' => array_merge(
  101. * [ 'fid' => "{$type}.{$itemKey}" ],
  102. * (!$simple)
  103. * ? [ "{$rootWfsNs}:web_link" => Request::getPathUri() . "index.php?_route=ViewTableAjax&namespace=" . $acl->getNamespace() . "#EDIT/{$itemKey}" ]
  104. * : []
  105. * )
  106. * ], $schemaCache, $printedFidLog);
  107. */
  108. public static function printXmlFeatureRecurse($xmlWriter, $acl, $item, $params = [], $schemaCache = [], $printedFidLog = []) {
  109. $showFields = V::get('fields', [], $params, 'array'); // TODO: if empty $showFields ?
  110. $tagName = V::get('tagName', '', $params);
  111. $attrs = V::get('xsdAttributes', [], $params, 'array');
  112. $showAdvancedAttrs = V::get('showAdvancedAttrs', false, $params, 'bool');
  113. $dbgFid = V::get('fid', 0, $attrs);
  114. $outputBlobFormat = V::get('outputBlobFormat', '', $params, 'word');
  115. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: print Xml Feature Recurse... '{$tagName}'" . ( $dbgFid ? " fid='{$dbgFid}'" : "" )); // TODO: DBG
  116. // DBG::log($acl, 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$acl)" . ( $dbgFid ? " fid='{$dbgFid}'" : "" ) . " \$acl");
  117. DBG::log($item, 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$item)" . ( $dbgFid ? " fid='{$dbgFid}'" : "" ) . " \$item");
  118. DBG::log($showFields, 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$item)" . ( $dbgFid ? " fid='{$dbgFid}'" : "" ) . " \$showFields");
  119. DBG::log([$attrs, $showAdvancedAttrs, array_keys($schemaCache), $printedFidLog], 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$attrs, \$showAdvancedAttrs, keys(\$schemaCache), \$printedFidLog)");
  120. if ($dbgFid) {
  121. if (in_array($dbgFid, $printedFidLog)) {
  122. DBG::log("TODO: in_array({$dbgFid}, \$printedFidLog)");
  123. }
  124. }
  125. if ($dbgFid) $printedFidLog[] = $dbgFid;
  126. // $rootWfsNs = 'p5';
  127. list($itemPrefix, $localName) = explode(':', $tagName);
  128. if (1 === count($item) && !empty($item['xlink'])) {
  129. // @example 'xlink' => 'https://biuro.biall-net.pl/wfs/default_db/CRM_PROCES#PROCES.857'
  130. $xlink = $item['xlink'];
  131. list($xlinkUrl, $xlinkFid) = explode('#', $xlink);
  132. DBG::log($item[$fldName], 'array', "xlinks for '{$tagName}'");
  133. $xmlWriter->startElement($tagName);
  134. foreach ($attrs as $name => $value) {
  135. $xmlWriter->writeAttribute($name, $value);
  136. }
  137. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  138. $xmlWriter->writeAttribute("p5:allow_read", "false");
  139. }
  140. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  141. $xmlWriter->writeAttribute("p5:allow_write", "true");
  142. }
  143. $xmlWriter->writeAttribute('xlink:href', $xlink);
  144. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  145. return;
  146. }
  147. $xmlWriter->startElement($tagName);
  148. foreach ($attrs as $name => $value) {
  149. $xmlWriter->writeAttribute($name, $value);
  150. }
  151. // $fldList = $acl->getRealFieldListByIdZasob();
  152. $fldList = $acl->getFieldListByIdZasob();
  153. $geomFld = null;
  154. foreach ($fldList as $fldName) {
  155. if ($acl->isGeomField($fldName)) {
  156. $geomFld = $fldName;
  157. }
  158. }
  159. DBG::log($fldList, 'array', ">>> loop start fields(".count($fldList).")");
  160. // DBG::log($showFields, 'array', "DBG: \$showLocalFields \$showFields 0");
  161. $showLocalFields = ($showFields) // if empty filter cols show all local fields
  162. ? array_filter($showFields, function ($showFieldName) {
  163. return (false === strpos($showFieldName, "/"));
  164. })
  165. : $fldList;
  166. DBG::log($showLocalFields, 'array', "DBG: \$showLocalFields");
  167. $fldList = array_filter($fldList, function ($fieldName) use ($showLocalFields) {
  168. return (in_array($fieldName, $showLocalFields));
  169. });
  170. DBG::log($fldList, 'array', ">>> loop start filtered fields(".count($fldList).")");
  171. foreach ($fldList as $idZasob => $fldName) {
  172. DBG::log(">>> loop {$idZasob} => {$fldName}...");
  173. $fldType = $acl->getXsdFieldType($fldName);
  174. DBG::log(">>> loop '{$fldName}' xsdType: '{$fldType}'");
  175. if (!$acl->canReadObjectField($fldName, (object)$item)) if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: skip - !canReadObjectField('{$fldName}')"); // TODO: DBG
  176. if (!$acl->canReadObjectField($fldName, (object)$item)) continue;
  177. DBG::log(">>> loop '{$fldName}' can read...");
  178. if ($geomFld != null && $fldName == $geomFld) {
  179. $xmlWriter->startElement("{$itemPrefix}:{$fldName}");
  180. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  181. $xmlWriter->writeAttribute("p5:allow_read", "false");
  182. }
  183. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  184. $xmlWriter->writeAttribute("p5:allow_write", "true");
  185. }
  186. (new Api_WfsGeomTypeConverter())->createGmlFromWkt_xmlWriter($item[$fldName], $xmlWriter);
  187. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  188. } else if (is_array($item[$fldName])) {// TODO: by struct - REF field
  189. DBG::log($item[$fldName], 'array', ">>> loop({$itemKey}) REF item[{$itemKey}][{$fldName}]");
  190. if (empty($item[$fldName])) { // SKIP empty fields
  191. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: skip empty field '{$fldName}'"); // TODO: DBG
  192. // $xmlWriter->h($fldName);
  193. } else {
  194. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: TODO: array field... '{$fldName}'"); // TODO: DBG
  195. // $xmlWriter->writeComment("TODO: ".$acl->getName().".{$itemKey}/{$fldName} ...");
  196. $fieldNs = str_replace(['__x3A__', ':'], '/', $fldName); // substr($xsdType, 4));
  197. if (!array_key_exists($fieldNs, $schemaCache)) {
  198. // maybe only xlinks - acl not needed
  199. $firstItem = reset($item[$fldName]);
  200. if (1 === count($firstItem) && !empty($firstItem['xlink'])) { // TODO: $schemaCache[$fieldNs] must exists for xlinks - xlmns is required
  201. foreach ($item[$fldName] as $childItem) {
  202. self::printXmlFeatureRecurse($xmlWriter, $childAcl = null, $childItem, [
  203. 'fields' => [],
  204. 'tagName' => $fldName,
  205. 'xsdAttributes' => [],
  206. 'showAdvancedAttrs' => $showAdvancedAttrs,
  207. 'outputBlobFormat' => $outputBlobFormat,
  208. ], $schemaCache, $printedFidLog);
  209. }
  210. } else {
  211. DBG::log($schemaCache, 'array', "Error Processing Request - field is not ref or missing acl ".$acl->getName().".{$itemKey}/{$fldName}");
  212. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("Error Processing Request - field is not ref or missing acl ".$acl->getName().".{$itemKey}/{$fldName}");
  213. }
  214. } else {
  215. DBG::log($schemaCache[$fieldNs], 'array', "fetch child acl ".$acl->getName().".{$itemKey}/{$fldName}");
  216. $childAcl = Core_AclHelper::getAclByNamespace($schemaCache[$fieldNs]['namespace'], false, $schemaCache[$fieldNs]);
  217. $childName = $schemaCache[$fieldNs]['name'];
  218. foreach ($item[$fldName] as $childItem) {
  219. // DBG::log($showFields, 'array', "DBG: fld({$fldName}) \$showFields 0");
  220. $showChildFields = array_filter($showFields, function ($showFieldName) use ($fldName) {
  221. return ("{$fldName}/" === substr("{$showFieldName}", 0, strlen($fldName) + 1));
  222. });
  223. // DBG::log($showChildFields, 'array', "DBG: fld({$fldName}) \$showChildFields 1");
  224. $showChildFields = array_map(function ($showFieldName) use ($fldName) {
  225. return substr($showFieldName, strlen($fldName) + 1);
  226. }, $showChildFields);
  227. // DBG::log($showChildFields, 'array', "DBG: fld({$fldName}) \$showChildFields 2");
  228. $childPK = V::get($childAcl->getPrimaryKeyField(), '', $childItem);
  229. self::printXmlFeatureRecurse($xmlWriter, $childAcl, $childItem, [
  230. 'fields' => $showChildFields,
  231. 'tagName' => $fldName,
  232. 'xsdAttributes' => ($childPK) ? [ 'fid' => "{$childName}.{$childPK}" ] : [],
  233. 'showAdvancedAttrs' => $showAdvancedAttrs,
  234. 'outputBlobFormat' => $outputBlobFormat,
  235. ], $schemaCache, $printedFidLog);
  236. }
  237. // foreach ($item[$fldName] as $refItem) {
  238. // DBG::log($refItem, 'array', "\$refItem fld({$fldName})");
  239. // if (1 == count($refItem) && !empty($refItem['xlink'])) {
  240. // $xmlWriter->startElement($schemaCache[$fieldNs]['typeName']);
  241. // $xmlWriter->writeAttribute("xlink:href", $refItem['xlink']);
  242. // $xmlWriter->endElement();
  243. // } else {
  244. // $xmlWriter->writeComment("DBG: array field ref ... '{$fldName}'"); // TODO: DBG
  245. // $xmlWriter->startElement($schemaCache[$fieldNs]['typeName']);
  246. // foreach ($schemaCache[$fieldNs]['field'] as $field) {
  247. // if (array_key_exists($field['fieldNamespace'], $refItem)) {
  248. // $xmlWriter->writeComment("REF field ({$field['fieldNamespace']}) value({$refItem[$field['fieldNamespace']]}) TODO: get xsdType - TODO: recurse");
  249. // DBG::log($refItem[$field['fieldNamespace']], 'array', "REF field ({$field['fieldNamespace']}) TODO: get xsdType - TODO: recurse");
  250. // if (false !== strpos($field['fieldNamespace'], ':')) { // is ref - TODO: better check by xsdType
  251. // $xmlWriter->startElement($field['fieldNamespace']);
  252. // $xmlWriter->writeComment("TODO: recurse ...");
  253. // // $xmlWriter->text($refItem[$field['fieldNamespace']]);
  254. // $xmlWriter->endElement();
  255. // } else {
  256. // $xmlWriter->startElement("{$schemaCache[$fieldNs]['nsPrefix']}:{$field['fieldNamespace']}");
  257. // $xmlWriter->text($refItem[$field['fieldNamespace']]);
  258. // $xmlWriter->endElement();
  259. // }
  260. // }
  261. // }
  262. // $xmlWriter->endElement();
  263. // }
  264. // }
  265. }
  266. }
  267. } else if ('xsd:base64Binary' === $acl->getXsdFieldType($fldName)) {
  268. if (empty($item[$fldName])) continue; // && '0' !== $item[$fldName]
  269. $xmlWriter->startElement("{$itemPrefix}:{$fldName}");
  270. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  271. $xmlWriter->writeAttribute("p5:allow_read", "false");
  272. }
  273. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  274. $xmlWriter->writeAttribute("p5:allow_write", "true");
  275. }
  276. $pk = V::get($acl->getPrimaryKeyField(), '', $item);
  277. if ($showAdvancedAttrs) {
  278. $xmlWriter->writeAttribute("p5:blobUrl", Request::getPathUri() . "wfs-data.php?" . implode("&", [
  279. "SERVICE=WFS", "VERSION=1.0.0", "REQUEST=GetBlob",
  280. "namespace=" . $acl->getNamespace(),
  281. "primaryKey={$pk}",
  282. "fieldName={$fldName}",
  283. ]));
  284. }
  285. if (in_array($item[$fldName], ['0', '1'])) {
  286. if ('base64' === $outputBlobFormat) {
  287. $content = DB::getPDO( $acl->getDatabaseID() )->getBlob( $acl->getRootTableName(), $fldName, $acl->getPrimaryKeyField(), $pk );
  288. $xmlWriter->text(base64_encode($content));
  289. } else if ('link' === $outputBlobFormat) {
  290. $xmlWriter->text($item[$fldName]);
  291. } else { // hide
  292. throw new Exception("Not implemented outputBlobFormat - expected base64 or link");
  293. }
  294. } else {
  295. $xmlWriter->text(base64_encode($item[$fldName]));
  296. }
  297. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  298. } else {
  299. $value = str_replace('&', '&amp;', $item[$fldName]);
  300. if (empty($value) && '0' !== $value) {
  301. continue;
  302. } else {
  303. $xmlWriter->startElement("{$itemPrefix}:{$fldName}");
  304. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  305. $xmlWriter->writeAttribute("p5:allow_read", "false");
  306. }
  307. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  308. $xmlWriter->writeAttribute("p5:allow_write", "true");
  309. }
  310. $xmlWriter->text($value);
  311. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  312. }
  313. }
  314. }
  315. $xmlWriter->endElement();
  316. }
  317. }