GetFeature.php 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. <?php
  2. Lib::loadClass('Api_WfsGeomTypeConverter');
  3. Lib::loadClass('Core_AclHelper');
  4. class Api_Wfs_GetFeature {
  5. /**
  6. * @param $_GET [ , POST Body - XML file ]
  7. * @return array $args
  8. * TODO: get $acl and check more restrictions
  9. */
  10. public static function parseGetFeatureArgsFromRequest() {
  11. $rawArgs = $_GET;// $_REQUEST; ($_POST values not allowed, only xml in post body)
  12. $args = array();
  13. $args['root'] = ('0' === User::get('ADM_ADMIN_LEVEL') && 1 === V::get('root', 0, $rawArgs, 'int')); // NOTE: require Admin perms
  14. $args['xsd:type'] = null;// @from: TYPENAME, typeName (typename)
  15. $args['typePrefix'] = null;// @from: TYPENAME, typeName (typename)
  16. $args['typeName'] = null;// @from: TYPENAME, typeName (typename)
  17. $args['srsname'] = null;// @from: SRSNAME (srsname)
  18. $args['limit'] = 0;// @from: MAXFEATURES, maxFeatures, COUNT (maxfeatures, count)
  19. $args['offset'] = 0;// @from: startIndex (startindex, count)
  20. $args['ogc:filter'] = null;// @from: FILTER, Filter (filter)
  21. $args['sortBy'] = null;// @from: sortBy (sortby)
  22. $args['wfs:propertyName'] = null;// @from: propertyName (propertyname)
  23. $args['filterFields'] = array();// array of field names to show - @from: propertyName
  24. $args['resultType'] = null;// @from: resultType (resulttype)
  25. $args['bbox'] = null;// @from: BBOX (bbox)
  26. $args['wfs:featureID'] = null;// @from: featureID, featureId (featureid)
  27. $args['primaryKey'] = null;// primaryKey type - @from: featureID or $req[primaryKey]
  28. $args['xlink'] = V::get('xlink', '', $rawArgs); // xlink = "https://biuro.biall-net.pl/wfs/default_db/CRM_PROCES#PROCES.100" but with '#' replaced with '/'
  29. $args['outputBlobFormat'] = V::get('outputBlobFormat', 'base64', $rawArgs); // how to print blob fields ('base64', 'link') - default base64
  30. $args['resolve'] = V::get('resolve', '', $rawArgs);
  31. $args['resolveDepth'] = V::get('resolveDepth', 0, $rawArgs, 'int'); // TODO: if ('*' === resolveDepth) - recurse resolve
  32. $args['backRefPK'] = V::get('backRefPK', '', $rawArgs);
  33. $args['backRefNS'] = V::get('backRefNS', '', $rawArgs);
  34. $args['backRefField'] = V::get('backRefField', '', $rawArgs);
  35. $lowerArgs = array(); foreach ($rawArgs as $name => $value) $lowerArgs[ strtolower($name) ] = trim($value);
  36. $args['xsd:type'] = V::get('typename', '', $lowerArgs);
  37. $exType = explode(':', $args['xsd:type']);
  38. if (count($exType) != 2) throw new HttpException("Wrong param TYPENAME", 400);
  39. $args['typePrefix'] = $exType[0];
  40. $args['typeName'] = $exType[1];
  41. $args['srsname'] = V::get('SRSNAME', '', $lowerArgs);// eg. EPSG:4326
  42. $args['limit'] = V::get('maxfeatures', $args['limit'], $lowerArgs, 'int');
  43. $args['limit'] = V::get('count', $args['limit'], $lowerArgs, 'int');
  44. $defaultFeaturesLimit = ('biuro.biall-net.pl' == V::get('SERVER_NAME', '', $_SERVER)) ? 50000 : 10000;
  45. if ($args['limit'] <= 0) $args['limit'] = $defaultFeaturesLimit;// default limit
  46. $args['offset'] = V::get('startindex', 0, $lowerArgs, 'int');
  47. $args['ogc:filter'] = urldecode(V::get('filter', '', $lowerArgs));
  48. if (empty($args['ogc:filter'])) {// read ogc:Filter from POST body if not defined in GET
  49. $reqBody = Request::getRequestBody();
  50. if (!empty($reqBody)) {
  51. $parsedRequest = self::parseOgcFilterRequest($reqBody);
  52. DBG::log($parsedRequest, 'array', 'parsed ogc query request');
  53. if (!empty($parsedRequest['ogc:Filter'])) $args['ogc:filter'] = $parsedRequest['ogc:Filter'];
  54. if (!empty($parsedRequest['wfs:PropertyName'])) {
  55. $args['filterFields'] = array_merge($args['filterFields'], $parsedRequest['wfs:PropertyName']);
  56. }
  57. }
  58. }
  59. if (empty($args['filterFields']) && $args['resolve'] && $args['resolveDepth'] > 0) {
  60. $args['filterFields'][] = '*' . str_repeat("/*", $args['resolveDepth'] - 1);
  61. }
  62. $args['sortBy'] = V::get('sortby', '', $lowerArgs);// TODO: split to array of array(fieldName, (asc | desc))
  63. $args['wfs:propertyName'] = trim(V::get('propertyname', '', $lowerArgs));
  64. if (strlen($args['wfs:propertyName']) > 0) {
  65. $exFields = explode(',', $args['wfs:propertyName']);
  66. $exFields = array_map('trim', $exFields);
  67. $exFields = array_filter($exFields, [ 'V', 'filterNotEmpty' ]);
  68. $args['filterFields'] = array_merge($args['filterFields'], $exFields);
  69. }
  70. $args['resultType'] = V::get('resulttype', '', $lowerArgs);
  71. $args['bbox'] = V::get('bbox', '', $lowerArgs);
  72. if (!empty($args['bbox'])) {
  73. // BBOX may have EPSG at then end - example: ",EPSG:4326"
  74. $num = "\d+.?\d*?";// "\d+(.\d+)?"
  75. if (!preg_match("/^({$num}),({$num}),({$num}),({$num})(\,EPSG:\d+)?$/", $args['bbox'], $matches)) throw new Exception("Illegal BBOX format");
  76. // QGIS send BBOX in correct order: 54.23580872176457,18.46844302390853,54.25220902538883,18.492990600812696
  77. // first number should not be smaller then second
  78. // example $matches:
  79. // [0] => 18.492990600812696,54.23580872176457,18.46844302390853,54.25220902538883
  80. // [1] => 18.492990600812696
  81. // [2] => 54.23580872176457
  82. // [3] => 18.46844302390853
  83. // [4] => 54.25220902538883
  84. // (optional) EPGS
  85. $bboxPoints = ($matches[1] > $matches[2])
  86. ? [ $matches[1], $matches[2], $matches[3], $matches[4] ]
  87. : [ $matches[2], $matches[3], $matches[4], $matches[1] ];
  88. $args['bbox'] = implode(",", $bboxPoints);
  89. }
  90. $args['wfs:featureID'] = V::get('featureid', null, $lowerArgs);// TODO: allow multiply feature id (csv)
  91. if ($args['wfs:featureID']) {
  92. $dotPos = strpos($args['wfs:featureID'], '.');
  93. if (!$dotPos) throw new Exception("Wrong param featureID");
  94. if ($args['typeName'] != substr($args['wfs:featureID'], 0, $dotPos)) throw new Exception("Wrong typeName in param featureID");
  95. $args['primaryKey'] = substr($args['wfs:featureID'], $dotPos + 1);
  96. }
  97. $pk = V::get('primarykey', '', $lowerArgs);
  98. if ($pk) {
  99. $args['primaryKey'] = (false !== strpos($pk, ',')) ? explode(',', $pk) : $pk;
  100. }
  101. if(DBG::isActive() && V::get('DBG', 0, $_GET)){ echo "\$lowerArgs:";print_r($lowerArgs); }
  102. if(DBG::isActive() && V::get('DBG', 0, $_GET)){ echo "\$args:";print_r($args); }
  103. return $args;
  104. }
  105. public static function parseOgcFilterRequest($requestOgcFilter) {
  106. // GetFeature: @maxFeatures, @traverseXlinkDepth, @traverseXlinkExpiry
  107. // \-- (1..) wfs:Query: @typeName
  108. // \--- (0..) ---: wfs:PropertyName
  109. // : wfs:XlinkPropertyName
  110. // : ogc:Function
  111. // ogc:Filter
  112. // ogc:SortBy
  113. // \--- (1..) ogc:SortProperty
  114. // \---: ogc:PropertyName
  115. // : ogc:SortOrder ( DESC / ASC )
  116. // <ogc:SortBy>
  117. // <ogc:SortProperty>
  118. // <ogc:PropertyName>ASTA</ogc:PropertyName>
  119. // <ogc:SortOrder>ASC</ogc:SortOrder>
  120. // </ogc:SortProperty>
  121. // </ogc:SortBy>
  122. if (empty($requestOgcFilter)) return '';
  123. $requestXml = new DOMDocument();
  124. if (!$requestXml->loadXml($requestOgcFilter)) {
  125. throw new Exception("Parse ogc query failed");
  126. }
  127. $rootNode = $requestXml->documentElement;
  128. // if ($rootNode->getAttribute('resolve'))
  129. DBG::log([$rootNode->getAttribute('resolve'), $rootNode->getAttribute('resolveDepth')], 'array', "TODO: use wfs:GetFeature @resolve, @resolveDepth");
  130. $nodesQuery = [];
  131. foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/wfs', 'Query') as $element) {
  132. DBG::log($element->nodeName, 'array', "main loop - wfs:Query");
  133. $nodesQuery[] = $element;
  134. }
  135. if (empty($nodesQuery)) { // legacy - try to find 'ogc:Filter'
  136. $tagsFilter = [];
  137. foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/ogc', 'Filter') as $element) {
  138. DBG::log($element->nodeName, 'array', "loop element (0 * wfs:Query)");
  139. $tagsFilter[] = $requestXml->saveXML($element);
  140. }
  141. DBG::log($tagsFilter, 'array', "\$tagsFilter (0 * wfs:Query)");
  142. return [
  143. 'ogc:Filter' => (!empty($tagsFilter))
  144. ? reset($tagsFilter) // TODO: only one ogc:Filter allowed
  145. : ''
  146. ];
  147. }
  148. if (1 === count($nodesQuery)) {
  149. $tagsFilter = [];
  150. foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/ogc', 'Filter') as $element) {
  151. // DBG::log($element->nodeName, 'array', "loop ogc:Filter (1 * wfs:Query)");
  152. $tagsFilter[] = $requestXml->saveXML($element);
  153. }
  154. DBG::log($tagsFilter, 'array', "\$tagsFilter (1 * wfs:Query)");
  155. $tagsWfsPropertyName = [];
  156. foreach ($requestXml->getElementsByTagNameNS('http://www.opengis.net/wfs', 'PropertyName') as $element) {
  157. // DBG::log($element->nodeValue, 'array', "loop wfs:PropertyName (1 * wfs:Query)");
  158. if ($element->getAttribute('resolve')) DBG::log([$element->getAttribute('resolve'), $element->getAttribute('resolveDepth'), $element->getAttribute('resolvePath')], 'array', "TODO: use wfs:PropertyName @resolve, @resolveDepth, @resolvePath");
  159. $value = $element->nodeValue;
  160. if (in_array($element->getAttribute('resolve'), ['all', 'local', 'remote'])) {
  161. $depth = $element->getAttribute('resolveDepth');
  162. if ('*' !== substr($value, -1)) { // TODO: propertyName is not regex
  163. if ('*' === $depth) $value .= "/**"; // TODO: resolveDepth="*" - resolve all
  164. else if ((int)$depth > 0) $value .= str_repeat("/*", $depth);
  165. }
  166. }
  167. $tagsWfsPropertyName[] = $value;
  168. }
  169. DBG::log($tagsWfsPropertyName, 'array', "\$tagsWfsPropertyName (1 * wfs:Query)");
  170. return array_filter([
  171. 'ogc:Filter' => (!empty($tagsFilter))
  172. ? reset($tagsFilter)
  173. : '',
  174. 'wfs:PropertyName' => (!empty($tagsWfsPropertyName))
  175. ? $tagsWfsPropertyName
  176. : '',
  177. ], function ($value) { return !empty($value); });
  178. }
  179. throw new Exception("Not imeplemented multiple ogc:Query"); // multiple ogc:Query require ogc:Query/@typeName
  180. }
  181. public static function convertOgcPropertyListToFeatureQueryCols(&$schemaCache, $ogcPropertyList, $acl, $isRoot = false) {
  182. return self::convertOgcPropsRecurse($schemaCache, $ogcPropertyList, $acl, $isRoot, $dbgLoopNr = 0, $dbgXpath = $acl->getNamespace());
  183. }
  184. public static function convertOgcPropsRecurse(&$schemaCache, $ogcPropertyList, $aclOrSchema, $isRoot = false, $dbgLoopNr = 0, $dbgXpath = '') {
  185. if ($dbgLoopNr > 10) {
  186. DBG::log($ogcPropertyList, 'array', 'convertOgcPropsRecurse: LOOP LIMIT 10! - return []');
  187. return [];
  188. }
  189. $acl = null;
  190. if (is_array($aclOrSchema) && !empty($aclOrSchema['namespace'])) {
  191. $acl = Core_AclHelper::getAclByNamespace($aclOrSchema['namespace'], false, $aclOrSchema);
  192. } else if ($aclOrSchema instanceof Core_AclBase) {
  193. $acl = $aclOrSchema;
  194. } else throw new Exception("Missing acl");
  195. if (empty($ogcPropertyList)) {
  196. $ogcPropertyList = array_values($acl->getFieldListByIdZasob());
  197. $ogcPropertyList = array_filter($ogcPropertyList, function ($fieldName) use ($acl, $isRoot) {
  198. return ($isRoot || $acl->canReadField($fieldName));
  199. });
  200. }
  201. $aclFields = array_filter($ogcPropertyList, function ($prop) { return ( false === strpos($prop, '/') ); });
  202. $nestedFields = array_reduce(
  203. array_filter($ogcPropertyList, function ($prop) { return ( false !== strpos($prop, '/') ); }),
  204. function ($ret, $propNested) {
  205. list($childName, $nestedPart) = explode('/', $propNested, 2);
  206. if (!array_key_exists($childName, $ret)) $ret[ $childName ] = [];
  207. $ret[ $childName ][] = $nestedPart;
  208. return $ret;
  209. },
  210. []
  211. );
  212. if (!empty($nestedFields)) {
  213. if (array_key_exists('*', $nestedFields)) { // add missing local fields if xpath like '*/...'
  214. $aclFields[] = '*';
  215. }
  216. }
  217. if (!empty($nestedFields)) { // add nested part to refs if xpath like '*/...'
  218. if (array_key_exists('*', $nestedFields)) {
  219. $nestedParts = $nestedFields['*'];
  220. unset($nestedFields['*']);
  221. DBG::log($nestedParts, 'array', "convertOgcPropsRecurse: '*' in \$nestedFields - \$nestedParts ns({$dbgXpath})");
  222. $canReadRefs = array_values($acl->getFieldListByIdZasob());
  223. $canReadRefs = array_filter($canReadRefs, function ($fieldName) use ($acl, $isRoot) {
  224. return (false !== strpos($fieldName, ":") && ($isRoot || $acl->canReadField($fieldName)));
  225. });
  226. foreach ($canReadRefs as $refField) {
  227. foreach ($nestedParts as $nestedPart) {
  228. $nestedFields[ $refField ][] = $nestedPart;
  229. }
  230. }
  231. }
  232. }
  233. if (!empty($nestedFields)) {
  234. $aclFields = array_unique(array_merge($aclFields, array_keys($nestedFields)));
  235. }
  236. if (in_array('*', $aclFields)) {
  237. $aclFields = array_filter($aclFields, function ($fieldName) { return ( '*' !== $fieldName); });
  238. $allAclFields = array_values($acl->getFieldListByIdZasob());
  239. $allAclFields = array_filter($allAclFields, function ($fieldName) use ($acl, $isRoot) {
  240. return ($isRoot || $acl->canReadField($fieldName));
  241. });
  242. if ($allAclFields) $aclFields = array_unique(array_merge($aclFields, $allAclFields));
  243. }
  244. DBG::log([$aclFields, $nestedFields, $acl->getFieldListByIdZasob()], 'array', "convertOgcPropsRecurse: splited to acl fields and nested fields ns({$dbgXpath})");
  245. foreach ($aclFields as $fieldName) {
  246. if (!$isRoot && !$acl->canReadField($fieldName)) throw new Exception("Access Denied to read field '{$fieldName}' from '" . $acl->getNamespace() . "'");
  247. }
  248. foreach ($aclFields as $fieldName) {
  249. if (false !== strpos($fieldName, ':')) {
  250. $childNs = str_replace(['__x3A__', ':'], '/', $fieldName);
  251. $schemaCache[$childNs] = SchemaFactory::loadDefaultObject('SystemObject')->getItem($childNs, [ 'propertyName' => '*,field' ]);
  252. }
  253. }
  254. $contextFieldList = $aclFields;
  255. foreach ($nestedFields as $childName => $nestedProps) {
  256. $childNs = str_replace(['__x3A__', ':'], '/', $childName);
  257. if (!array_key_exists($childNs, $schemaCache)) {
  258. $schemaCache[$childNs] = SchemaFactory::loadDefaultObject('SystemObject')->getItem($childNs, [ 'propertyName' => '*,field' ]);
  259. }
  260. $childCtxFields = array_map(function ($childCtxFld) use ($childName) {
  261. return "{$childName}/{$childCtxFld}";
  262. }, self::convertOgcPropsRecurse($schemaCache, $nestedProps, $schemaCache[$childNs], $dbgLoopNr + 1, "{$dbgXpath}/{$childName}"));
  263. DBG::log([ $contextFieldList, $childCtxFields ], 'array', "convertOgcPropsRecurse: array merge ns({$dbgXpath})");
  264. $contextFieldList = array_merge( $contextFieldList, $childCtxFields );
  265. DBG::log($contextFieldList, 'array', "convertOgcPropsRecurse: array merged ns({$dbgXpath})");
  266. }
  267. DBG::log($contextFieldList, 'array', "convertOgcPropsRecurse: return \$contextFieldList ns({$dbgXpath})");
  268. return $contextFieldList;
  269. }
  270. // TODO: add $contextAcl and context xpath to check for special perms by contextAcl
  271. /** @example:
  272. * Api_Wfs_GetFeature::printXmlFeatureRecurse($xmlWriter, $acl, $item, [
  273. * 'fields' => $searchParams['cols'],
  274. * 'tagName' => "{$wfsNs}:{$type}",
  275. * 'showAdvancedAttrs' => !$simple,
  276. * 'xsdAttributes' => array_merge(
  277. * [ 'fid' => "{$type}.{$itemKey}" ],
  278. * (!$simple)
  279. * ? [ "{$rootWfsNs}:web_link" => Request::getPathUri() . "index.php?_route=ViewTableAjax&namespace=" . $acl->getNamespace() . "#EDIT/{$itemKey}" ]
  280. * : []
  281. * )
  282. * ], $schemaCache, $printedFidLog);
  283. */
  284. public static function printXmlFeatureRecurse($xmlWriter, $acl, $item, $params = [], $schemaCache = [], $printedFidLog = []) {
  285. $showFields = V::get('fields', [], $params, 'array'); // TODO: if empty $showFields ?
  286. $tagName = V::get('tagName', '', $params);
  287. $attrs = V::get('xsdAttributes', [], $params, 'array');
  288. $showAdvancedAttrs = V::get('showAdvancedAttrs', false, $params, 'bool');
  289. $dbgFid = V::get('fid', 0, $attrs);
  290. $outputBlobFormat = V::get('outputBlobFormat', '', $params, 'word');
  291. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: print Xml Feature Recurse... '{$tagName}'" . ( $dbgFid ? " fid='{$dbgFid}'" : "" )); // TODO: DBG
  292. // DBG::log($acl, 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$acl)" . ( $dbgFid ? " fid='{$dbgFid}'" : "" ) . " \$acl");
  293. DBG::log($item, 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$item)" . ( $dbgFid ? " fid='{$dbgFid}'" : "" ) . " \$item");
  294. DBG::log($showFields, 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$item)" . ( $dbgFid ? " fid='{$dbgFid}'" : "" ) . " \$showFields");
  295. DBG::log([$attrs, $showAdvancedAttrs, array_keys($schemaCache), $printedFidLog], 'array', "DBG: print Xml Feature Recurse( ... {$tagName}, \$attrs, \$showAdvancedAttrs, keys(\$schemaCache), \$printedFidLog)");
  296. if ($dbgFid) {
  297. if (in_array($dbgFid, $printedFidLog)) {
  298. DBG::log("TODO: in_array({$dbgFid}, \$printedFidLog)");
  299. }
  300. }
  301. if ($dbgFid) $printedFidLog[] = $dbgFid;
  302. // $rootWfsNs = 'p5';
  303. list($itemPrefix, $localName) = explode(':', $tagName);
  304. if (1 === count($item) && !empty($item['xlink'])) {
  305. // @example 'xlink' => 'https://biuro.biall-net.pl/wfs/default_db/CRM_PROCES#PROCES.857'
  306. $xlink = $item['xlink'];
  307. list($xlinkUrl, $xlinkFid) = explode('#', $xlink);
  308. DBG::log($item[$fldName], 'array', "xlinks for '{$tagName}'");
  309. $xmlWriter->startElement($tagName);
  310. foreach ($attrs as $name => $value) {
  311. $xmlWriter->writeAttribute($name, $value);
  312. }
  313. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  314. $xmlWriter->writeAttribute("p5:allow_read", "false");
  315. }
  316. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  317. $xmlWriter->writeAttribute("p5:allow_write", "true");
  318. }
  319. $xmlWriter->writeAttribute('xlink:href', $xlink);
  320. $xmlWriter->writeAttribute('p5:primaryKey', substr($xlink, strrpos($xlink, '.') + 1));
  321. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  322. return;
  323. }
  324. if (1 === count($item) && !empty($item['p5:links'])) {
  325. DBG::log($item, 'array', "DBG p5:links");
  326. // array (
  327. // 'p5:links' =>
  328. // array (
  329. // 'p5:next' =>
  330. // array (
  331. // '@typeName' => 'default_db__x3A__CRM_WSKAZNIK:CRM_WSKAZNIK',
  332. // '@backRefPK' => '6125',
  333. // ),
  334. // ),
  335. // )
  336. $xmlWriter->startElement('p5:links');
  337. foreach ($item['p5:links'] as $type => $link) {
  338. if ('p5:next' === $type) {
  339. $xmlWriter->startElement('p5:next');
  340. if (empty($link['@typeName'])) throw new Exception("Missing 'p5:links/p5:next/@typeName'");
  341. if (empty($link['@backRefNS'])) throw new Exception("Missing 'p5:links/p5:next/@backRefNS'");
  342. if (empty($link['@backRefPK'])) throw new Exception("Missing 'p5:links/p5:next/@backRefPK'");
  343. if (empty($link['@startIndex'])) throw new Exception("Missing 'p5:links/p5:next/@startIndex'");
  344. if (empty($link['value'])) throw new Exception("Missing 'p5:links/p5:next'");
  345. $xmlWriter->writeAttribute("p5:typeName", $link['@typeName']);
  346. $xmlWriter->writeAttribute("p5:backRefNS", $link['@backRefNS']);
  347. $xmlWriter->writeAttribute("p5:backRefPK", $link['@backRefPK']);
  348. $xmlWriter->writeAttribute("p5:startIndex", $link['@startIndex']);
  349. $xmlWriter->text($link['value']); // str_replace('&', '&amp;', $link['value'])
  350. $xmlWriter->endElement(); // 'p5:next'
  351. } else {
  352. DBG::log("TODO: Not Implemented p5:links chldren '{$type}'");
  353. $xmlWriter->writeComment("TODO: Not implemented p5:links children '{$type}'");
  354. }
  355. }
  356. $xmlWriter->endElement(); // 'p5:links'
  357. return;
  358. }
  359. $xmlWriter->startElement($tagName);
  360. foreach ($attrs as $name => $value) {
  361. $xmlWriter->writeAttribute($name, $value);
  362. }
  363. if (!empty($item['@primaryKey'])) $xmlWriter->writeAttribute('p5:primaryKey', $item['@primaryKey']);
  364. // $fldList = $acl->getRealFieldListByIdZasob();
  365. $fldList = $acl->getFieldListByIdZasob();
  366. $geomFld = null;
  367. foreach ($fldList as $fldName) {
  368. if ($acl->isGeomField($fldName)) {
  369. $geomFld = $fldName;
  370. }
  371. }
  372. DBG::log($fldList, 'array', ">>> loop start fields(".count($fldList).")");
  373. // DBG::log($showFields, 'array', "DBG: \$showLocalFields \$showFields 0");
  374. $showLocalFields = ($showFields) // if empty filter cols show all local fields
  375. ? array_filter($showFields, function ($showFieldName) {
  376. return (false === strpos($showFieldName, "/"));
  377. })
  378. : $fldList;
  379. DBG::log($showLocalFields, 'array', "DBG: \$showLocalFields");
  380. $fldList = array_filter($fldList, function ($fieldName) use ($showLocalFields) {
  381. return (in_array($fieldName, $showLocalFields));
  382. });
  383. DBG::log($fldList, 'array', ">>> loop start filtered fields(".count($fldList).")");
  384. foreach ($fldList as $idZasob => $fldName) {
  385. DBG::log(">>> loop {$idZasob} => {$fldName}...");
  386. $fldType = $acl->getXsdFieldType($fldName);
  387. DBG::log(">>> loop '{$fldName}' xsdType: '{$fldType}'");
  388. if (!$acl->canReadObjectField($fldName, (object)$item)) if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: skip - !canReadObjectField('{$fldName}')"); // TODO: DBG
  389. if (!$acl->canReadObjectField($fldName, (object)$item)) continue;
  390. DBG::log(">>> loop '{$fldName}' can read...");
  391. if ($geomFld != null && $fldName == $geomFld) {
  392. $xmlWriter->startElement("{$itemPrefix}:{$fldName}");
  393. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  394. $xmlWriter->writeAttribute("p5:allow_read", "false");
  395. }
  396. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  397. $xmlWriter->writeAttribute("p5:allow_write", "true");
  398. }
  399. (new Api_WfsGeomTypeConverter())->createGmlFromWkt_xmlWriter($item[$fldName], $xmlWriter);
  400. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  401. } else if (is_array($item[$fldName])) {// TODO: by struct - REF field
  402. DBG::log($item[$fldName], 'array', ">>> loop({$itemKey}) REF item[{$itemKey}][{$fldName}]");
  403. if (empty($item[$fldName])) { // SKIP empty fields
  404. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: skip empty field '{$fldName}'"); // TODO: DBG
  405. // $xmlWriter->h($fldName);
  406. } else {
  407. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("DBG: TODO: array field... '{$fldName}'"); // TODO: DBG
  408. // $xmlWriter->writeComment("TODO: ".$acl->getName().".{$itemKey}/{$fldName} ...");
  409. $fieldNs = str_replace(['__x3A__', ':'], '/', $fldName); // substr($xsdType, 4));
  410. if (!array_key_exists($fieldNs, $schemaCache)) {
  411. // maybe only xlinks - acl not needed
  412. $firstItem = reset($item[$fldName]);
  413. if (1 === count($firstItem) && !empty($firstItem['xlink'])) {
  414. foreach ($item[$fldName] as $childItem) { // xlink or p5:links
  415. self::printXmlFeatureRecurse($xmlWriter, $childAcl = null, $childItem, [
  416. 'fields' => [],
  417. 'tagName' => $fldName,
  418. 'xsdAttributes' => [],
  419. 'showAdvancedAttrs' => $showAdvancedAttrs,
  420. 'outputBlobFormat' => $outputBlobFormat,
  421. ], $schemaCache, $printedFidLog);
  422. }
  423. } else {
  424. DBG::log($schemaCache, 'array', "Error Processing Request - field is not ref or missing acl ".$acl->getName().".{$itemKey}/{$fldName}");
  425. if(V::get('DBG_XML', '', $_GET))$xmlWriter->writeComment("Error Processing Request - field is not ref or missing acl ".$acl->getName().".{$itemKey}/{$fldName}");
  426. }
  427. } else {
  428. DBG::log($schemaCache[$fieldNs], 'array', "fetch child acl ".$acl->getName().".{$itemKey}/{$fldName}");
  429. $childAcl = Core_AclHelper::getAclByNamespace($schemaCache[$fieldNs]['namespace'], false, $schemaCache[$fieldNs]);
  430. $childName = $schemaCache[$fieldNs]['name'];
  431. foreach ($item[$fldName] as $childItem) {
  432. // DBG::log($showFields, 'array', "DBG: fld({$fldName}) \$showFields 0");
  433. $showChildFields = array_filter($showFields, function ($showFieldName) use ($fldName) {
  434. return ("{$fldName}/" === substr("{$showFieldName}", 0, strlen($fldName) + 1));
  435. });
  436. // DBG::log($showChildFields, 'array', "DBG: fld({$fldName}) \$showChildFields 1");
  437. $showChildFields = array_map(function ($showFieldName) use ($fldName) {
  438. return substr($showFieldName, strlen($fldName) + 1);
  439. }, $showChildFields);
  440. // DBG::log($showChildFields, 'array', "DBG: fld({$fldName}) \$showChildFields 2");
  441. $childPK = V::get($childAcl->getPrimaryKeyField(), '', $childItem);
  442. self::printXmlFeatureRecurse($xmlWriter, $childAcl, $childItem, [
  443. 'fields' => $showChildFields,
  444. 'tagName' => $fldName,
  445. 'xsdAttributes' => ($childPK) ? [ 'fid' => "{$childName}.{$childPK}" ] : [],
  446. 'showAdvancedAttrs' => $showAdvancedAttrs,
  447. 'outputBlobFormat' => $outputBlobFormat,
  448. ], $schemaCache, $printedFidLog);
  449. }
  450. // foreach ($item[$fldName] as $refItem) {
  451. // DBG::log($refItem, 'array', "\$refItem fld({$fldName})");
  452. // if (1 == count($refItem) && !empty($refItem['xlink'])) {
  453. // $xmlWriter->startElement($schemaCache[$fieldNs]['typeName']);
  454. // $xmlWriter->writeAttribute("xlink:href", $refItem['xlink']);
  455. // $xmlWriter->endElement();
  456. // } else {
  457. // $xmlWriter->writeComment("DBG: array field ref ... '{$fldName}'"); // TODO: DBG
  458. // $xmlWriter->startElement($schemaCache[$fieldNs]['typeName']);
  459. // foreach ($schemaCache[$fieldNs]['field'] as $field) {
  460. // if (array_key_exists($field['fieldNamespace'], $refItem)) {
  461. // $xmlWriter->writeComment("REF field ({$field['fieldNamespace']}) value({$refItem[$field['fieldNamespace']]}) TODO: get xsdType - TODO: recurse");
  462. // DBG::log($refItem[$field['fieldNamespace']], 'array', "REF field ({$field['fieldNamespace']}) TODO: get xsdType - TODO: recurse");
  463. // if (false !== strpos($field['fieldNamespace'], ':')) { // is ref - TODO: better check by xsdType
  464. // $xmlWriter->startElement($field['fieldNamespace']);
  465. // $xmlWriter->writeComment("TODO: recurse ...");
  466. // // $xmlWriter->text($refItem[$field['fieldNamespace']]);
  467. // $xmlWriter->endElement();
  468. // } else {
  469. // $xmlWriter->startElement("{$schemaCache[$fieldNs]['nsPrefix']}:{$field['fieldNamespace']}");
  470. // $xmlWriter->text($refItem[$field['fieldNamespace']]);
  471. // $xmlWriter->endElement();
  472. // }
  473. // }
  474. // }
  475. // $xmlWriter->endElement();
  476. // }
  477. // }
  478. }
  479. }
  480. } else if ('xsd:base64Binary' === $acl->getXsdFieldType($fldName)) {
  481. if (empty($item[$fldName])) continue; // && '0' !== $item[$fldName]
  482. $xmlWriter->startElement("{$itemPrefix}:{$fldName}");
  483. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  484. $xmlWriter->writeAttribute("p5:allow_read", "false");
  485. }
  486. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  487. $xmlWriter->writeAttribute("p5:allow_write", "true");
  488. }
  489. $pk = V::get($acl->getPrimaryKeyField(), '', $item);
  490. if ($showAdvancedAttrs) {
  491. $xmlWriter->writeAttribute("p5:blobUrl", Request::getPathUri() . "wfs-data.php?" . implode("&", [
  492. "SERVICE=WFS", "VERSION=1.0.0", "REQUEST=GetBlob",
  493. "namespace=" . $acl->getNamespace(),
  494. "primaryKey={$pk}",
  495. "fieldName={$fldName}",
  496. ]));
  497. }
  498. if (in_array($item[$fldName], ['0', '1'])) {
  499. if ('base64' === $outputBlobFormat) {
  500. $content = DB::getPDO( $acl->getDatabaseID() )->getBlob( $acl->getRootTableName(), $fldName, $acl->getPrimaryKeyField(), $pk );
  501. $xmlWriter->text(base64_encode($content));
  502. } else if ('link' === $outputBlobFormat) {
  503. $xmlWriter->text($item[$fldName]);
  504. } else { // hide
  505. throw new Exception("Not implemented outputBlobFormat - expected base64 or link");
  506. }
  507. } else {
  508. $xmlWriter->text(base64_encode($item[$fldName]));
  509. }
  510. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  511. } else {
  512. $value = str_replace('&', '&amp;', $item[$fldName]);
  513. if (empty($value) && '0' !== $value) {
  514. continue;
  515. } else {
  516. $xmlWriter->startElement("{$itemPrefix}:{$fldName}");
  517. if ($showAdvancedAttrs && !$acl->canReadObjectField($fldName, (object)$item)) {
  518. $xmlWriter->writeAttribute("p5:allow_read", "false");
  519. }
  520. if ($showAdvancedAttrs && $acl->canWriteObjectField($fldName, (object)$item)) {
  521. $xmlWriter->writeAttribute("p5:allow_write", "true");
  522. }
  523. $xmlWriter->text($value);
  524. $xmlWriter->endElement();// {$itemPrefix}:{$fldName}
  525. }
  526. }
  527. }
  528. $xmlWriter->endElement();
  529. }
  530. public static function printJsonFeatures($items, $acl, $showFields = []) {
  531. header('Content-Type: text/json; charset=UTF-8');
  532. // TODO: only fields with Read access, then filter by $showFields list if set
  533. $outputBlobFormat = V::get('outputBlobFormat', 'base64', $_GET);
  534. if ('default_db/COMPANIES_FV' === $acl->getNamespace()) {
  535. $items = array_map(function ($item) use ($outputBlobFormat) {
  536. if (1 == $item['DOKUMENT']) {
  537. $item['DOKUMENT'] = ('base64' === $outputBlobFormat)
  538. ? base64_encode( DB::getPDO()->getBlob('COMPANIES_FV', 'DOKUMENT', 'ID', $item['ID']) )
  539. : Request::getPathUri() . "wfs-data.php?SERVICE=WFS&VERSION=1.0.0&REQUEST=GetBlob&namespace=default_db/COMPANIES_FV&primaryKey={$item['ID']}&fieldName=DOKUMENT";
  540. }
  541. return $item;
  542. }, $items);
  543. }
  544. echo json_encode($items);
  545. exit;
  546. }
  547. }