فهرست منبع

import wfs from feature api

Piotr Labudda 10 سال پیش
والد
کامیت
7a640b4c61

+ 141 - 0
SE/se-lib/Api/Wfs.php

@@ -0,0 +1,141 @@
+<?php
+
+Lib::loadClass('ApiDataSourceTodo');// TODO: @see Entity/Source/Mysql from feature-schema-install
+Lib::loadClass('Api_WfsException');
+Lib::loadClass('Api_WfsServer');
+Lib::loadClass('UserAcl');
+
+class Api_Wfs {
+
+	private $_apiUser;
+	private $_dataSourceName;
+	private $_tblName;
+	private $_tblSchema;
+
+	public function setUser($user) {
+		$this->_apiUser = $user;
+	}
+
+	private function reqDBG($request, $line) {
+		$reqLog = "[" . date("Y-m-d H:m:s") . "] WFS: ---- {$_SERVER['REQUEST_METHOD']}: {$_SERVER['REQUEST_URI']}";
+		if ($_SERVER['REQUEST_METHOD'] == 'POST') $reqLog .= "\n------------ POST:\n" . file_get_contents("php://input");
+		if (!empty($request)) $reqLog .= "\n------------ request: " . json_encode($request);
+		$reqLog .= "\n------------ END.";
+		$this->DBG($reqLog, $line);
+	}
+
+	private function DBG($reqLog, $line) {
+		$errorLogFile = APP_PATH_ROOT . "/wfs.log";
+		if (!is_writable($errorLogFile)) {
+			$fp = @fopen($errorLogFile, "w");
+			if ($fp === false) {
+				return;
+			}
+			@fclose($fp);
+		}
+		error_log("L.{$line}:{$reqLog}\n", 3, $errorLogFile);
+	}
+
+	public function execute($request) {
+
+		$this->reqDBG($request, __LINE__);
+
+		/* TODO: return response xml document
+		$responseDocument = null;
+		try {
+			$responseDocument = $this->wfsServerAction($request);
+		} catch (Api_WfsException $e) {// TODO: create WfsException - http://schemas.opengis.net/wfs/1.0.0/OGC-exception.xsd
+		//} catch (Exception $e) {// TODO: create WfsException - http://schemas.opengis.net/wfs/1.0.0/OGC-exception.xsd
+			$responseDocument = $this->wfsExceptionAction($request, $e);
+		}
+		return $responseDocument;
+		*/
+
+		if (empty($request->segments)) {
+			//$this->mainWpsAction($request);// show list of posible data source
+			throw new HttpException("Bad Request", 400);
+		} else {
+			$this->_dataSourceName = array_shift($request->segments);
+			$this->DBG("dataSourceAction({$this->_dataSourceName}) ...", __LINE__);
+			try {
+				$this->dataSourceAction($request);
+			} catch (Api_WfsException $e) {
+				$responseDocument = $this->wfsExceptionAction($e);
+				header('Content-type: application/xml');
+				echo $responseDocument;
+			} catch (Exception $e) {
+				$responseDocument = $this->wfsExceptionAction($e);
+				header('Content-type: application/xml');
+				echo $responseDocument;
+			}
+			$this->DBG("dataSourceAction() END", __LINE__);
+		}
+
+		exit;
+		// return document tree - array of arrays
+	}
+
+	private function wfsServerAction($request) {
+	}
+
+	private function wfsExceptionAction($e) {
+		$dom = new DOMDocument('1.0', 'utf-8');
+		$dom->formatOutput = true;
+		$dom->preserveWhiteSpace = false;
+		$rootNode = $dom->createElementNS('http://www.opengis.net/ogc', 'ServiceExceptionReport');
+		$dom->appendChild($rootNode);
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+		$rootNode->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation', 'http://www.opengis.net/ogc http://schemas.opengis.net/wfs/1.0.0/OGC-exception.xsd');
+		$rootNode->setAttribute('version', '1.2.0');
+
+		$srvExNode = $dom->createElement('ServiceException', $e->getMessage());
+		$rootNode->appendChild($srvExNode);
+
+		return $dom->saveXML();
+	}
+
+	private function dataSourceAction($request) {
+		$document = '';
+		//$userAcl = User::getAcl();
+		IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">user (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($this->_apiUser);echo'</pre>';}
+		$userAcl = new UserAcl($this->_apiUser->getID(), $use_cache = true);
+		$userAcl->fetchGroups();
+		$userAcl->fetchAllPerms(true);
+		IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$userAcl (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($userAcl);echo'</pre>';}
+
+		$this->DBG("WfsServer(" . $this->_apiUser->getID() . ") ...", __LINE__);
+		$wfsServer = new Api_WfsServer($userAcl);
+		if ('WFS' != V::get('SERVICE', '', $request->query)) {
+			throw new Api_WfsException("Only WFS Service is allowed");
+		}
+		$req = V::get('REQUEST', '', $request->query);
+		if (!empty($req)) {
+			$methodName = "{$req}Action";
+			if (!method_exists($wfsServer, $methodName)) {
+				throw new Api_WfsException("Not Implemented " . htmlspecialchars($req), 501);
+			}
+			$this->DBG("WfsServer->{$methodName}() ...", __LINE__);
+			$document = $wfsServer->$methodName($urlQuery);
+		}
+		else {
+			$this->DBG("WfsServer->parseXMLRequest() ...", __LINE__);
+			$document = $wfsServer->parseXMLRequest();
+
+			header('Content-type: application/xml');
+			echo '<?xml version="1.0" encoding="UTF-8"?>';
+			echo $document; exit;// TODO: return $document;
+		}
+		IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">$document (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($document);echo'</pre>';}
+
+		header('Content-type: application/xml');
+		echo $document; exit;// TODO: return $document;
+
+		exit;
+		// TODO: return $document;
+	}
+
+	private function mainWpsAction($request) {
+		return array('TODO:'=>'TODO: Show main WPS GetCapabilities');
+	}
+
+}

+ 4 - 0
SE/se-lib/Api/WfsException.php

@@ -0,0 +1,4 @@
+<?php
+
+class Api_WfsException extends Exception {
+}

+ 179 - 0
SE/se-lib/Api/WfsGeomTypeConverter.php

@@ -0,0 +1,179 @@
+<?php
+
+Lib::loadClass('Api_WfsException');
+
+class Api_WfsGeomTypeConverter {
+
+	public function getType($geomType) {
+
+	}
+
+	public function convertGmlCoordinatesToWkt($gmlCoordinates) {
+		$DBG = (V::get('DBG_GML_CONV', '', $_GET) > 0);// TODO: Profiler
+		$wkt = null;
+		if($DBG){echo "\ngmlCoordinates L." . __LINE__ . ": " . str_replace(' ', "\n", $gmlCoordinates) . "\n";}
+		$gmlFormats = array();
+		$gmlFormats[] = array('cs'=>',', 'ts'=>' ');// QGIS request
+		$gmlFormats[] = array('cs'=>' ', 'ts'=>',', 'decimal'=>'.');// WKT
+
+		$wktTypesMap = array();
+		$wktTypesMap['Point'] = 'POINT';
+		$wktTypesMap['LineString'] = 'LINESTRING';
+		$wktTypesMap['Polygon'] = 'POLYGON';
+		//$wktTypesMap[''] = 'MULTIPOINT';
+		//$wktTypesMap[''] = 'MULTILINESTRING';
+		//$wktTypesMap[''] = 'MULTIPOLYGON';
+
+		/*
+			<extension base="string">
+				<attribute name="decimal" type="string" use="optional" default="."/>
+				<attribute name="cs" type="string" use="optional" default=","/>
+				<attribute name="ts" type="string" use="optional" default=" "/>
+			</extension>
+		*/
+		foreach ($gmlFormats as $i => $gmlFormat) {
+			$gmlPoint = '((\-?\d+\.?\d*)' .$gmlFormat['cs'] . '(\-?\d+\.?\d*))';
+			//$gmlPoints = $gmlPoint . '(' .$gmlFormat['ts'] . $gmlPoint . ')+';
+			$gmlPoints = $gmlPoint . '(' .$gmlFormat['ts'] . $gmlPoint . ')*';
+			$wktPolygonPattern = '/^([a-zA-Z]+)\(\(?' . $gmlPoints . '\)?\)$/';
+			if($DBG){echo 'wktPolygonPattern:';print_r($wktPolygonPattern);echo "\n";}
+			$matches = array();
+			if (preg_match($wktPolygonPattern, $gmlCoordinates, $matches)) {
+				if($DBG){echo "[{$i}]matches:";print_r($matches);echo "\n";}
+				if (count($matches) > 2) {
+					$wktType = $matches[1];
+					if($DBG){echo 'wktType:';print_r($wktType);echo "\n";}
+					if (!array_key_exists($wktType, $wktTypesMap)) {
+						throw new Api_WfsException("Gml type '{$wktType}' not supported", 501);
+					}
+					$wkt = $gmlCoordinates;
+					$wkt = str_replace(array($gmlFormat['cs'], $gmlFormat['ts']), array('#cs#', '#ts#'), $wkt);
+					$wkt = str_replace(array('#cs#', '#ts#'), array(' ', ','), $wkt);
+					$wkt = str_replace($wktType, $wktTypesMap[$wktType], $wkt);
+					if($DBG){echo 'wkt:';print_r($wkt);echo "\n";}
+				}
+				break;
+			}
+		}
+
+		if (!$wkt) {
+			throw new Api_WfsException("Gml coordinates not supported", 501);
+		}
+
+		return $wkt;
+	}
+
+	public function createGmlFromWkt($geomAsWkt, $dom) {
+		/* @see http://en.wikipedia.org/wiki/Geography_Markup_Language
+		 *
+		 * LineString:
+			<gml:LineString xmlns:gml="http://www.opengis.net/gml" srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+				<gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs=" " ts=",">
+					18.5142937987997 54.3142300873962,18.5142730427068 54.3142435923024
+				</gml:coordinates>
+			</gml:LineString>
+		 *
+		 * Polygon:
+			<gml:Polygon xmlns:gml="http://www.opengis.net/gml" srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+				<gml:outerBoundaryIs>
+					<gml:LinearRing>
+						<gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs=" " ts=",">18.4252094051732 54.377797436489,18.4150883433964 54.3729569286827,18.4425912286593 54.3696565824512,18.4379707439351 54.3780174595711,18.4252094051732 54.377797436489</gml:coordinates>
+					</gml:LinearRing>
+				</gml:outerBoundaryIs>
+			</gml:Polygon>
+		 *
+		 * Point:
+			<gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+				<gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs=" " ts=",">100,200</gml:coordinates>
+			</gml:Point>
+		 *
+		 */
+		$DBG = (V::get('DBG_GEO', '', $_GET) > 0);// TODO: Profiler
+		if (empty($geomAsWkt)) return null;
+		// WKT: http://en.wikipedia.org/wiki/Well-known_text
+if($DBG){echo 'wkt:';print_r($geomAsWkt);echo "\n";}
+		$gmlCoordinatesFromWkt = '';
+		$bbox = null;
+		$bboxPolygon = null;
+		$wktType = substr($geomAsWkt, 0, strpos($geomAsWkt, '('));
+		$sql = "select
+		--	GeometryType(GeomFromText('{$geomAsWkt}')) as type,
+			AsText(Envelope(GeomFromText('{$geomAsWkt}'))) as bbox
+		";
+		$db = DB::getDB();
+		$res = $db->query($sql);
+		if ($r = $db->fetch($res)) {
+			$bboxPolygon = $r->bbox;
+		}
+if($DBG){echo 'wktType:';print_r($wktType);echo "\n";}
+if($DBG){echo 'bboxPolygon:';print_r($bboxPolygon);echo "\n";}
+		if ($bboxPolygon) {// eg. POLYGON((18.550927583884 54.3124269117497,18.6401914998997 54.3124269117497,18.6401914998997 54.3608667397837,18.550927583884 54.3608667397837,18.550927583884 54.3124269117497))
+			$bbox = '';
+			$wktPoint = '(\d+\.?\d*) (\d+\.?\d*)';
+			$wktPolygonPattern = '/^POLYGON\(\(' . $wktPoint . ','  . $wktPoint . ',' . $wktPoint . ',' . $wktPoint . ',' . $wktPoint . '\)\)$/';
+if($DBG){echo 'wktPolygonPattern:';print_r($wktPolygonPattern);echo "\n";}
+			if (preg_match($wktPolygonPattern, $bboxPolygon, $matches)) {
+				$bbox = $matches;
+			}
+		}
+if($DBG){echo 'bbox:';print_r($bbox);echo "\n";}
+
+		if ('POLYGON' === $wktType) {
+			$gmlCoordinatesFromWkt = $geomAsWkt;
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, strlen('POLYGON(('));
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, 0, -1 * strlen('))'));
+			$gmlType = 'Polygon';
+		}
+		else if ('LINESTRING' === $wktType) {
+			$gmlCoordinatesFromWkt = $geomAsWkt;
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, strlen('LINESTRING('));
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, 0, -1 * strlen(')'));
+			$gmlType = 'LineString';
+		}
+		else if ('POINT' === $wktType) {
+			$gmlCoordinatesFromWkt = $geomAsWkt;
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, strlen('POINT('));
+			$gmlCoordinatesFromWkt = substr($gmlCoordinatesFromWkt, 0, -1 * strlen(')'));
+			$gmlCoordinatesFromWkt = str_replace(' ', ',', $gmlCoordinatesFromWkt);
+			$gmlType = 'Point';
+		}
+		else {
+			return null;
+		}
+if($DBG){echo 'gmlCoordinatesFromWkt:';print_r($gmlCoordinatesFromWkt);echo "\n";}
+
+		$geomNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:' . $gmlType);
+		$geomNode->setAttribute('srsName', "http://www.opengis.net/gml/srs/epsg.xml#4326");// TODO: EPSG
+		if ('Point' === $gmlType) {
+			$coordinatesNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:coordinates');
+			$geomNode->appendChild($coordinatesNode);
+			//$coordinatesNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+			//$coordinatesNode->setAttribute('decimal', '.');
+			//$coordinatesNode->setAttribute('cs', ' ');
+			//$coordinatesNode->setAttribute('ts', ',');
+			$coordinatesNode->nodeValue = $gmlCoordinatesFromWkt;
+		} else if ('LineString' === $gmlType) {
+			$coordinatesNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:coordinates');
+			$geomNode->appendChild($coordinatesNode);
+			$coordinatesNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+			$coordinatesNode->setAttribute('decimal', '.');
+			$coordinatesNode->setAttribute('cs', ' ');
+			$coordinatesNode->setAttribute('ts', ',');
+			$coordinatesNode->nodeValue = $gmlCoordinatesFromWkt;
+		} else {
+			$outerBoundsNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:outerBoundaryIs');
+			$geomNode->appendChild($outerBoundsNode);
+				$linearRingNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:LinearRing');
+				$outerBoundsNode->appendChild($linearRingNode);
+					$coordinatesNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:coordinates');
+					$linearRingNode->appendChild($coordinatesNode);
+					$coordinatesNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+					$coordinatesNode->setAttribute('decimal', '.');
+					$coordinatesNode->setAttribute('cs', ' ');
+					$coordinatesNode->setAttribute('ts', ',');
+					$coordinatesNode->nodeValue = $gmlCoordinatesFromWkt;
+		}
+		return $geomNode;
+	}
+
+}

