diff --git a/Component/DataStore.php b/Component/DataStore.php index 9a854f5..5c64f56 100644 --- a/Component/DataStore.php +++ b/Component/DataStore.php @@ -3,7 +3,6 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Statement; -use FOM\UserBundle\Entity\User; use Mapbender\DataSourceBundle\Component\Drivers\BaseDriver; use Mapbender\DataSourceBundle\Component\Drivers\DoctrineBaseDriver; use Mapbender\DataSourceBundle\Component\Drivers\Interfaces\Base; @@ -12,7 +11,7 @@ use Mapbender\DataSourceBundle\Component\Drivers\YAML; use Mapbender\DataSourceBundle\Entity\DataItem; use Symfony\Component\Config\Definition\Exception\Exception; -use Symfony\Component\DependencyInjection\ContainerAware; +use Symfony\Component\DependencyInjection\ContainerAwareTrait; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -23,8 +22,10 @@ * @package Mapbender\DataSourceBundle * @author Andriy Oblivantsev */ -class DataStore extends ContainerAware +class DataStore { + use ContainerAwareTrait; + const ORACLE_PLATFORM = 'oracle'; const POSTGRESQL_PLATFORM = 'postgresql'; const SQLITE_PLATFORM = 'sqlite'; diff --git a/Component/Drivers/DoctrineBaseDriver.php b/Component/Drivers/DoctrineBaseDriver.php index dd7d5b9..f5fffcc 100644 --- a/Component/Drivers/DoctrineBaseDriver.php +++ b/Component/Drivers/DoctrineBaseDriver.php @@ -244,27 +244,7 @@ public function getSelectQueryBuilder(array $fields = array()) */ public function search(array $criteria = array()) { - - /** @var Statement $statement */ - $maxResults = isset($criteria['maxResults']) ? intval($criteria['maxResults']) : self::MAX_RESULTS; - $where = isset($criteria['where']) ? $criteria['where'] : null; - $queryBuilder = $this->getSelectQueryBuilder(); - // $returnType = isset($criteria['returnType']) ? $criteria['returnType'] : null; - - // add filter (https://trac.wheregroup.com/cp/issues/3733) - if (!empty($this->sqlFilter)) { - $queryBuilder->andWhere($this->sqlFilter); - } - - // add second filter (https://trac.wheregroup.com/cp/issues/4643) - if ($where) { - $queryBuilder->andWhere($where); - } - - $queryBuilder->setMaxResults($maxResults); - // $queryBuilder->setParameters($params); - $statement = $queryBuilder->execute(); - $rows = $statement->fetchAll(); + $rows = $this->getSearchQueryBuilder($criteria)->execute()->fetchAll(); $hasResults = count($rows) > 0; // Cast array to DataItem array list @@ -439,6 +419,34 @@ public function getLastInsertId() return $this->getConnection()->lastInsertId(); } + /** + * Get search query builder + * + * @param array $criteria + * @return QueryBuilder + */ + public function getSearchQueryBuilder(array $criteria) + { + /** @var Statement $statement */ + $maxResults = isset($criteria['maxResults']) ? intval($criteria['maxResults']) : self::MAX_RESULTS; + $where = isset($criteria['where']) ? $criteria['where'] : null; + $queryBuilder = $this->getSelectQueryBuilder(); + // $returnType = isset($criteria['returnType']) ? $criteria['returnType'] : null; + + // add filter (https://trac.wheregroup.com/cp/issues/3733) + if (!empty($this->sqlFilter)) { + $queryBuilder->andWhere($this->sqlFilter); + } + + // add second filter (https://trac.wheregroup.com/cp/issues/4643) + if ($where) { + $queryBuilder->andWhere($where); + } + + $queryBuilder->setMaxResults($maxResults); + return $queryBuilder; + } + /** * Extract ordered type list from two associate key lists of data and types. * @@ -459,4 +467,17 @@ protected function extractTypeValues(array $data, array $types) return $typeValues; } + + /** + * Return next possible ID + * + * @return mixed + */ + public function getNextPossibleId() + { + $con = $this->connection; + return $con->fetchColumn('SELECT MAX(' + . $con->quoteIdentifier($this->getUniqueId()) + . ")+1 FROM " . $con->quoteIdentifier($this->getTableName())); + } } \ No newline at end of file diff --git a/Component/Drivers/PostgreSQL.php b/Component/Drivers/PostgreSQL.php index d618ee2..3755ae2 100644 --- a/Component/Drivers/PostgreSQL.php +++ b/Component/Drivers/PostgreSQL.php @@ -326,6 +326,17 @@ public function getGeomAttributeAsWkt($geometryAttribute, $sridTo) return "ST_ASTEXT(ST_TRANSFORM($geomFieldName, $sridTo)) AS $geomFieldName"; } + /** + * @inheritdoc + */ + public function getGeomAttributeAsJson($geometryAttribute, $sridTo) + { + $connection = $this->getConnection(); + $geomFieldName = $connection->quoteIdentifier($geometryAttribute); + $sridTo = is_numeric($sridTo)?intval($sridTo):$connection->quote($sridTo); + return "ST_AsGeoJSON(ST_TRANSFORM($geomFieldName, $sridTo)) AS $geomFieldName"; + } + /** * @inheritdoc */ diff --git a/Component/Drivers/SQLite.php b/Component/Drivers/SQLite.php index a772d9b..53f9d3e 100644 --- a/Component/Drivers/SQLite.php +++ b/Component/Drivers/SQLite.php @@ -1,14 +1,36 @@ */ -class SQLite extends DoctrineBaseDriver +class SQLite extends PostgreSQL implements Geographic { + /** + * Get spatial driver instance + * + * @return SpatialiteShellDriver|null + */ + public function getSpatialDriver() + { + static $driver = null; + + if (!$driver) { + $dbPath = isset($this->settings['path']) ? $this->settings['path'] : $this->container->get('kernel')->getRootDir() . "/app/db/"; + $driver = new SpatialiteShellDriver($dbPath); + } + + return $driver; + } + /** * Get table fields * @@ -29,4 +51,112 @@ public function getStoreFields() } return $columns; } + + /** + * @inheritdoc + */ + public function search(array $criteria = array()) + { + $sql = $this->getSearchQueryBuilder($criteria)->getSQL(); + $rows = $this->getSpatialDriver()->query($sql); + $hasResults = count($rows) > 0; + + // Cast array to DataItem array list + if ($hasResults) { + $this->prepareResults($rows); + } + + return $rows; + } + + /** + * Add geometry column + * + * @param $tableName + * @param $type + * @param $srid + * @param string $geomFieldName + * @param string $schemaName + * @param int $dimensions + * @return mixed + */ + public function addGeometryColumn($tableName, + $type, + $srid, + $geomFieldName = "geom", + $schemaName = "public", + $dimensions = 2) + { + $spatialDriver = $this->getSpatialDriver(); + return $spatialDriver->addGeometryColumn($tableName, $geomFieldName, $srid, $type); + } + + /** + * Get table geometry type + * + * @param $tableName + * @param string $schema + * @return mixed + */ + public function getTableGeomType($tableName, $schema = null) + { + $connection = $this->getSpatialDriver(); + if (strpos($tableName, '.')) { + list($schema, $tableName) = explode('.', $tableName); + } + $_schema = $schema ? $connection->quote($schema) : 'current_schema()'; + + $type = $connection->query("SELECT \"type\" + FROM geometry_columns + WHERE f_table_schema = " . $_schema . " + AND f_table_name = " . $connection->quote($tableName))->fetchColumn(); + return $type; + } + + /** + * @param $ewkt + * @param null $srid + * @return mixed + * @internal param $wkt + */ + public function transformEwkt($ewkt, $srid = null) + { + $db = $this->getSpatialDriver(); + $type = $this->getTableGeomType($this->getTableName()); + $wktType = static::getWktType($ewkt); + + if ($type + && $wktType != $type + && in_array(strtoupper($wktType), Feature::$simpleGeometries) + && in_array(strtoupper($type), Feature::$complexGeometries) + ) { + $ewkt = 'SRID=' . $srid . ';' . $db->fetchColumn("SELECT ST_ASTEXT(ST_TRANSFORM(ST_MULTI(" . $db->quote($ewkt) . "),$srid))"); + } + + $srid = is_numeric($srid) ? intval($srid) : $db->quote($srid); + $ewkt = $db->quote($ewkt); + + return $db->fetchColumn("SELECT ST_TRANSFORM(ST_GEOMFROMTEXT($ewkt), $srid)"); + } + + /** + * Get WKB geometry attribute as WKT + * + * @param string $tableName + * @param string $geomFieldName + * @return string SQL + */ + public function findGeometryFieldSrid($tableName, $geomFieldName) + { + $connection = $this->getSpatialDriver(); + $schemaName = "current_schema()"; + if (strpos($tableName, ".")) { + list($schemaName, $tableName) = explode('.', $tableName); + $schemaName = $connection->quote($schemaName); + } + + return $connection->fetchColumn("SELECT Find_SRID(" . $schemaName . ", + " . $connection->quote($tableName) . ", + " . $connection->quote($geomFieldName) . ")"); + } } \ No newline at end of file diff --git a/Component/FeatureType.php b/Component/FeatureType.php index e748ef7..c1d9221 100644 --- a/Component/FeatureType.php +++ b/Component/FeatureType.php @@ -6,6 +6,7 @@ use Doctrine\ORM\Mapping as ORM; use Mapbender\CoreBundle\Component\Application as AppComponent; use Mapbender\DataSourceBundle\Component\Drivers\BaseDriver; +use Mapbender\DataSourceBundle\Component\Drivers\DoctrineBaseDriver; use Mapbender\DataSourceBundle\Component\Drivers\Interfaces\Geographic; use Mapbender\DataSourceBundle\Component\Drivers\Oracle; use Mapbender\DataSourceBundle\Component\Drivers\PostgreSQL; @@ -191,7 +192,17 @@ public function save($featureData, $autoUpdate = true) if ($this->allowSave) { // Insert if no ID given if (!$autoUpdate || !$feature->hasId()) { - $feature = $this->insert($feature); + try{ + $feature = $this->insert($feature); + } catch (\Exception $e){ + // Fallback, if can't save, course no auto ID set + // then try it to set it manuel and try to save it one more time. + $driver = $this->getDriver(); + if($driver instanceof DoctrineBaseDriver){ + $feature->setId($driver->getNextPossibleId()); + $feature = $this->insert($feature); + } + } } // Replace if has ID else { $feature = $this->update($feature); @@ -313,6 +324,8 @@ public function update($featureData) * * @param array $criteria * @return Feature[] + * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException + * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException */ public function search(array $criteria = array()) { diff --git a/Controller/BaseController.php b/Controller/BaseController.php index 76a1de6..1d4186c 100644 --- a/Controller/BaseController.php +++ b/Controller/BaseController.php @@ -15,7 +15,7 @@ class BaseController extends Controller */ protected function getRequestData() { - $content = $this->getRequest()->getContent(); + $content = $this->get('request_stack')->getCurrentRequest()->getContent(); $request = array_merge($_POST, $_GET); if (!empty($content)) { $request = array_merge($request, json_decode($content, true)); diff --git a/Element/BaseElement.php b/Element/BaseElement.php index 2f4ece4..b22618d 100644 --- a/Element/BaseElement.php +++ b/Element/BaseElement.php @@ -3,7 +3,6 @@ use Doctrine\DBAL\Connection; use Mapbender\CoreBundle\Element\HTMLElement; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Zumba\Util\JsonSerializer; @@ -127,6 +126,71 @@ protected function prepareItem($item) return $item; } + /** + * + * Parse raw HTTP request data + * + * Pass in $a_data as an array. This is done by reference to avoid copying + * the data around too much. + * + * Any files found in the request will be added by their field name to the + * $data['files'] array. + * + * @see http://www.chlab.ch/blog/archives/webdevelopment/manually-parse-raw-http-data-php + * @param array Empty array to fill with data + * @return array Associative array of request data + */ + public static function parseMultiPartRequest($content) + { + $result = array(); + // read incoming data + + // grab multipart boundary from content type header + preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches); + + // content type is probably regular form-encoded + if (!count($matches)) { + // we expect regular puts to containt a query string containing data + parse_str(urldecode($content), $result); + return $result; + } + + $boundary = $matches[1]; + + // split content by boundary and get rid of last -- element + $a_blocks = preg_split("/-+$boundary/", $content); + array_pop($a_blocks); + + // loop data blocks + foreach ($a_blocks as $id => $block) { + if (empty($block)) { + continue; + } + + // you'll have to var_dump $block to understand this and maybe replace \n or \r with a visibile char + + // parse uploaded files + if (strpos($block, 'application/octet-stream') !== false) { + // match "name", then everything after "stream" (optional) except for prepending newlines + preg_match("/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s", $block, $matches); + $keyName = preg_replace('/\[\]$/', '', $matches[1]); + + if (!isset($result[ $keyName ])) { + $result[ $keyName ] = array(); + } + $result[ $keyName ][] = $matches[2]; + + } // parse all other fields + else { + // match "name" and optional value in between newline sequences + preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $block, $matches); + $result[ $matches[1] ] = $matches[2]; + } + } + + return $result; + } + /** * @return array|mixed * @throws \LogicException @@ -135,11 +199,17 @@ protected function prepareItem($item) */ protected function getRequestData() { - $content = $this->container->get('request')->getContent(); - $request = array_merge($_POST, $_GET); - - if (!empty($content)) { - $request = array_merge($request, json_decode($content, true)); + $content = $this->container->get('request')->getContent(); + $request = array_merge($_POST, $_GET); + $hasContent = !empty($content); + + if ($hasContent) { + $isMultipart = strpos($content, '-') === 0; + if ($isMultipart) { + $request = array_merge($request, static::parseMultiPartRequest($content)); + } else { + $request = array_merge($request, json_decode($content, true)); + } } return $this->decodeRequest($request); diff --git a/Entity/BaseConfiguration.php b/Entity/BaseConfiguration.php index da1345b..6f7f4f3 100644 --- a/Entity/BaseConfiguration.php +++ b/Entity/BaseConfiguration.php @@ -1,4 +1,5 @@ fill($args); + if ($data) { + if (isset($data['@attributes'])) { + $this->fill($data['@attributes']); + } + $this->fill($data); } } /** - * Fill object with values from $args - * - * @param $args + * @param array $data */ - public function fill($args) + public function fill(array &$data) { - foreach (get_object_vars($this) as $key => $value) { - foreach ($args as $argKey => $argValue) { - if ($key == $argKey || $key == $argKey . "Name") { - $this->$key = $argValue; + static $className, $methods, $vars, $reflection; + + if (!$className) { + $className = get_class($this); + $methods = get_class_methods($className); + $vars = array_keys(get_class_vars($className)); + $reflection = new \ReflectionClass($className); + } + + foreach ($data as $k => $v) { + if ($k == "@attributes") { + continue; + } + + $methodName = 'set' . ucfirst($k); + if (in_array($methodName, $methods)) { + $this->{$methodName}($v); + continue; + } + + $methodName = 'set' . ucfirst($this->removeNameSpaceFromVariableName($k)); + if (in_array($methodName, $methods)) { + $this->{$methodName}($v); + continue; + } + + $varName = lcfirst($this->removeNameSpaceFromVariableName($k)); + if (in_array($varName, $vars)) { + $docComment = $reflection->getProperty($varName)->getDocComment(); + if (preg_match('/@var ([\\\]?[A-Z]\S+)/s', $docComment, $annotations)) { + $varClassName = $annotations[1]; + if (class_exists($varClassName)) { + $v = new $varClassName($v); + } + } + $this->{$varName} = $v; + continue; + } + + $varName .= "s"; + if (in_array($varName, $vars)) { + $docComment = $reflection->getProperty($varName)->getDocComment(); + if ($annotations = self::parse('/@var\s+([\\\]?[A-Z]\S+)(\[\])/s', $docComment)) { + $varClassName = $annotations[1]; + if (class_exists($varClassName)) { + $items = array(); + $isNumeric = is_int(key($v)); + $list = $isNumeric ? $v : array($v); + foreach ($list as $subData) { + $items[] = new $varClassName($subData); + } + $v = $items; + } } + $this->{$varName} = $v; + continue; } } } /** - * Export + * @param $name + * @return mixed + */ + private function removeNameSpaceFromVariableName($name) + { + return preg_replace("/^.+?_/", '', $name); + } + + /** + * Export data as array */ public function toArray() { return get_object_vars($this); } + + /** + * Parse string + * + * @param string $reg regular expression + * @param string $str + * @return null + */ + private static function parse($reg, $str) + { + $annotations = null; + preg_match($reg, $str, $annotations); + return $annotations; + } } \ No newline at end of file diff --git a/composer.json b/composer.json index 7ad9a44..bb2740c 100644 --- a/composer.json +++ b/composer.json @@ -10,10 +10,12 @@ "require": { "php": ">=5.3.3", "zumba/json-serializer": "1.x", + "eslider/spatialite": "0.x", "phayes/geophp": "1.2" }, "require-dev": { "phpunit/phpunit": "^3.7", + "eslider/spatialite": "0.x", "symfony/framework-bundle" :"*" }, "config": {