+ 1527 - 0
SE/se-lib/Api/WfsServer.php

@@ -0,0 +1,1527 @@
+<?php
+
+Lib::loadClass('Api_WfsException');
+Lib::loadClass('Api_WfsGeomTypeConverter');
+
+class Api_WfsServer {
+
+	private $_usrAcl;
+	private $_typeConverter;
+
+	public function __construct($usrAcl) {
+		$this->_usrAcl = $usrAcl;
+		$this->_typeConverter = new Api_WfsGeomTypeConverter();
+	}
+
+	public function parseXMLRequest() {
+		$data = array();
+		$reqContent = file_get_contents('php://input');
+		if (empty($reqContent)) {
+			throw new Exception("Empty request");
+		}
+		$parserXml = xml_parser_create();
+		xml_parser_set_option($parserXml, XML_OPTION_CASE_FOLDING, 0);
+		xml_parser_set_option($parserXml, XML_OPTION_SKIP_WHITE, 1);
+		if (0 == xml_parse_into_struct($parserXml, $reqContent, $tags)) {
+			throw new Exception("Error parsing xml");
+		}
+		xml_parser_free($parserXml);
+		if (empty($tags)) {
+			throw new Exception("Empty structure from request");
+		}
+
+		$rootTagName = V::get('tag', '', $tags[0]);
+		if ('Transaction' == $rootTagName) {
+			return $this->_parseTransactionXmlStruct($reqContent, $tags);
+		}
+
+		throw new Api_WfsException("TODO ... L." . __LINE__, 501);
+
+		$xml = new SimpleXMLElement($reqContent);
+		$namespaces = $xml->getNameSpaces(true);
+
+		if ('Transaction' == $xml->getName()) {
+			$this->_parseTransactionXml($xml);
+		}
+		else {
+			throw new Api_WfsException("Not Implemented " . htmlspecialchars($xml->getName()), 501);
+		}
+	}
+
+	private function _parseTransactionXmlStruct($requestXml, $requestXmlTags) {
+		$DBG = (V::get('DBG_XML', '', $_GET) > 0);// TODO: Profiler
+		$rootTagName = V::get('tag', '', $requestXmlTags[0]);
+		if ('Transaction' != $rootTagName) {
+			throw new Exception("Parse Request xml error #" . __LINE__);
+		}
+
+		// 1. convert request: wfs.transaction.convert-wfs-request.xsl
+		// 2. validate converted request: wfs.transaction-converted-request.xsd
+		// 3. execute request in data source
+		// ? acl check?
+
+		$usedSourceNsList = array();
+		$sourceNsList = $this->_getSourceNsList();
+		//$sourceNsList = array();
+		//foreach ($requestXmlTags[0]['attributes'] as $attrName => $attrValue) {
+		//	if ('xmlns:p5_' == substr($attrName, 0, 9)) {
+		//		$sourceNsList[substr($attrName, 6)] = $attrValue;
+		//	}
+		//}
+		{// get used typeNames
+			$usedTypeNames = array();
+			// <Update xmlns="http://www.opengis.net/wfs" typeName="p5_default_db:TEST_PERMS">
+			foreach ($requestXmlTags as $tag) {
+				if ('Update' == $tag['tag'] && 'open' == $tag['type']) {
+					$typeName = $tag['attributes']['typeName'];
+					foreach ($sourceNsList as $nsInd => $ns) {
+						if ("p5_{$ns[0]}:{$ns[1]}" == $typeName) {
+							$usedSourceNsList[$nsInd] = $ns;
+						}
+					}
+				}
+			}
+			// TODO: check: <Transaction xmlns:p5_default_db="https://biuro.biall-net.pl/wfs/default_db/TEST_PERMS"
+			// <Insert xmlns="http://www.opengis.net/wfs">
+			//   <TEST_PERMS xmlns="https://biuro.biall-net.pl/wfs/default_db/TEST_PERMS">
+			$lastTagName = '';
+			foreach ($requestXmlTags as $tag) {
+				if ('Insert' == $lastTagName) {
+					$typeName = $tag['tag'];
+					foreach ($sourceNsList as $nsInd => $ns) {
+						if ("{$ns[1]}" == $typeName) {
+							$usedSourceNsList[$nsInd] = $ns;
+						}
+					}
+				}
+				$lastTagName = $tag['tag'];
+			}
+		}
+		if (empty($usedSourceNsList)) {
+			throw new Exception("Parse Request xml error #" . __LINE__ . ": not implemented");
+		}
+
+		$convertedTransaction = $this->_convertTransactionXml($requestXml, $usedSourceNsList);
+if($DBG){echo '$convertedTransaction:';print_r($convertedTransaction);echo "\n";}
+		if (empty($convertedTransaction)) {
+			throw new Exception("Parse Request xml error #" . __LINE__ . ": convert Transaction");
+		}
+//echo "\ntags[0]:\n" . json_encode($requestXmlTags[0]) . "\n";
+//echo "\nconvertedTransaction:\n" . $convertedTransaction . "\n";
+//echo "\nsourceNsList:\n" . json_encode($sourceNsList) . "\n";
+		if (!$this->_validateConvertedTransactionXml($convertedTransaction, $usedSourceNsList)) {
+			throw new Exception("Parse Request xml error #" . __LINE__ . ": schema validation failed");
+		}
+
+		$parserXml = xml_parser_create();
+		xml_parser_set_option($parserXml, XML_OPTION_CASE_FOLDING, 0);
+		xml_parser_set_option($parserXml, XML_OPTION_SKIP_WHITE, 1);
+		if (0 == xml_parse_into_struct($parserXml, $convertedTransaction, $tags)) {
+			throw new Exception("Parse Request xml error #" . __LINE__ . ": parse converted transaction failed");
+		}
+		xml_parser_free($parserXml);
+		if (empty($tags)) {
+			throw new Exception("Parse Request xml error #" . __LINE__ . ": parse converted transaction returns empty structure");
+		}
+
+		// [{"tag":"Transaction","type":"open","level":1,"attributes":{"version":"1.0.0","service":"WFS"}},
+		//	{"tag":"Update","type":"open","level":2,"attributes":
+		//		{"typeName":"p5_default_db_13051:TEST_PERMS","featureId":"TEST_PERMS.25"}},
+		//			{"tag":"PARENT_ID","type":"complete","level":3,"value":"0"},
+		//	{"tag":"Update","type":"close","level":2},
+		//	{"tag":"Transaction","type":"close","level":1}]
+		if($DBG){echo "\nTODO: tags L." . __LINE__ . ":\n"; print_r($tags);echo "\n";}
+		$actionTag = null;
+		$prevTagName = '';
+		$theGeomField = 'the_geom';// TODO: get the geom field name from acl
+		$itemPatchs = array();
+		foreach ($tags as $tag) {
+			switch (substr($tag['tag'], 0, 6)) {
+				case 'Transa': continue; break;// Transaction
+				case 'Update':
+				case 'Insert':
+				case 'Delete':
+				case 'Native':
+					if ('open' == $tag['type']) {
+						$actionTag = array();
+						$actionTag['tag'] = substr($tag['tag'], 0, 6);
+						$actionTag['typeName'] = $tag['attributes']['typeName'];
+						if ('Insert' == substr($tag['tag'], 0, 6)) {
+							$actionTag['typeName'] = "p5_default_db:{$actionTag['typeName']}";
+						}
+						$featureEx = explode('.', $tag['attributes']['featureId'], 2);
+						$actionTag['featureId'] = $featureEx[1];
+						if ('Update' == substr($tag['tag'], 0, 6) && empty($actionTag['featureId'])) {
+							throw new Api_WfsException("Syntax error - could not read feature id!");
+						}
+						$actionTag['itemPatch'] = array();
+					} else {
+						$itemPatchs[] = $actionTag;
+						$actionTag = null;
+					}
+					break;
+				default: {// fields
+						if (3 != $tag['level'] && 'close' == $tag['type']) {
+							$actionTag = null;
+						}
+						if (3 != $tag['level']) continue;
+						if (empty($actionTag)) continue;
+						if ('Update' == $actionTag['tag']) {
+							if ($theGeomField == $tag['tag']) {
+								$actionTag['itemPatch'][$tag['tag']] = $this->_typeConverter->convertGmlCoordinatesToWkt($tag['value']);
+							} else {
+								$actionTag['itemPatch'][$tag['tag']] = $tag['value'];
+							}
+						}
+						else if ('Insert' == $actionTag['tag']) {
+							if ($theGeomField == $tag['tag']) {
+								$actionTag['itemPatch'][$tag['tag']] = $this->_typeConverter->convertGmlCoordinatesToWkt($tag['value']);
+							} else {
+								$actionTag['itemPatch'][$tag['tag']] = $tag['value'];
+							}
+						}
+					}
+			}
+
+			if (empty($prevTagName)) $prevTagName = $tag['tag'];
+		}
+
+		if($DBG){echo "\nTODO: itemPatchs L." . __LINE__ . ":\n"; print_r($itemPatchs);echo "\n";}
+		if($DBG){echo "\nTODO: _user_id L." . __LINE__ . ":\n"; print_r($this->_usrAcl->_user_id);echo "\n";}
+		$changesList = array();
+		if (!empty($itemPatchs)) {
+			foreach ($itemPatchs as $itemPatchInfo) {
+				if($DBG){echo "itemPatchInfo['itemPatch']:\n";print_r($itemPatchInfo['itemPatch']);}
+				if (empty($itemPatchInfo['itemPatch'])) continue;
+
+				$acl = $this->getAclFromTypeName($itemPatchInfo['typeName']);
+				$itemPatch = $itemPatchInfo['itemPatch'];
+				if ('Update' == $itemPatchInfo['tag']) {
+					$itemPatch[$acl->getPrimaryKeyField()] = $itemPatchInfo['featureId'];
+				}
+				else if ('Insert' == $itemPatchInfo['tag']) {
+					if (!empty($itemPatch[$acl->getPrimaryKeyField()])) {
+						$itemPatchInfo['featureId'] = $itemPatch[$acl->getPrimaryKeyField()];
+					}
+					else {
+						//throw new Exception("TODO: Insert #" . __LINE__ . ": Create new record");
+					}
+				}
+				if($DBG){echo "TODO update itemPatch:\n";print_r($itemPatch);}
+				if ('Insert' == $itemPatchInfo['tag'] && empty($itemPatch[$acl->getPrimaryKeyField()])) {
+					$newId = $acl->addItem($itemPatch);
+					$changesList[$newId] = array('Status'=>(($newId > 0)? 'SUCCESS' : 'FAILED'), 'Message'=>"created {$newId}.");
+					if ($newId > 0) {
+						$changesList[$newId]['fid'] = $acl->getName() . '.' . $newId;
+					}
+					if($DBG){echo "created {$newId}.\n";}
+				} else {
+					$affected = $acl->updateItem($itemPatch);
+					$changesList[$itemPatchInfo['featureId']] = array('Status'=>(($affected >= 0)? 'SUCCESS' : 'FAILED'), 'Message'=>"affected {$affected}.");
+					if($DBG){echo "affected {$affected}.\n";}
+				}
+			}
+			//throw new Exception("TODO: run query #" . __LINE__ . " \nitemPatchs:\n" . json_encode($itemPatchs) . " \ntags:\n" . json_encode($tags) . "\n");
+		}
+		else {
+			throw new Exception("Parse Request xml error #" . __LINE__ . ": Nothing to change");
+		}
+
+		return $this->_transactionResponse($changesList);
+	}
+
+	private function _transactionResponse($changesList) {
+		//  <WFS_TransactionResponse>
+		//    <TransactionResult>
+		//      <Status> : SUCCESS / FAILED / PARTIAL
+		//      [<Locator]
+		//      [<Message]
+		$messageTag = '';
+		$statusTag = '';
+		$statusIsFailed = false;
+		$statusAll = null;
+		$createdFetureId = '';
+		foreach ($changesList as $featureId => $change) {
+			if ('FAILED' == $change['Status']) {
+				$statusIsFailed = true;
+			}
+			if ('SUCCESS' == $change['Status'] && !empty($change['fid'])) {
+				$createdFetureId = $change['fid'];
+			}
+			if (!empty($change['Message'])) $messageTag .= "Feature '{$featureId}' {$change['Status']}: {$change['Message']}\n";
+		}
+		$statusTag = ($statusIsFailed)? 'FAILED' : 'SUCCESS';
+		$statusTag = "<wfs:{$statusTag}/>";
+		$messageTag = '';//"<wfs:Message>{$messageTag}</wfs:Message>";
+		/* Example:
+		<?xml version="1.0" encoding="UTF-8"?>
+		<wfs:WFS_TransactionResponse version="1.0.0" xmlns:wfs="http://www.opengis.net/wfs"
+			xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+			xsi:schemaLocation="http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.0.0/WFS-transaction.xsd">
+			<wfs:InsertResult>
+				<ogc:FeatureId fid="archsites.26" />
+			</wfs:InsertResult>
+			<wfs:TransactionResult handle="Updating Signature rock label">
+				<wfs:Status>
+					<wfs:SUCCESS />
+				</wfs:Status>
+			</wfs:TransactionResult>
+		</wfs:WFS_TransactionResponse>*/
+		// TODO: build xml by DOMDocument
+		// TODO: xsi:schemaLocation="http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.0.0/WFS-transaction.xsd"
+		$wfsInsertResult = '';
+		if (!empty($createdFetureId)) {
+			$wfsInsertResult = <<<EOF
+	<wfs:InsertResult>
+		<ogc:FeatureId fid="{$createdFetureId}" xmlns:ogc="http://www.opengis.net/ogc"/>
+	</wfs:InsertResult>
+EOF;
+		}
+		$tranRes = <<<EOF
+<wfs:WFS_TransactionResponse version="1.0.0"
+		xmlns:wfs="http://www.opengis.net/wfs"
+		xmlns:ogc="http://www.opengis.net/ogc"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	{$wfsInsertResult}
+	<wfs:TransactionResult>
+		<wfs:Status>{$statusTag}</wfs:Status>
+		{$messageTag}
+	</wfs:TransactionResult>
+</wfs:WFS_TransactionResponse>
+EOF;
+		return $tranRes;
+	}
+
+	private function _convertTransactionXml($requestXmlString, $sourceNsList) {
+		$DBG = (V::get('DBG_XSL', '', $_GET) > 0);// TODO: Profiler
+if($DBG){echo 'sourceNsList:';print_r($sourceNsList);echo "\n";}
+		$updateActionsXsd = array();
+		$insertActionsXsd = array();
+		//<!-- TODO: create tag Update{X} where X is namespace index -->
+		foreach ($sourceNsList as $nsInd => $sourceNs) {
+			// <Update>
+			$theGeomField = 'the_geom';// TODO: get from fields list
+			$typeName = "p5_{$sourceNs[0]}:{$sourceNs[1]}";
+			$updateElementName = "UpdateNs{$nsInd}";
+			$geomCoordsUpdateXpath = "//wfs:Value/*/gml:outerBoundaryIs/gml:LinearRing/gml:coordinates";
+			$geomCoordsInsertXpath = "//*/gml:outerBoundaryIs/gml:LinearRing/gml:coordinates";
+			$acl = $this->getAclFromTypeName($typeName);
+			$geomType = $acl->getGeomFieldType($theGeomField);
+			if ('polygon' == $geomType) {
+				$geomCoordsUpdateXpath = "//wfs:Value/*/gml:outerBoundaryIs/gml:LinearRing/gml:coordinates";
+				$geomCoordsUpdateXpath = "((<xsl:value-of select=\"{$geomCoordsUpdateXpath}\"/>))";
+				$geomCoordsInsertXpath = "//*/gml:outerBoundaryIs/gml:LinearRing/gml:coordinates";
+				$geomCoordsInsertXpath = "((<xsl:value-of select=\"{$geomCoordsInsertXpath}\"/>))";
+			} else if ('linestring' == $geomType) {
+				$geomCoordsUpdateXpath = "//wfs:Value/*/gml:coordinates";
+				$geomCoordsUpdateXpath = "(<xsl:value-of select=\"{$geomCoordsUpdateXpath}\"/>)";
+				$geomCoordsInsertXpath = "//*/gml:coordinates";
+				$geomCoordsInsertXpath = "(<xsl:value-of select=\"{$geomCoordsInsertXpath}\"/>)";
+			} else if ('point' == $geomType) {
+				$geomCoordsUpdateXpath = "//wfs:Value/*/gml:coordinates";
+				$geomCoordsUpdateXpath = "(<xsl:value-of select=\"{$geomCoordsUpdateXpath}\"/>)";
+				$geomCoordsInsertXpath = "//*/gml:coordinates";
+				$geomCoordsInsertXpath = "(<xsl:value-of select=\"{$geomCoordsInsertXpath}\"/>)";
+			}
+			$actionXsd = <<<EOF
+					<xsl:when test="@typeName = '{$typeName}'">
+						<xsl:element name="{$updateElementName}">
+							<xsl:attribute name="typeName"><xsl:value-of select="@typeName" /></xsl:attribute>
+							<xsl:attribute name="featureId"><xsl:value-of select="ogc:Filter/ogc:FeatureId/@fid" /></xsl:attribute>
+							<xsl:for-each select="wfs:Property">
+								<xsl:element name="{wfs:Name}">
+									<xsl:choose>
+										<xsl:when test="wfs:Name = '{$theGeomField}'"><xsl:value-of select="local-name(//wfs:Value/*[1])"/>{$geomCoordsUpdateXpath}</xsl:when>
+										<xsl:otherwise><xsl:value-of select="wfs:Value"/></xsl:otherwise>
+									</xsl:choose>
+								</xsl:element>
+							</xsl:for-each>
+						</xsl:element>
+					</xsl:when>
+EOF;
+			$updateActionsXsd[] = $actionXsd;
+
+			$typeName = "{$sourceNs[1]}";//"p5_{$sourceNs[0]}:{$sourceNs[1]}";
+			$insertElementName = "InsertNs{$nsInd}";
+			$actionXsd = <<<EOF
+					<xsl:when test="local-name() = '{$typeName}'">
+						<xsl:element name="{$insertElementName}">
+							<xsl:attribute name="typeName"><xsl:value-of select="local-name()" /></xsl:attribute>
+							<xsl:for-each select="*">
+								<xsl:element name="{local-name()}">
+									<xsl:choose>
+										<xsl:when test="local-name() = '{$theGeomField}'"><xsl:value-of select="local-name(*[1])"/>{$geomCoordsInsertXpath}</xsl:when>
+										<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
+									</xsl:choose>
+								</xsl:element>
+							</xsl:for-each>
+						</xsl:element>
+					</xsl:when>
+EOF;
+			$insertActionsXsd[] = $actionXsd;
+		}
+		if (!empty($updateActionsXsd)) {
+			$updateActionsXsd = implode("\n", $updateActionsXsd);
+			$updateActionsXsd = <<<EOF
+				<xsl:choose>
+					{$updateActionsXsd}
+				</xsl:choose>
+EOF;
+		} else {
+			$updateActionsXsd = '';
+		}
+		if (!empty($insertActionsXsd)) {
+			$insertActionsXsd = implode("\n", $insertActionsXsd);
+			$insertActionsXsd = <<<EOF
+				<xsl:choose>
+					{$insertActionsXsd}
+				</xsl:choose>
+EOF;
+		} else {
+			$insertActionsXsd = '';
+		}
+
+		$convertTransactionXslString = <<<EOF
+<?xml version="1.0"?>
+<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:template match="/">
+		<xsl:for-each select="wfs:Transaction">
+			<Transaction>
+				<xsl:attribute name="version"><xsl:value-of select="@version" /></xsl:attribute>
+				<xsl:attribute name="service"><xsl:value-of select="@service" /></xsl:attribute>
+				<xsl:for-each select="wfs:Update">
+					{$updateActionsXsd}
+				</xsl:for-each>
+				<!-- TODO: Insert -->
+				<xsl:for-each select="wfs:Insert/*">
+					{$insertActionsXsd}
+				</xsl:for-each>
+				<!-- TODO: Delete -->
+				<!-- TODO: Native -->
+			</Transaction>
+		</xsl:for-each>
+	</xsl:template>
+
+</xsl:transform>
+EOF;
+if($DBG){echo '$convertTransactionXslString:' . $convertTransactionXslString . "\n";}
+		$requestXml = new DOMDocument();
+		$requestXml->loadXml($requestXmlString);
+
+		$convertTransactionXsl = new DOMDocument();
+		$convertTransactionXsl->loadXml($convertTransactionXslString);
+		$proc = new XSLTProcessor();
+		$proc->importStylesheet($convertTransactionXsl);
+		return $proc->transformToXML($requestXml);
+	}
+
+	private function _validateConvertedTransactionXml($convertedTransaction, $sourceNsList) {
+		$DBG = (V::get('DBG_XSD', '', $_GET) > 0);// TODO: Profiler
+if($DBG){echo 'sourceNsList:';print_r($sourceNsList);echo "\n";}
+
+		$dom = new DOMDocument('1.0', 'utf-8');
+		$dom->formatOutput = true;
+		$dom->preserveWhiteSpace = false;
+		$rootNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:schema');
+		$dom->appendChild($rootNode);
+		// $rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+		// $rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . $wfsNs, $wfsNsUri);
+		$rootNode->setAttribute('elementFormDefault', 'qualified');
+		// $rootNode->setAttribute('targetNamespace', $wfsNsUri);
+		$rootNode->setAttribute('version', '1.0');
+
+		{// <xsd:element name="Transaction" type="TransactionType">
+			$elNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+			$rootNode->appendChild($elNode);
+			$elNode->setAttribute('name', 'Transaction');
+			$elNode->setAttribute('type', 'TransactionType');
+			$cTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:complexType');
+			$rootNode->appendChild($cTypeNode);
+			$cTypeNode->setAttribute('name', 'TransactionType');
+				$seqNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:sequence');
+				$cTypeNode->appendChild($seqNode);
+
+					$choiceNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:choice');
+					$seqNode->appendChild($choiceNode);
+					$choiceNode->setAttribute('minOccurs', '0');
+					$choiceNode->setAttribute('maxOccurs', 'unbounded');
+
+					// <!-- <xsd:element ref="Update"/>	-->
+					foreach ($sourceNsList as $nsInd => $sourceNs) {
+						$typeName = "p5_{$sourceNs[0]}:{$sourceNs[1]}";
+						$updateElementName = "UpdateNs{$nsInd}";
+						$updateElementType = "UpdateNs{$nsInd}ElementType";
+						$updateElemNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+						$choiceNode->appendChild($updateElemNode);
+						$updateElemNode->setAttribute('name', $updateElementName);
+						$updateElemNode->setAttribute('type', $updateElementType);
+					}
+					// <!-- <xsd:element ref="Insert"/>	-->
+					foreach ($sourceNsList as $nsInd => $sourceNs) {
+						$typeName = "p5_{$sourceNs[0]}:{$sourceNs[1]}";
+						$insertElementName = "InsertNs{$nsInd}";
+						$insertElementType = "InsertNs{$nsInd}ElementType";
+						$insertElemNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+						$choiceNode->appendChild($insertElemNode);
+						$insertElemNode->setAttribute('name', $insertElementName);
+						$insertElemNode->setAttribute('type', $insertElementType);
+					}
+					// <!-- <xsd:element ref="Delete"/>	-->
+					// <!-- <xsd:element ref="Native"/>	-->
+
+				$attrNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:attribute');
+				$cTypeNode->appendChild($attrNode);
+				$attrNode->setAttribute('name', 'version');
+				$attrNode->setAttribute('type', 'xsd:string');
+				$attrNode->setAttribute('use', 'required');
+				$attrNode->setAttribute('fixed', '1.0.0');
+
+				$attrNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:attribute');
+				$cTypeNode->appendChild($attrNode);
+				$attrNode->setAttribute('name', 'service');
+				$attrNode->setAttribute('type', 'xsd:string');
+				$attrNode->setAttribute('use', 'required');
+				$attrNode->setAttribute('fixed', 'WFS');
+		}
+
+		foreach ($sourceNsList as $nsInd => $sourceNs) {
+			$transactionTypesList = array();
+			$transactionTypesList[] = 'Update';
+			$transactionTypesList[] = 'Insert';
+			foreach ($transactionTypesList as $transactionType) {
+				$typeName = "p5_{$sourceNs[0]}:{$sourceNs[1]}";
+				$acl = $this->getAclFromTypeName($typeName);
+				$updateElementName = "{$transactionType}Ns{$nsInd}";
+				$updateElementType = "{$transactionType}Ns{$nsInd}ElementType";
+
+	/*
+	<xsd:complexType name="{$updateElementType}">
+			<xsd:sequence>
+				<xsd:element name="PARENT_ID" minOccurs="0" maxOccurs="1" type="xsd:integer" />
+			</xsd:sequence>
+	*/
+				$updateTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:complexType');
+				$rootNode->appendChild($updateTypeNode);
+				$updateTypeNode->setAttribute('name', $updateElementType);
+					$seqNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:sequence');
+					$updateTypeNode->appendChild($seqNode);
+
+						$pKeyField = $acl->getPrimaryKeyField();
+						$fldList = $acl->getRealFieldList();
+						foreach ($fldList as $fldName) {
+							if ($acl->isGeomField($fldName)) continue;
+
+							$elNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+							$seqNode->appendChild($elNode);
+							$elNode->setAttribute('name', $fldName);
+							$minOccurs = 0;
+							if ($pKeyField == $fldName) {
+								$minOccurs = '1';
+							} else {
+								$minOccurs = '0';
+							}
+							$elNode->setAttribute('minOccurs', $minOccurs);
+
+							$fldType = null;
+							if ($acl->isIntegerField($fldName)) {
+								$fldType = 'xsd:integer';
+							}
+							else if ($acl->isDecimalField($fldName)) {
+								$fldType = 'xsd:decimal';
+							}
+							else if ($acl->isDateField($fldName)) {
+								$fldType = 'xsd:date';
+							}
+							else if ($acl->isDateTimeField($fldName)) {
+								$fldType = 'xsd:dateTime';
+							}
+							else if ($acl->isGeomField($fldName)) {
+								//$fldType = 'gml:GeometryPropertyType';
+								// TODO: use geom types from gml to wkt
+								// TODO: pattern wg atrybutów gml:coordinates decimal="." cs="," ts=" "
+								$patternWkt = '';// TODO: error if empty - unsupported geom type
+								$patternNum = '\-?\d+\.?\d*';
+								$patternPoint = $patternNum . ',' . $patternNum;
+								$patternPoints = '(' . $patternPoint . ')( ' . $patternPoint . ')+';
+
+								$geomType = $acl->getGeomFieldType($fldName);
+								if ('polygon' == $geomType) {
+									// [a-zA-Z]+\(\((\-?\d+\.?\d*,\-?\d+\.?\d*)( (\-?\d+\.?\d*,\-?\d+\.?\d*))+\)\)
+									$patternWkt = '[a-zA-Z]+\(\(' . $patternPoints . '\)\)';
+								} else if ('linestring' == $geomType) {
+									// [a-zA-Z]+\((\-?\d+\.?\d*,\-?\d+\.?\d*)( (\-?\d+\.?\d*,\-?\d+\.?\d*))+\)
+									$patternWkt = '[a-zA-Z]+\(' . $patternPoints . '\)';
+								} else if ('point' == $geomType) {
+									// [a-zA-Z]+\(\-?\d\.?\d*,\-?\d\.?\d*\)
+									$patternWkt = '[a-zA-Z]+\(' . $patternPoint . '\)';
+								}
+
+								$simpleTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:simpleType');
+								$restrictionNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:restriction');
+								$patternNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:pattern');
+								$restrictionNode->setAttribute('base', 'xsd:string');
+								$patternNode->setAttribute('value', $patternWkt);
+								$restrictionNode->appendChild($patternNode);
+								$simpleTypeNode->appendChild($restrictionNode);
+								$elNode->appendChild($simpleTypeNode);
+							}
+							else {
+								$fldType = 'xsd:string';
+							}
+							if ($fldType) $elNode->setAttribute('type', $fldType);
+							$elNode->setAttribute('nillable', 'true');
+							$elNode->setAttribute('minOccurs', '0');
+						}
+						foreach ($fldList as $fldName) {
+							if (!$acl->isGeomField($fldName)) continue;
+
+							$elNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+							$seqNode->appendChild($elNode);
+							$elNode->setAttribute('name', $fldName);
+							$minOccurs = 0;
+							if ($pKeyField == $fldName) {
+								$minOccurs = '1';
+							} else {
+								$minOccurs = '0';
+							}
+							$elNode->setAttribute('minOccurs', $minOccurs);
+							if ($acl->isGeomField($fldName)) {
+								//$fldType = 'gml:GeometryPropertyType';
+								// TODO: use geom types from gml to wkt
+								// TODO: pattern wg atrybutów gml:coordinates decimal="." cs="," ts=" "
+								$patternWkt = '';// TODO: error if empty - unsupported geom type
+								$patternNum = '\-?\d+\.?\d*';
+								$patternPoint = $patternNum . ',' . $patternNum;
+								$patternPoints = '(' . $patternPoint . ')( ' . $patternPoint . ')+';
+
+								$geomType = $acl->getGeomFieldType($fldName);
+								if ('polygon' == $geomType) {
+									// [a-zA-Z]+\(\((\-?\d+\.?\d*,\-?\d+\.?\d*)( (\-?\d+\.?\d*,\-?\d+\.?\d*))+\)\)
+									$patternWkt = '[a-zA-Z]+\(\(' . $patternPoints . '\)\)';
+								} else if ('linestring' == $geomType) {
+									// [a-zA-Z]+\((\-?\d+\.?\d*,\-?\d+\.?\d*)( (\-?\d+\.?\d*,\-?\d+\.?\d*))+\)
+									$patternWkt = '[a-zA-Z]+\(' . $patternPoints . '\)';
+								} else if ('point' == $geomType) {
+									// [a-zA-Z]+\(\-?\d\.?\d*,\-?\d\.?\d*\)
+									$patternWkt = '[a-zA-Z]+\(' . $patternPoint . '\)';
+								}
+
+								$simpleTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:simpleType');
+								$restrictionNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:restriction');
+								$patternNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:pattern');
+								$restrictionNode->setAttribute('base', 'xsd:string');
+								$patternNode->setAttribute('value', $patternWkt);
+								$restrictionNode->appendChild($patternNode);
+								$simpleTypeNode->appendChild($restrictionNode);
+								$elNode->appendChild($simpleTypeNode);
+							}
+							$elNode->setAttribute('nillable', 'true');
+							$elNode->setAttribute('minOccurs', '0');
+						}
+
+				$attrNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:attribute');
+				$updateTypeNode->appendChild($attrNode);
+				$attrNode->setAttribute('name', 'typeName');
+				$attrNode->setAttribute('type', 'xsd:token');
+				$attrNode->setAttribute('use', 'required');
+
+				if ($transactionType == 'Update') {
+					$attrNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:attribute');
+					$updateTypeNode->appendChild($attrNode);
+					$attrNode->setAttribute('name', 'featureId');
+					$attrNode->setAttribute('use', 'required');
+						$sTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:simpleType');
+						$attrNode->appendChild($sTypeNode);
+							$resNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:restriction');
+							$sTypeNode->appendChild($resNode);
+							$resNode->setAttribute('base', 'xsd:string');
+								$patternNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:pattern');
+								$resNode->appendChild($patternNode);
+								$patternNode->setAttribute('value', '[a-zA-Z_]*\.[0-9]*');
+				}
+			}
+		}
+
+		$validateConvertedTransactionXsdString = $dom->saveXml();
+if($DBG){echo '$validateConvertedTransactionXsdString:';print_r($validateConvertedTransactionXsdString);echo "\n";}
+		$reqXml = new DOMDocument();
+		$reqXml->loadXml($convertedTransaction);
+		// TODO: fetch PHP Warning: DOMDocument::schemaValidateSource(): Element 'PARENT_ID': 'abc' is not a valid value of the atomic type 'xs:integer'.
+		return $reqXml->schemaValidateSource($validateConvertedTransactionXsdString);
+	}
+
+	private function _generateXsdTypeNode($ns, $typeName, $dom, $parentNode) {
+		//$fieldsXsd = '';
+		//$fieldsXsd .= '<xsd:element name="PARENT_ID" minOccurs="0" maxOccurs="1" type="xsd:integer" />';
+		$acl = $this->getAclFromTypeName($typeName);
+
+		$cTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:complexType');
+		$rootNode = $cTypeNode;
+		$cTypeNode->setAttribute('name', $typeName);
+
+		$cConNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:complexContent');
+		$cTypeNode->appendChild($cConNode);
+
+		$extNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:extension');
+		$cConNode->appendChild($extNode);
+		$extNode->setAttribute('base', 'gml:AbstractFeatureType');
+
+		$seqNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:sequence');// or xsd:all ?
+		$extNode->appendChild($seqNode);
+
+		// <xsd:element maxOccurs="1" minOccurs="0" name="{$fldName}" nillable="true" type="xsd:integer"/>
+		$pKeyField = $acl->getPrimaryKeyField();
+		$fldList = $acl->getRealFieldList();
+		foreach ($fldList as $fldName) {
+			$elNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+			$seqNode->appendChild($elNode);
+			$elNode->setAttribute('name', $fldName);
+			$minOccurs = 0;
+			if ($pKeyField == $fldName) {
+				$minOccurs = '1';
+			} else {
+				$minOccurs = '0';
+			}
+			$elNode->setAttribute('minOccurs', $minOccurs);
+			if ($acl->isIntegerField($fldName)) {
+				$fldType = 'xsd:integer';
+			}
+			else if ($acl->isDecimalField($fldName)) {
+				$fldType = 'xsd:decimal';
+			}
+			else if ($acl->isDateField($fldName)) {
+				$fldType = 'xsd:date';
+			}
+			else if ($acl->isDateTimeField($fldName)) {
+				$fldType = 'xsd:dateTime';
+			}
+			else if ($acl->isGeomField($fldName)) {
+				$fldType = 'gml:GeometryPropertyType';
+				//$fldType = 'gml:Polygon';// nie działa musi być gml:GeometryPropertyType
+			}
+			else {
+				$fldType = 'xsd:string';
+			}
+			$elNode->setAttribute('type', $fldType);
+			$elNode->setAttribute('nillable', 'true');
+		}
+
+/*
+			<xsd:attribute name="typeName" type="xsd:token" use="required"/>
+			<xsd:attribute name="featureId" use="required">
+				<xsd:simpleType>
+					<xsd:restriction base="xsd:string">
+						<xsd:pattern value="[a-zA-Z_]*\.[0-9]*"/>
+					</xsd:restriction>
+				</xsd:simpleType>
+			</xsd:attribute>
+*/
+		$cAttrNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:attribute');
+		$rootNode->appendChild($cAttrNode);
+		$cAttrNode->setAttribute('name', 'typeName');
+		$cAttrNode->setAttribute('type', 'xsd:token');
+		$cAttrNode->setAttribute('use', 'required');
+
+		$cAttrNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:attribute');
+		$rootNode->appendChild($cAttrNode);
+		$cAttrNode->setAttribute('name', 'featureId');
+		$cAttrNode->setAttribute('use', 'required');
+			$sTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:simpleType');
+			$cAttrNode->appendChild($sTypeNode);
+				$restrNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:restriction');
+				$cAttrNode->appendChild($restrNode);
+				$restrNode->setAttribute('base', 'xsd:string');
+					$patternNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:pattern');
+					$restrNode->appendChild($patternNode);
+					$patternNode->setAttribute('value', '[a-zA-Z_]*\.[0-9]*');
+
+		return $dom->saveXml($cTypeNode);
+
+		$typeXsd = <<<EOF
+		<xsd:complexType name="{$typeName}">
+			<xsd:all>
+				{$fieldsXsd}
+			</xsd:all>
+			<xsd:attribute name="typeName" type="xsd:token" use="required"/>
+			<xsd:attribute name="featureId" use="required">
+				<xsd:simpleType>
+					<xsd:restriction base="xsd:string">
+						<xsd:pattern value="[a-zA-Z_]*\.[0-9]*"/>
+					</xsd:restriction>
+				</xsd:simpleType>
+			</xsd:attribute>
+		</xsd:complexType>
+EOF;
+		return $typeXsd;
+	}
+
+	/**
+	 * @param string $typeName - 'p5_default_db:TEST_PERMS'
+	*/
+	public function getAclFromTypeName($typeName) {
+		$typeEx = explode(':', $typeName);
+		if (2 != count($typeEx)) {
+			throw new Api_WfsException("Could not get acl for '{$typeName}' - syntax error");
+		}
+		if ('p5_' != substr($typeEx[0], 0, 3)) {
+			throw new Api_WfsException("Could not get acl for '{$typeName}' - prefix error");
+		}
+		$sourceName = substr($typeEx[0], 3);
+		$objName = $typeEx[1];
+		$acl = $this->_usrAcl->getObjectAcl($sourceName, $objName);
+		if (!$acl) {
+			throw new Api_WfsException("Could not get acl for '{$typeName}'");
+		}
+		$forceTblAclInit = 0;//('1' == V::get('_force', '', $_GET));
+		$acl->init($forceTblAclInit);
+		return $acl;
+	}
+
+	public function getFeatureAction() {
+		$type = V::get('TYPENAME', '', $_REQUEST);
+		$typeEx = explode(':', $type);
+		$maxFeatures = V::get('MAXFEATURES', '10000', $_REQUEST, 'int');// TODO: Set Deafult Limit
+		$srsname = V::get('SRSNAME', '', $_REQUEST);// eg. EPSG:4326
+		if (count($typeEx) == 2) {
+			return $this->getFeatures($typeEx[0], $typeEx[1], $maxFeatures, $srsname);
+		} else {
+			throw new HttpException("Wrong param TYPENAME", 400);
+		}
+	}
+
+	public function getFeatures($nsPrefix, $type, $maxFeatures, $srsname) {
+		$DBG = (V::get('DBG_GEO', '', $_GET) > 0);// TODO: Profiler
+		$typeName = "{$nsPrefix}:{$type}";
+		$acl = $this->getAclFromTypeName($typeName);
+		$fldList = $acl->getRealFieldList();
+
+		$wfsNs = 'p5_default_db_' . $type;//$nsPrefix;
+		$wfsNsUri = 'https://biuro.biall-net.pl/wfs/' . substr($nsPrefix, 3) . '/' . $type;
+
+		// get BBox from geom_field (only one geom fld is allowed)
+		$geomFld = null;
+		{
+			foreach ($fldList as $fldName) {
+				if ($acl->isGeomField($fldName)) {
+					$geomFld = $fldName;
+				}
+			}
+		}
+
+		$dbGeomType = $acl->getGeomFieldType($geomFld);
+
+		$searchParams = array();
+		$searchParams['limit'] = $maxFeatures;
+		$searchParams['order_by'] = $acl->getPrimaryKeyField();
+		$searchParams['order_dir'] = 'DESC';
+		if ($geomFld) $searchParams["f_{$geomFld}"] = 'IS NOT NULL';
+		if ($geomFld) $searchParams["f_{$geomFld}"] = 'GeometryType=' . strtoupper($dbGeomType);
+		//if ($geomFld) $searchParams["f_{$geomFld}"] = 'GeometryType=LINESTRING';
+if($DBG){echo 'getItems:';print_r($searchParams);echo "\n";}
+		$items = $acl->getItems($searchParams);
+
+		$dom = new DOMDocument('1.0', 'utf-8');
+		$dom->formatOutput = true;
+		$dom->preserveWhiteSpace = false;
+		$rootNode = $dom->createElementNS('http://www.opengis.net/wfs', 'wfs:FeatureCollection');
+		$dom->appendChild($rootNode);
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', 'http://www.opengis.net/wfs');
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:wfs', 'http://www.opengis.net/wfs');
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . $wfsNs, $wfsNsUri);
+		$rootNode->setAttribute('xsi:schemaLocation', 'http://www.opengis.net/wfs');// TODO: add DescribeFeatureType xsd uri
+
+if(0){// TODO: get BBOX for add features
+			$boundedByNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:boundedBy');
+			$rootNode->appendChild($boundedByNode);
+				$boxNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:Box');
+				$boundedByNode->appendChild($boxNode);
+				$boxNode->setAttribute('srsName', "http://www.opengis.net/gml/srs/epsg.xml#4326");// TODO: EPSG
+					$coordinatesNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:coordinates');
+					$boxNode->appendChild($coordinatesNode);
+					$coordinatesNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+					$coordinatesNode->setAttribute('decimal', '.');
+					$coordinatesNode->setAttribute('cs', ',');
+					$coordinatesNode->setAttribute('ts', ' ');
+					$coordinatesNode->nodeValue = '1544947.6295,4322758.105 1548002.2259,4330464.1001';// TODO: coordinates for all items?
+}
+
+if($DBG){echo '(geomFld: '.$geomFld.'):';print_r($acl->getFieldType($geomFld));echo "\n";}
+		if (empty($items)) {
+			$pKeyField = $acl->getPrimaryKeyField();
+			$fakeItem = new stdClass();
+			$fakeItem->{$pKeyField} = 0;
+			if ('polygon' == $dbGeomType) {
+				$fakeItem->the_geom = "POLYGON(())";
+			} else if ('linestring' == $dbGeomType) {
+				$fakeItem->the_geom = "LINESTRING()";
+			} else if ('point' == $dbGeomType) {
+				$fakeItem->the_geom = "POINT(0,0)";
+			}
+			$items[0] = $fakeItem;
+		}
+		foreach ($items as $itemKey => $item) {
+			//if($item->ID == 19)continue;
+
+if($DBG){echo 'item['.$itemKey.'] ('.$geomFld.')isEmpty('.empty($item->{$geomFld}).'):';print_r($item->{$geomFld});echo "\n";}
+			if ($geomFld) {
+				if (empty($item->{$geomFld})) {
+					continue;// QGIS crash when WFS contain features with empty geom field
+				}
+			}
+
+			$featureMemberNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:featureMember');
+			$rootNode->appendChild($featureMemberNode);
+				$featureNode = $dom->createElementNS($wfsNsUri, "{$wfsNs}:{$type}");
+				$featureMemberNode->appendChild($featureNode);
+				$featureNode->setAttribute('fid', "{$type}.{$itemKey}");
+if(0){// TODO: get BBOX
+					$boundedByNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:boundedBy');
+					$featureNode->appendChild($boundedByNode);
+						$boxNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:Box');
+						$boundedByNode->appendChild($boxNode);
+						$boxNode->setAttribute('srsName', "http://www.opengis.net/gml/srs/epsg.xml#4326");// TODO: EPSG
+							$coordinatesNode = $dom->createElementNS('http://www.opengis.net/gml', 'gml:coordinates');
+							$boxNode->appendChild($coordinatesNode);
+							$coordinatesNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+							$coordinatesNode->setAttribute('decimal', '.');
+							$coordinatesNode->setAttribute('cs', ',');
+							$coordinatesNode->setAttribute('ts', ' ');
+							$coordinatesNode->nodeValue = '1546472.2363,4328949.5775 1548002.2259,4330464.1001';// TODO: coordinates for item?
+}
+					foreach ($fldList as $fldName) {
+						$featureFldNode = $dom->createElementNS($wfsNsUri, "{$wfsNs}:{$fldName}");
+						if ($acl->isGeomField($fldName)) {
+							$geomNode = $this->_typeConverter->createGmlFromWkt($item->{$fldName}, $dom);
+							if (!$geomNode) continue;
+							$featureFldNode->appendChild($geomNode);
+						} else {
+							$featureFldNode->nodeValue = str_replace('&', '&amp;', $item->{$fldName});
+							if (empty($featureFldNode->nodeValue)) {
+								continue;
+							}
+						}
+						$featureNode->appendChild($featureFldNode);
+					}
+		}
+
+		return $dom->saveXml();
+	}
+
+	public function describeFeatureTypeAction() {
+		$type = V::get('TYPENAME', '', $_REQUEST);
+		if (empty($type)) {
+			throw new HttpException("Wrong param TYPENAME", 400);
+		}
+		$typeEx = explode(':', $type);
+		if (count($typeEx) != 2) {
+			throw new HttpException("Wrong param TYPENAME", 400);
+		}
+		return $this->getDescribeFeatureType($typeEx[0], $typeEx[1]);
+	}
+
+	private function getDescribeFeatureType($nsPrefix, $type) {
+		$typeName = "{$nsPrefix}:{$type}";
+		$acl = $this->getAclFromTypeName($typeName);
+
+		$wfsNs = 'p5_default_db_' . $type;//$nsPrefix;
+		$wfsNsUri = 'https://biuro.biall-net.pl/wfs/' . substr($nsPrefix, 3) . '/' . $type;
+
+		if (empty($type)) {
+			throw new HttpException("Feature Type Name not defined", 400);
+		}
+		if (!$this->isAllowedFeatureType($nsPrefix, $type)) {
+			throw new Api_WfsException("Could not find type: " . htmlspecialchars($type));
+		}
+
+		$typeName = $type . 'Type';
+		$fldList = $acl->getRealFieldList();
+		header('Content-type: application/xml');
+		// <xsd:import namespace="http://www.opengis.net/gml" schemaLocation="http://webgis.regione.sardegna.it:80/geoserver/schemas/gml/2.1.2/feature.xsd"/>
+		//	<xsd:element name="{type}" substitutionGroup="gml:_Feature" type="dbu:{typeName}"/>
+
+		$dom = new DOMDocument('1.0', 'utf-8');
+		$dom->formatOutput = true;
+		$dom->preserveWhiteSpace = false;
+		$rootNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:schema');
+		$dom->appendChild($rootNode);
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:gml', 'http://www.opengis.net/gml');
+		$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . $wfsNs, $wfsNsUri);
+		$rootNode->setAttribute('elementFormDefault', 'qualified');
+		$rootNode->setAttribute('targetNamespace', $wfsNsUri);
+
+		$cTypeNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:complexType');
+		$rootNode->appendChild($cTypeNode);
+		$cTypeNode->setAttribute('name', $typeName);
+
+		$cConNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:complexContent');
+		$cTypeNode->appendChild($cConNode);
+
+		$extNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:extension');
+		$cConNode->appendChild($extNode);
+		$extNode->setAttribute('base', 'gml:AbstractFeatureType');
+
+		$seqNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:sequence');
+		$extNode->appendChild($seqNode);
+
+		// <xsd:element maxOccurs="1" minOccurs="0" name="{$fldName}" nillable="true" type="xsd:integer"/>
+		$pKeyField = $acl->getPrimaryKeyField();
+		foreach ($fldList as $fldName) {
+			$elNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+			$seqNode->appendChild($elNode);
+			$elNode->setAttribute('name', $fldName);
+			$minOccurs = 0;
+			if ($pKeyField == $fldName) {
+				$minOccurs = '1';
+			} else {
+				$minOccurs = '0';
+			}
+			$elNode->setAttribute('minOccurs', $minOccurs);
+			if ($acl->isIntegerField($fldName)) {
+				$fldType = 'xsd:integer';
+			}
+			else if ($acl->isDecimalField($fldName)) {
+				$fldType = 'xsd:decimal';
+			}
+			else if ($acl->isDateField($fldName)) {
+				$fldType = 'xsd:date';
+			}
+			else if ($acl->isDateTimeField($fldName)) {
+				$fldType = 'xsd:dateTime';
+			}
+			else if ($acl->isGeomField($fldName)) {
+				$fldType = 'gml:GeometryPropertyType';
+				//$fldType = 'gml:Polygon';// nie działa musi być gml:GeometryPropertyType
+			}
+			else {
+				$fldType = 'xsd:string';
+			}
+			$elNode->setAttribute('type', $fldType);
+			$elNode->setAttribute('nillable', 'true');
+		}
+
+		$elNode = $dom->createElementNS('http://www.w3.org/2001/XMLSchema', 'xsd:element');
+		$rootNode->appendChild($elNode);
+		$elNode->setAttribute('name', $type);
+		$elNode->setAttribute('type', $wfsNs . ':' . $typeName);
+
+		echo $dom->saveXML();
+		//print_r($acl);
+	}
+
+	public function isAllowedFeatureType($nsPrefix, $type) {
+		if ('p5_' != substr($nsPrefix, 0, 3)) return false;
+		if ('p5_default_db' == $nsPrefix) {
+			$typeName = "p5_default_db:{$type}";
+			try {
+				$acl = $this->getAclFromTypeName($typeName);
+			} catch (Exception $e) {
+				return false;
+			}
+			if ($acl) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public function getCapabilitiesAction() {
+		$wfsServerUrl = 'https://biuro.biall-net.pl/dev-pl/se-feature-api/wfs.php/xml/wfs/default_db';
+		$serviceTitle = "BIALL-NET Web Feature Service";
+		$serviceDescription = "This is the reference implementation of WFS 1.0.0 and WFS 1.1.0, supports all WFS operations including Transaction.";
+
+		//header('Content-type: application/xml; charset="utf-8"');
+		header('Content-type: application/xml');
+		echo '<?xml version="1.0" encoding="UTF-8"?>';
+		?>
+<WFS_Capabilities version="1.0.0"
+				  xmlns="http://www.opengis.net/wfs"
+				  xmlns:ogc="http://www.opengis.net/ogc"
+				  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+				  <?php echo $this->_getXmlNamespaceList(); ?>
+				  xsi:schemaLocation="http://www.opengis.net/wfs <?php echo $this->_getSchemaLocationString(); ?>">
+  <Service>
+    <Name>WFS</Name>
+    <Title><?php echo $serviceTitle; ?></Title>
+    <Abstract><?php echo $serviceDescription; ?></Abstract>
+    <Keywords>WFS, WMS</Keywords>
+    <OnlineResource><?php echo $wfsServerUrl; ?></OnlineResource>
+    <Fees>NONE</Fees>
+    <AccessConstraints>NONE</AccessConstraints>
+  </Service>
+  <Capability>
+    <Request>
+		<?php $this->_printGetCapabilitiesXml($wfsServerUrl); ?>
+		<?php $this->_printDescribeFeatureTypeXml($wfsServerUrl); ?>
+		<?php $this->_printGetFeatureXml($wfsServerUrl); ?>
+		<?php $this->_printTransactionXml($wfsServerUrl); ?>
+		<?php $this->_printLockFeatureXml($wfsServerUrl); ?>
+		<?php $this->_printGetFeatureWithLockXml($wfsServerUrl); ?>
+    </Request>
+  </Capability>
+  <FeatureTypeList>
+    <Operations>
+      <Query />
+      <Insert />
+      <Update />
+      <Delete />
+      <Lock />
+    </Operations>
+	<?php echo $this->_printFeatureTypeListXml(); ?>
+  </FeatureTypeList>
+  <ogc:Filter_Capabilities>
+    <ogc:Spatial_Capabilities>
+      <ogc:Spatial_Operators>
+        <ogc:Disjoint />
+        <ogc:Equals />
+        <ogc:DWithin />
+        <ogc:Beyond />
+        <ogc:Intersect />
+        <ogc:Touches />
+        <ogc:Crosses />
+        <ogc:Within />
+        <ogc:Contains />
+        <ogc:Overlaps />
+        <ogc:BBOX />
+      </ogc:Spatial_Operators>
+    </ogc:Spatial_Capabilities>
+    <ogc:Scalar_Capabilities>
+      <ogc:Logical_Operators />
+      <ogc:Comparison_Operators>
+        <ogc:Simple_Comparisons />
+        <ogc:Between />
+        <ogc:Like />
+        <ogc:NullCheck />
+      </ogc:Comparison_Operators>
+      <ogc:Arithmetic_Operators>
+        <ogc:Simple_Arithmetic />
+        <ogc:Functions>
+          <ogc:Function_Names>
+            <ogc:Function_Name nArgs="1">abs</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">abs_2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">abs_3</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">abs_4</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">acos</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">AddCoverages</ogc:Function_Name>
+            <ogc:Function_Name nArgs="4">Aggregate</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Area</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">area2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">AreaGrid</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">asin</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">atan</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">atan2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="14">BarnesSurface</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">between</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">boundary</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">boundaryDimension</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Bounds</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">buffer</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">BufferFeatureCollection</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">bufferWithSegments</ogc:Function_Name>
+            <ogc:Function_Name nArgs="7">Categorize</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">ceil</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Centroid</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">classify</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">Clip</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">CollectGeometries</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Collection_Average</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Collection_Bounds</ogc:Function_Name>
+            <ogc:Function_Name nArgs="0">Collection_Count</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Collection_Max</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Collection_Median</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Collection_Min</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Collection_Sum</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Collection_Unique</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Concatenate</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">contains</ogc:Function_Name>
+            <ogc:Function_Name nArgs="7">Contour</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">convert</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">convexHull</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">cos</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">Count</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">CropCoverage</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">crosses</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">dateFormat</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">dateParse</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">difference</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">dimension</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">disjoint</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">disjoint3D</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">distance</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">distance3D</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">double2bool</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">endAngle</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">endPoint</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">env</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">envelope</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">EqualInterval</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">equalsExact</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">equalsExactTolerance</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">equalTo</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">exp</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">exteriorRing</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">Feature</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">floor</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">geometryType</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">geomFromWKT</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">geomLength</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">getGeometryN</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">getX</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">getY</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">getz</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">greaterEqualThan</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">greaterThan</ogc:Function_Name>
+            <ogc:Function_Name nArgs="5">Grid</ogc:Function_Name>
+            <ogc:Function_Name nArgs="7">Heatmap</ogc:Function_Name>
+            <ogc:Function_Name nArgs="0">id</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">IEEEremainder</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">if_then_else</ogc:Function_Name>
+            <ogc:Function_Name nArgs="11">in10</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">in2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="4">in3</ogc:Function_Name>
+            <ogc:Function_Name nArgs="5">in4</ogc:Function_Name>
+            <ogc:Function_Name nArgs="6">in5</ogc:Function_Name>
+            <ogc:Function_Name nArgs="7">in6</ogc:Function_Name>
+            <ogc:Function_Name nArgs="8">in7</ogc:Function_Name>
+            <ogc:Function_Name nArgs="9">in8</ogc:Function_Name>
+            <ogc:Function_Name nArgs="10">in9</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">InclusionFeatureCollection</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">int2bbool</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">int2ddouble</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">interiorPoint</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">interiorRingN</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">Interpolate</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">intersection</ogc:Function_Name>
+            <ogc:Function_Name nArgs="7">IntersectionFeatureCollection</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">intersects</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">intersects3D</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">isClosed</ogc:Function_Name>
+            <ogc:Function_Name nArgs="0">isCoverage</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">isEmpty</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">isLike</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">isNull</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">isometric</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">isRing</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">isSimple</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">isValid</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">isWithinDistance</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">isWithinDistance3D</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">Jenks</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">length</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">lessEqualThan</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">lessThan</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">list</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">log</ogc:Function_Name>
+            <ogc:Function_Name nArgs="4">LRSGeocode</ogc:Function_Name>
+            <ogc:Function_Name nArgs="5">LRSMeasure</ogc:Function_Name>
+            <ogc:Function_Name nArgs="5">LRSSegment</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">max</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">max_2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">max_3</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">max_4</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">min</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">min_2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">min_3</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">min_4</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">mincircle</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">minimumdiameter</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">minrectangle</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">modulo</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">MultiplyCoverages</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">Nearest</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">not</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">notEqualTo</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">numberFormat</ogc:Function_Name>
+            <ogc:Function_Name nArgs="5">numberFormat2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">numGeometries</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">numInteriorRing</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">numPoints</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">octagonalenvelope</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">offset</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">overlaps</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">parameter</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">parseBoolean</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">parseDouble</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">parseInt</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">parseLong</ogc:Function_Name>
+            <ogc:Function_Name nArgs="0">pi</ogc:Function_Name>
+            <ogc:Function_Name nArgs="4">PointBuffers</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">pointN</ogc:Function_Name>
+            <ogc:Function_Name nArgs="7">PointStacker</ogc:Function_Name>
+            <ogc:Function_Name nArgs="6">PolygonExtraction</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">pow</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">property</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">PropertyExists</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">Quantile</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">Query</ogc:Function_Name>
+            <ogc:Function_Name nArgs="0">random</ogc:Function_Name>
+            <ogc:Function_Name nArgs="5">RangeLookup</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">RasterAsPointCollection</ogc:Function_Name>
+            <ogc:Function_Name nArgs="4">RasterZonalStatistics</ogc:Function_Name>
+            <ogc:Function_Name nArgs="5">Recode</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">RectangularClip</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">relate</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">relatePattern</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">Reproject</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">rint</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">round</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">round_2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">roundDouble</ogc:Function_Name>
+            <ogc:Function_Name nArgs="6">ScaleCoverage</ogc:Function_Name>
+            <ogc:Function_Name nArgs="4">sdo_nn</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">setCRS</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">Simplify</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">sin</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">Snap</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">sqrt</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">StandardDeviation</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">startAngle</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">startPoint</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">strCapitalize</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strConcat</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strEndsWith</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strEqualsIgnoreCase</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strIndexOf</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strLastIndexOf</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">strLength</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strMatches</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">strPosition</ogc:Function_Name>
+            <ogc:Function_Name nArgs="4">strReplace</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strStartsWith</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">strSubstring</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">strSubstringStart</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">strToLowerCase</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">strToUpperCase</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">strTrim</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">strTrim2</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">StyleCoverage</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">symDifference</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">tan</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">toDegrees</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">toRadians</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">touches</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">toWKT</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">Transform</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">union</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">UnionFeatureCollection</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">Unique</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">UniqueInterval</ogc:Function_Name>
+            <ogc:Function_Name nArgs="6">VectorToRaster</ogc:Function_Name>
+            <ogc:Function_Name nArgs="3">VectorZonalStatistics</ogc:Function_Name>
+            <ogc:Function_Name nArgs="1">vertices</ogc:Function_Name>
+            <ogc:Function_Name nArgs="2">within</ogc:Function_Name>
+          </ogc:Function_Names>
+        </ogc:Functions>
+      </ogc:Arithmetic_Operators>
+    </ogc:Scalar_Capabilities>
+  </ogc:Filter_Capabilities>
+</WFS_Capabilities>
+		<?php
+	}
+
+	private function _getSchemaLocationString() {
+		$schemaLocations = array();
+		// $schemaLocations[] = 'http://webgis.regione.sardegna.it:80/geoserver/schemas/wfs/1.0.0/WFS-capabilities.xsd';// @from http://webgis.regione.sardegna.it/geoserver/ows?service=WFS&request=GetCapabilities
+		return implode(' ', $schemaLocations);
+	}
+	private function _getXmlNamespaceList() {
+		$namespaceList = $this->_getSourceNsList();
+		$namespaceListOut = array();
+		foreach ($namespaceList as $nsObj) {
+			$ns = "p5_{$nsObj[0]}_{$nsObj[1]}";
+			$uri = "https://biuro.biall-net.pl/wfs/{$nsObj[0]}/{$nsObj[1]}";
+			$namespaceListOut[] = 'xmlns:' . $ns . '="' . $uri . '"';
+		}
+		return implode(' ', $namespaceListOut);
+	}
+	private function _getSourceNsList() {
+		$usrObjList = array();
+		$tblsAcl = $this->_usrAcl->getTablesAcl();
+		foreach ($tblsAcl as $tblAcl) {
+			$dataSourceName = 'default_db';// TODO: getSourceName
+			$tblName = $tblAcl->getName();
+			$usrObjList[] = array($dataSourceName, $tblName);
+		}
+		return $usrObjList;
+	}
+	private function _printGetCapabilitiesXml($wfsServerUrl) {
+		?>
+			<GetCapabilities>
+				<DCPType>
+					<HTTP>
+						<Get onlineResource="<?php echo $wfsServerUrl; ?>?REQUEST=GetCapabilities" />
+					</HTTP>
+				</DCPType>
+				<DCPType>
+					<HTTP>
+						<Post onlineResource="<?php echo $wfsServerUrl; ?>" />
+					</HTTP>
+				</DCPType>
+			</GetCapabilities>
+		<?php
+	}
+	private function _printDescribeFeatureTypeXml($wfsServerUrl) {
+		?>
+			<DescribeFeatureType>
+				<SchemaDescriptionLanguage>
+					<XMLSCHEMA />
+				</SchemaDescriptionLanguage>
+				<DCPType>
+					<HTTP>
+						<Get onlineResource="<?php echo $wfsServerUrl; ?>?REQUEST=DescribeFeatureType" />
+					</HTTP>
+				</DCPType>
+				<DCPType>
+					<HTTP>
+						<Post onlineResource="<?php echo $wfsServerUrl; ?>" />
+					</HTTP>
+				</DCPType>
+			</DescribeFeatureType>
+		<?php
+	}
+	private function _printGetFeatureXml($wfsServerUrl) {
+		?>
+			<GetFeature>
+				<ResultFormat>
+					<WFSKMLOutputFormat />
+					<GML2 />
+					<GML3 />
+					<SHAPE-ZIP />
+					<CSV />
+					<JSON />
+				</ResultFormat>
+				<DCPType>
+					<HTTP>
+						<Get onlineResource="<?php echo $wfsServerUrl; ?>?REQUEST=GetFeature" />
+					</HTTP>
+				</DCPType>
+				<DCPType>
+					<HTTP>
+						<Post onlineResource="<?php echo $wfsServerUrl; ?>" />
+					</HTTP>
+				</DCPType>
+			</GetFeature>
+		<?php
+	}
+	private function _printTransactionXml($wfsServerUrl) {
+		?>
+			<Transaction>
+				<DCPType>
+					<HTTP>
+						<Get onlineResource="<?php echo $wfsServerUrl; ?>?request=Transaction" />
+					</HTTP>
+				</DCPType>
+				<DCPType>
+					<HTTP>
+						<Post onlineResource="<?php echo $wfsServerUrl; ?>" />
+					</HTTP>
+				</DCPType>
+			</Transaction>
+		<?php
+	}
+	private function _printLockFeatureXml($wfsServerUrl) {
+		?>
+			<LockFeature>
+				<DCPType>
+					<HTTP>
+						<Get onlineResource="<?php echo $wfsServerUrl; ?>?REQUEST=LockFeature" />
+					</HTTP>
+				</DCPType>
+				<DCPType>
+					<HTTP>
+						<Post onlineResource="<?php echo $wfsServerUrl; ?>" />
+					</HTTP>
+				</DCPType>
+			</LockFeature>
+		<?php
+	}
+	private function _printGetFeatureWithLockXml($wfsServerUrl) {
+		?>
+			<GetFeatureWithLock>
+				<ResultFormat>
+					<GML2 />
+				</ResultFormat>
+				<DCPType>
+					<HTTP>
+						<Get onlineResource="<?php echo $wfsServerUrl; ?>?REQUEST=GetFeatureWithLock" />
+					</HTTP>
+				</DCPType>
+				<DCPType>
+					<HTTP>
+						<Post onlineResource="<?php echo $wfsServerUrl; ?>" />
+					</HTTP>
+				</DCPType>
+			</GetFeatureWithLock>
+		<?php
+	}
+	private function _printFeatureTypeListXml() {
+		// TODO: get list from userAcl
+		$featureTypes = array();
+		$tblsAcl = $this->_usrAcl->getTablesAcl();
+		foreach ($tblsAcl as $tblAcl) {
+			$dataSourceName = 'default_db';// TODO: getSourceName
+			$tblName = $tblAcl->getName();
+			$usrObjList[] = array($dataSourceName, $tblName);
+			$featureType = array();
+			$featureType['ns'] = "p5_{$dataSourceName}";
+			$featureType['Title'] = $tblAcl->getRawLabel();
+			$featureType['Abstract'] = $tblAcl->getRawLabel();
+			$featureType['Keywords'] = array();
+			$featureType['Keywords'][] = $tblAcl->getID();
+			$featureType['Keywords'][] = $tblName;
+			$featureType['Keywords'][] = $tblAcl->getRawLabel();
+			$featureType['Keywords'] = implode(", ", $featureType['Keywords']);
+			$featureType['SRS'] = "EPSG:4326";
+			$featureType['LatLongBoundingBox'] = array();// TODO: feature LatLongBoundingBox
+			$featureType['LatLongBoundingBox']['minx'] = "8.12328509871721";
+			$featureType['LatLongBoundingBox']['miny'] = "38.8575126897477";
+			$featureType['LatLongBoundingBox']['maxx'] = "9.838674658246807";
+			$featureType['LatLongBoundingBox']['maxy'] = "41.31378404137082";
+			$featureTypes[$tblName] = $featureType;
+		}
+		/*
+			<FeatureType>
+				<Name>ppr06:AMBITIPAESAGGIO</Name>
+				<Title>AMBITIPAESAGGIO</Title>
+				<Abstract />
+				<Keywords>features, AMBITIPAESAGGIO</Keywords>
+				<SRS>EPSG:4326</SRS>
+				<LatLongBoundingBox minx="8.12328509871721" miny="38.8575126897477" maxx="9.838674658246807" maxy="41.31378404137082" />
+			</FeatureType>
+		*/
+		$featureTypesXml = '';
+		foreach ($featureTypes as $tblName => $feature) {
+			$featureTypesXml .= '<FeatureType>' . "\n";
+				$featureTypesXml .= '<Name>' . "{$feature['ns']}:{$tblName}" . '</Name>' . "\n";
+				$featureTypesXml .= '<Title>' . "{$feature['Title']}" . '</Title>' . "\n";
+				if (!empty($feature['Abstract'])) {
+					$featureTypesXml .= '<Abstract>' . "{$feature['Abstract']}" . '</Abstract>' . "\n";
+				} else {
+					$featureTypesXml .= '<Abstract/>' . "\n";
+				}
+				if (!empty($feature['Keywords'])) {
+					$featureTypesXml .= '<Keywords>' . "{$feature['Keywords']}" . '</Keywords>' . "\n";
+				} else {
+					$featureTypesXml .= '<Keywords/>' . "\n";
+				}
+				$featureTypesXml .= '<SRS>' . "{$feature['SRS']}" . '</SRS>' . "\n";
+				if (!empty($feature['LatLongBoundingBox'])) {
+					$latLongBoundingBoxOut = array();
+					$latLongBoundingBoxOut[] = 'minx="' . $feature['LatLongBoundingBox']['minx'] . '"';
+					$latLongBoundingBoxOut[] = 'miny="' . $feature['LatLongBoundingBox']['miny'] . '"';
+					$latLongBoundingBoxOut[] = 'maxx="' . $feature['LatLongBoundingBox']['maxx'] . '"';
+					$latLongBoundingBoxOut[] = 'maxy="' . $feature['LatLongBoundingBox']['maxy'] . '"';
+					$featureTypesXml .= '<LatLongBoundingBox ' . implode(' ', $latLongBoundingBoxOut) . ' />';
+				}
+			$featureTypesXml .= '</FeatureType>' . "\n";
+		}
+		return $featureTypesXml;
+	}
+
+}

+ 295 - 0
SE/se-lib/Api/Xsd.php

@@ -0,0 +1,295 @@
+<?php
+
+Lib::loadClass('ApiDataSourceTodo');// TODO: @see Entity/Source/Mysql from feature-schema-install
+Lib::loadClass('Data_Source');
+
+class Api_Xsd {
+
+	private $_apiUser;
+	private $_dataSourceName;
+	private $_tblName;
+	private $_tblSchema;
+
+	public function setUser($user) {
+		$this->_apiUser = $user;
+	}
+
+	public function execute($request) {
+		if (!$this->_apiUser->isAdmin()) {
+			throw new HttpException("Forbidden", 403);
+		}
+
+		IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">request->segments (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($request->segments);echo'</pre>';}
+		if (empty($request->segments) || !is_array($request->segments)) return;
+
+		if (count($request->segments) < 1) {
+			throw new HttpException("Data source and table name not specified", 400);
+		}
+
+		$this->_dataSourceName = array_shift($request->segments);
+
+		if ('default_db' == $this->_dataSourceName) {
+			$db = DB::getDB();
+
+			$limit = 1000;
+			$tbls = array();
+			$sql = "show full tables";
+			$res = $db->query($sql);
+			while ($r = $db->fetch($res)) {
+				if ('BASE TABLE' == $r->Table_type) {
+					$tblName = get_object_vars($r);
+					$tblName = array_values($tblName);
+					$tblName = reset($tblName);
+					if ('DEALS_TABLE_2015_03_17_zest_dla_zubryka' == $tblName) {
+						continue;// fields name 'grup_concat(...'
+					}
+					if ('KSIEG_DOKUMENTY' == $tblName) {
+						continue;// fields name '201_...'
+					}
+					if ('KSIEG_DOKUMENTY_HIST' == $tblName) {
+						continue;// fields name '201_...'
+					}
+					if ('Rozdzielcza_rurociag_wsg84' == $tblName) {
+						continue;// fields name with space ' '
+					}
+					$ds = new Data_Source();
+					$ds->set_table($tblName);
+					$ds->get_cols();
+					$tbls[$tblName] = $ds;
+					if (--$limit < 0) break;
+				}
+			}
+			$xml = <<<XMLEOF
+<?xml version="1.0"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:{$this->_dataSourceName}="http://biuro.biall-net.pl/xmlschema_procesy5/{$this->_dataSourceName}" targetNamespace="http://biuro.biall-net.pl/xmlschema_procesy5/{$this->_dataSourceName}">
+XMLEOF;
+			Lib::loadClass('TableAcl');
+			$acl = new TableAcl();
+			foreach ($tbls as $tblName => $ds) {
+				$xmlFields = array();
+				$tblFields = $ds->_col_types;
+				foreach ($tblFields as $fldName => $colType) {
+					$minOccurs = 0;
+					$maxOccurs = 1;
+					$colDefault = null;
+					$xsdType = 'string';
+					//$xsdType = 'token';
+					//$xsdType = 'integer';
+					//$xsdType = 'double';
+					$xsdRestrictions = array();
+					if (false !== strpos($colType, ';')) {
+						$colType = explode(';', $colType, 2);
+						$colDefault = array_pop($colType);
+						$colType = array_shift($colType);
+					}
+
+					//$xsdRestrictions[] = '<xs:enumeration value="WAITING"/>';
+					if ($this->isIntegerField($colType)) {
+						$xsdType = 'integer';
+					}
+					else if ($this->isDecimalField($colType)) {
+						$xsdType = 'double';
+					}
+					else if ($this->isStringField($colType)) {
+						$xsdType = 'string';
+						$maxLength = (int)str_replace(array(' ','(',')'), '', substr($colType, strpos($colType, '(') + 1, -1));
+						if ($maxLength > 0) {
+							$xsdRestrictions[] = '<xs:maxLength value="' . $maxLength . '"/>';
+						}
+					}
+					else if ($this->isTextField($colType)) {
+						$xsdType = 'string';
+					}
+					else if ($this->isDateField($colType)) {
+						$xsdType = 'token';
+						$xsdDatePattern = '[0-9]{4}-[0-9]{2}-[0-9]{2}';
+						$xsdRestrictions[] = '<xs:pattern value="' . $xsdDatePattern . '"/>';
+					}
+					else if ($this->isDateTimeField($colType)) {
+						$xsdType = 'token';
+						$xsdDatePattern = '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}';
+						$xsdRestrictions[] = '<xs:pattern value="' . $xsdDatePattern . '"/>';
+					}
+					else if (substr($colType, 0, 4) == 'enum') {
+						$xsdType = 'string';
+
+						$values = explode(',', str_replace(array('(',')',"'",'"'), '', substr($colType, 5)));
+						foreach ($values as $val) {
+							$xsdRestrictions[] = '<xs:enumeration value="' . $val . '"/>';
+						}
+					}
+					else if ('polygon' == $colType) {
+						$xsdType = 'string';
+					}// Wielokąt
+					else if ('multipolygon' == $colType) {
+						$xsdType = 'string';
+					}// Zbiór wielokątów
+					else if ('linestring' == $colType) {
+						$xsdType = 'string';
+					}// Krzywa z interpolacji liniowej pomiędzy punktami
+					else if ('point' == $colType) {
+						$xsdType = 'string';
+					}// Punkt w przestrzeni 2-wymiarowej
+					else if ('geometry' == $colType) {
+						$xsdType = 'string';
+					}// Typy, które mogą przechowywać geometrię dowolnego typu
+					else if ('multipoint' == $colType) {
+						$xsdType = 'string';
+					}// Zbiór punktów
+					else if ('multilinestring' == $colType) {
+						$xsdType = 'string';
+					}// Zbiór krzywych z interpolacji liniowej pomiędzy punktami
+					else if ('geometrycollection' == $colType) {
+						$xsdType = 'string';
+					}// Zbiór obiektów geometrycznych dowolnego typu
+					else if ('timestamp' == substr($colType, 0, 9)) {
+						$xsdType = 'string';
+					}
+					else if ('time' == substr($colType, 0, 4)) {
+						$xsdType = 'token';
+						$xsdDatePattern = '[0-9]{2}:[0-9]{2}:[0-9]{2}';
+						$xsdRestrictions[] = '<xs:pattern value="' . $xsdDatePattern . '"/>';
+					}
+					else if ('binary' == substr($colType, 0, 6)
+									 || 'varbinary' == substr($colType, 0, 9)) {
+						$xsdType = 'hexBinary';
+					}
+					else if ('blob' == substr($colType, 0, 4)
+									 || 'longblob' == substr($colType, 0, 8)
+									 || 'mediumblob' == substr($colType, 0, 10)
+									 || 'tinyblob' == substr($colType, 0, 8)) {
+						$xsdType = 'hexBinary';
+					}
+					else if (substr($colType, 0, 3) == 'set') {
+						$xsdType = 'string';
+
+						$values = explode(',', str_replace(array('(',')',"'",'"'), '', substr($colType, 4)));
+						foreach ($values as $val) {
+							$xsdRestrictions[] = '<xs:enumeration value="' . $val . '"/>';
+						}
+					}
+					else {
+						$xsdType = 'unknown-Type-'.$colType.'';
+					}
+
+					$xmlFld = '';
+					$xmlFld .= '<xs:element minOccurs="' . $minOccurs . '" maxOccurs="' . $maxOccurs . '" name="' . $fldName . '">';
+						$xmlFld .= "\n\t\t\t\t" . '<xs:simpleType>';
+						if (!empty($xsdRestrictions)) {
+							$xmlFld .= "\n\t\t\t\t\t" . '<xs:restriction base="xs:' . $xsdType . '">';
+								$xmlFld .= "\n\t\t\t\t\t\t" . implode("\n\t\t\t\t\t\t", $xsdRestrictions);
+							$xmlFld .= "\n\t\t\t\t\t" . '</xs:restriction>';
+						} else {
+							$xmlFld .= "\n\t\t\t\t\t" . '<xs:restriction base="xs:' . $xsdType . '"/>';
+						}
+						$xmlFld .= "\n\t\t\t\t" . '</xs:simpleType>';
+					$xmlFld .= "\n\t\t\t" . '</xs:element>';
+					$xmlFields[] = $xmlFld;
+				}
+				$xmlFields = implode("\n\t\t\t", $xmlFields);
+				$xml .= <<<XMLEOF
+
+
+	<xs:element name="{$tblName}" type="{$this->_dataSourceName}:{$tblName}"/>
+
+	<xs:complexType name="{$tblName}">
+		<xs:sequence>
+			{$xmlFields}
+		</xs:sequence>
+	</xs:complexType>
+XMLEOF;
+			}
+			$xml .= <<<XMLEOF
+</xs:schema>
+XMLEOF;
+
+			header('Content-Type: text/xml; charset=utf-8');
+			echo $xml;
+			exit;
+
+			//echo'<pre>';print_r($tbls);echo'</pre>';
+		}
+
+		// return document tree - array of arrays
+	}
+
+	// FROM TableAcl {
+	public function isIntegerField($type) {
+		if (substr($type, 0, 3) == 'int'
+				|| substr($type, 0, 7) == 'tinyint'
+				|| substr($type, 0, 8) == 'smallint'
+				|| substr($type, 0, 9) == 'mediumint'
+				|| substr($type, 0, 6) == 'bigint'
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	public function isDecimalField($type) {
+		if (substr($type, 0, 7) == 'decimal'
+				|| substr($type, 0, 7) == 'numeric'
+				|| substr($type, 0, 6) == 'double'
+				|| substr($type, 0, 5) == 'float'
+				|| substr($type, 0, 4) == 'real'
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	public function isDateField($type) {
+		if (substr($type, 0, 4) == 'date' && substr($type, 0, 8) != 'datetime') {
+			return true;
+		}
+		return false;
+	}
+
+	public function isDateTimeField($type) {
+		if (substr($type, 0, 8) == 'datetime') {
+			return true;
+		}
+		return false;
+	}
+
+	public function isStringField($type) {
+		if (substr($type, 0, 7) == 'varchar'
+				|| substr($type, 0, 4) == 'char'
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	public function isTextField($type) {
+		if (substr($type, 0, 4) == 'text'
+				|| substr($type, 0, 8) == 'tinytext'
+				|| substr($type, 0, 10) == 'mediumtext'
+				|| substr($type, 0, 8) == 'longtext'
+		) {
+			return true;
+		}
+		return false;
+	}
+	// FROM } TableAcl
+
+	private function getDataSource() {
+		if (!$this->_dataSource) {
+
+			// TODO: get data source from Factory
+			{
+				if ('default_db' == $this->_dataSourceName) {
+					$this->_dataSource = new ApiDataSourceTodo($this->_dataSourceName);
+					$this->_dataSource->setTable($this->_tblName);
+				}
+				else if ('931' == $this->_dataSourceName) {
+					$this->_dataSource = new ApiDataSourceTodo($this->_dataSourceName);
+					$this->_dataSource->setTable($this->_tblName);
+				}
+			}
+
+		}
+		return $this->_dataSource;
+	}
+
+}

+ 9 - 6
SE/se-lib/ApiUser.php

@@ -19,12 +19,15 @@ class ApiUser {
 				$this->exitUnauthorized();
 			}
 
-			$errors = array();
-			$ldap = LDAP::getInstance();
-			if ($ldap != null && $ldap->isConnected()) {
-				$this->_user = User::loginByLDAP($login, $pass, $errors);
-			} else {
-				$this->_user = User::loginByDB($login, $pass, $errors);
+			try {
+				$ldap = LDAP::getInstance();
+				if ($ldap != null && $ldap->isConnected()) {
+					$this->_user = User::loginByLDAP($login, $pass);
+				} else {
+					$this->_user = User::loginByDB($login, $pass);
+				}
+			} catch (Exception $e) {
+				$this->exitUnauthorized();
 			}
 
 			if (!$this->_user) {

+ 27 - 0
SE/se-lib/Data_Source.php

@@ -507,6 +507,9 @@ if(V::get('DBG_DS', 0, $_GET) > 0){echo'<pre style="max-height:200px;overflow:au
 				}
 			}
 		}
+		else if ('GeometryType=' == substr($fltrValue, 0, 13)) {
+			$sqlFilter = "GeometryType({$tblPrefix}.`{$fldName}`)='" . substr($fltrValue, 13) . "'";
+		}
 		return $sqlFilter;
 	}
 
@@ -821,6 +824,18 @@ if(V::get('DBG_DS', 0, $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 			throw new HttpException("Item Primary Key not set!", 400);
 		}
 
+		// the geom fix
+		foreach ($itemPatch as $fld => $val) {
+			if ($this->isGeomField($fld)) {
+				if (!empty($val)
+						&& 'NULL' !== $val
+						&& 'GeomFromText' != substr($val, 0, strlen('GeomFromText'))
+					 ) {
+					$itemPatch[$fld] = "GeomFromText('{$val}')";
+				}
+			}
+		}
+
 		$itemPatch = (object)$itemPatch;
 		$affected = $this->_db->UPDATE_OBJ($this->_tbl, $itemPatch);
 		if ($affected < 0) {
@@ -845,6 +860,18 @@ if(V::get('DBG_DS', 0, $_GET) > 2){echo'<pre style="max-height:200px;overflow:au
 			return 0;// nothing to insert
 		}
 
+		// the geom fix
+		foreach ($item as $fld => $val) {
+			if ($this->isGeomField($fld)) {
+				if (!empty($val)
+						&& 'NULL' !== $val
+						&& 'GeomFromText' != substr($val, 0, strlen('GeomFromText'))
+					 ) {
+					$item[$fld] = "GeomFromText('{$val}')";
+				}
+			}
+		}
+
 		$item = (object)$item;
 		$primaryKey = $this->_db->ADD_NEW_OBJ($this->_tbl, $item);
 		if ($primaryKey < 0) {

+ 83 - 0
SE/se-lib/TableAcl.php

@@ -1069,6 +1069,13 @@ class TableAcl {
 		return $ds->isGeomField($fldName);
 	}
 
+	public function getGeomFieldType($fldName) {
+		$dbGeomType = $this->getFieldType($fldName);
+		$dbGeomType = (!empty($dbGeomType['type']))? $dbGeomType['type'] : '';
+		$geomType = strtolower($dbGeomType);
+		return $geomType;
+	}
+
 	public function getHistItems($id) {
 		$ds = $this->getDataSource();
 		return $ds->getHistItems($id);
@@ -1266,4 +1273,80 @@ class TableAcl {
 		return $ds->getPrimaryKeyField();
 	}
 
+	public function isIntegerField($fldName) {
+		$type = $this->getFieldType($fldName);
+		if (!$type) return false;
+
+		if (substr($type['type'], 0, 3) == 'int'
+				|| substr($type['type'], 0, 7) == 'tinyint'
+				|| substr($type['type'], 0, 8) == 'smallint'
+				|| substr($type['type'], 0, 9) == 'mediumint'
+				|| substr($type['type'], 0, 6) == 'bigint'
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	public function isDecimalField($fldName) {
+		$type = $this->getFieldType($fldName);
+		if (!$type) return false;
+
+		if (substr($type['type'], 0, 7) == 'decimal'
+				|| substr($type['type'], 0, 7) == 'numeric'
+				|| substr($type['type'], 0, 6) == 'double'
+				|| substr($type['type'], 0, 5) == 'float'
+				|| substr($type['type'], 0, 4) == 'real'
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	public function isDateField($fldName) {
+		$type = $this->getFieldType($fldName);
+		if (!$type) return false;
+
+		if (substr($type['type'], 0, 4) == 'date' && substr($type['type'], 0, 8) != 'datetime') {
+			return true;
+		}
+		return false;
+	}
+
+	public function isDateTimeField($fldName) {
+		$type = $this->getFieldType($fldName);
+		if (!$type) return false;
+
+		if (substr($type['type'], 0, 4) == 'datetime') {
+			return true;
+		}
+		return false;
+	}
+
+	public function isStringField($fldName) {
+		$type = $this->getFieldType($fldName);
+		if (!$type) return false;
+
+		if (substr($type['type'], 0, 7) == 'varchar'
+				|| substr($colType['type'], 0, 4) == 'char'
+		) {
+			return true;
+		}
+		return false;
+	}
+
+	public function isTextField($fldName) {
+		$type = $this->getFieldType($fldName);
+		if (!$type) return false;
+
+		if (substr($colType['type'], 0, 4) == 'text'
+				|| substr($colType['type'], 0, 8) == 'tinytext'
+				|| substr($colType['type'], 0, 10) == 'mediumtext'
+				|| substr($colType['type'], 0, 8) == 'longtext'
+		) {
+			return true;
+		}
+		return false;
+	}
+
 }

+ 68 - 0
SE/wfs.php

@@ -0,0 +1,68 @@
+<?php
+
+/* QGIS- reverse inginiering:
+	?SERVICE=WFS&REQUEST=GetCapabilities&VERSION=1.0.0
+ */
+
+if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') {
+	header("Location: https://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}");
+	exit();
+}
+
+require_once dirname(__FILE__) . '/se-lib/bootstrap.php';
+
+date_default_timezone_set('Europe/Warsaw');// PHP 5 >= 5.1.0 required by date functions
+
+$errorReportingLevel = E_ALL & ~E_NOTICE;
+error_reporting($errorReportingLevel);
+ini_set('error_reporting', $errorReportingLevel);
+
+if (file_exists(APP_PATH_ROOT . "/config/.config_{$_SERVER['SERVER_NAME']}.php")) {
+	require APP_PATH_ROOT . "/config/.config_{$_SERVER['SERVER_NAME']}.php";
+}
+if (file_exists(APP_PATH_ROOT . "/.config.php")) include APP_PATH_ROOT . "/.config.php";
+
+require_once APP_PATH_ROOT . "/superedit-SEF.php";
+SEF('DEBUG_S');
+
+Lib::loadClass('ApiUser');
+Lib::loadClass('Api');
+
+session_start();
+session_write_close();
+
+$apiUser = new ApiUser();
+// ?LOGIN=LOGOUT
+if ('LOGOUT' == V::get('LOGIN', '', $_GET)) {
+	$apiUser->logout();
+} else {
+	$apiUser->auth();
+}
+
+set_time_limit(5 * 60);
+
+$reqUri = V::get('REQUEST_URI', '', $_SERVER);
+$reqScript = V::get('SCRIPT_NAME', '', $_SERVER);
+$taskPath = str_replace($reqScript, '', $reqUri);
+IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">taskPath (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($taskPath);echo'</pre>';}
+
+$api = new Api();
+$api->setUser($apiUser);
+try {
+	$api->execute($taskPath);
+}
+catch (HttpException $e) {
+	Http::sendHeaderByCode($e->getCode());
+	echo $e->getMessage();
+}
+catch (Exception $e) {
+	Http::sendHeaderByCode(500);
+	echo $e->getMessage();
+}
+
+IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">PHP_AUTH_USER (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($_SERVER['PHP_AUTH_USER']);echo'</pre>';}
+IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">REQUEST_URI (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($_SERVER['REQUEST_URI']);echo'</pre>';}
+IF(V::get('DBG','',$_GET)){echo'<pre style="max-height:200px;overflow:auto;border:1px solid red;text-align:left;">SCRIPT_NAME (' . __CLASS__ . '::' . __FUNCTION__ . ':' . __LINE__ . '): ';print_r($_SERVER['SCRIPT_NAME']);echo'</pre>';}
+IF(V::get('DBG','',$_GET)){die("\nEOF L." . __LINE__);}
+
+?>