From 0bf0dc48e5dd6e5d2c2986155dc173786664224f Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Sun, 21 Oct 2018 15:15:56 +0200 Subject: [PATCH 1/8] Fix create type bug --- api.php | 48 ++++++++++--------- src/Tqdev/PhpCrudApi/Cache/MemcacheCache.php | 2 +- src/Tqdev/PhpCrudApi/Cache/MemcachedCache.php | 2 +- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/api.php b/api.php index 6f0b9c23..fa02ae67 100644 --- a/api.php +++ b/api.php @@ -75,7 +75,7 @@ public function __construct(String $prefix, String $config) $this->memcache->addServer($address, $port); } - protected function create(): stdClass + protected function create() /*: \Memcache*/ { return new \Memcache(); } @@ -100,7 +100,7 @@ public function clear(): bool class MemcachedCache extends MemcacheCache { - protected function create(): stdClass + protected function create() /*: \Memcached*/ { return new \Memcached(); } @@ -440,6 +440,11 @@ public function isGeometry(): bool return $this->type == 'geometry'; } + public function isInteger(): bool + { + return in_array($this->type, ['integer', 'bigint', 'smallint', 'tinyint']); + } + public function setPk($value) /*: void*/ { $this->pk = $value; @@ -484,18 +489,15 @@ public function jsonSerialize() class ReflectedDatabase implements \JsonSerializable { - private $name; private $tableTypes; - public function __construct(String $name, array $tableTypes) + public function __construct(array $tableTypes) { - $this->name = $name; $this->tableTypes = $tableTypes; } public static function fromReflection(GenericReflection $reflection): ReflectedDatabase { - $name = $reflection->getDatabaseName(); $tableTypes = []; foreach ($reflection->getTables() as $table) { $tableName = $table['TABLE_NAME']; @@ -505,19 +507,13 @@ public static function fromReflection(GenericReflection $reflection): ReflectedD } $tableTypes[$tableName] = $tableType; } - return new ReflectedDatabase($name, $tableTypes); + return new ReflectedDatabase($tableTypes); } public static function fromJson( /* object */$json): ReflectedDatabase { - $name = $json->name; $tableTypes = (array) $json->tables; - return new ReflectedDatabase($name, $tableTypes); - } - - public function getName(): String - { - return $this->name; + return new ReflectedDatabase($tableTypes); } public function hasTable(String $tableName): bool @@ -547,7 +543,6 @@ public function removeTable(String $tableName): bool public function serialize() { return [ - 'name' => $this->name, 'tables' => $this->tableTypes, ]; } @@ -987,12 +982,11 @@ public function __construct(Router $router, Responder $responder, ReflectionServ public function getDatabase(Request $request): Response { - $name = $this->reflection->getDatabaseName(); $tables = []; foreach ($this->reflection->getTableNames() as $table) { $tables[] = $this->reflection->getTable($table); } - $database = ['name' => $name, 'tables' => $tables]; + $database = ['tables' => $tables]; return $this->responder->success($database); } @@ -1701,6 +1695,8 @@ private function convertRecordValue($conversion, $value) switch ($conversion) { case 'boolean': return $value ? true : false; + case 'integer': + return (int) $value; } return $value; } @@ -1710,6 +1706,9 @@ private function getRecordValueConversion(ReflectedColumn $column): String if (in_array($this->driver, ['mysql', 'sqlsrv']) && $column->isBoolean()) { return 'boolean'; } + if ($this->driver == 'sqlsrv' && $column->getType() == 'bigint') { + return 'integer'; + } return 'none'; } @@ -1818,6 +1817,7 @@ private function getOptions(): array \PDO::ATTR_PERSISTENT => true, ]; case 'sqlsrv':return $options + [ + \PDO::SQLSRV_ATTR_DIRECT_QUERY => false, \PDO::SQLSRV_ATTR_FETCHES_NUMERIC_TYPE => true, ]; } @@ -1886,7 +1886,11 @@ public function createSingle(ReflectedTable $table, array $columnValues) /*: ?St $stmt = $this->query('SELECT LAST_INSERT_ID()', []); break; } - return $stmt->fetchColumn(0); + $pkValue = $stmt->fetchColumn(0); + if ($this->driver == 'sqlsrv' && $table->getPk()->getType() == 'bigint') { + return (int) $pkValue; + } + return $pkValue; } public function selectSingle(ReflectedTable $table, array $columnNames, String $id) /*: ?array*/ @@ -3701,7 +3705,7 @@ private function isOperationOnColumnAllowed(String $operation, String $tableName private function setPath(String $tableName) /*: void*/ { $table = $this->reflection->getTable($tableName); - $type = $table->getType($tableName); + $type = $table->getType(); $pk = $table->getPk(); $pkName = $pk ? $pk->getName() : ''; foreach ($this->operations as $operation => $method) { @@ -3751,7 +3755,7 @@ private function setPath(String $tableName) /*: void*/ private function setComponentSchema(String $tableName) /*: void*/ { $table = $this->reflection->getTable($tableName); - $type = $table->getType($tableName); + $type = $table->getType(); $pk = $table->getPk(); $pkName = $pk ? $pk->getName() : ''; foreach ($this->operations as $operation => $method) { @@ -3793,7 +3797,7 @@ private function setComponentSchema(String $tableName) /*: void*/ private function setComponentResponse(String $tableName) /*: void*/ { $table = $this->reflection->getTable($tableName); - $type = $table->getType($tableName); + $type = $table->getType(); $pk = $table->getPk(); $pkName = $pk ? $pk->getName() : ''; foreach (['list', 'read'] as $operation) { @@ -3818,7 +3822,7 @@ private function setComponentResponse(String $tableName) /*: void*/ private function setComponentRequestBody(String $tableName) /*: void*/ { $table = $this->reflection->getTable($tableName); - $type = $table->getType($tableName); + $type = $table->getType(); $pk = $table->getPk(); $pkName = $pk ? $pk->getName() : ''; if ($pkName && $type == 'table') { diff --git a/src/Tqdev/PhpCrudApi/Cache/MemcacheCache.php b/src/Tqdev/PhpCrudApi/Cache/MemcacheCache.php index 6c6ea581..799b1531 100644 --- a/src/Tqdev/PhpCrudApi/Cache/MemcacheCache.php +++ b/src/Tqdev/PhpCrudApi/Cache/MemcacheCache.php @@ -22,7 +22,7 @@ public function __construct(String $prefix, String $config) $this->memcache->addServer($address, $port); } - protected function create(): stdClass + protected function create() /*: \Memcache*/ { return new \Memcache(); } diff --git a/src/Tqdev/PhpCrudApi/Cache/MemcachedCache.php b/src/Tqdev/PhpCrudApi/Cache/MemcachedCache.php index 13445ce6..24990821 100644 --- a/src/Tqdev/PhpCrudApi/Cache/MemcachedCache.php +++ b/src/Tqdev/PhpCrudApi/Cache/MemcachedCache.php @@ -3,7 +3,7 @@ class MemcachedCache extends MemcacheCache { - protected function create(): stdClass + protected function create() /*: \Memcached*/ { return new \Memcached(); } From 01b40cadc7c45369be6338a6e60ac93902f07720 Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Sun, 21 Oct 2018 22:07:05 +0200 Subject: [PATCH 2/8] Remove file upload support --- README.md | 23 ++----------- examples/clients/upload/form.html | 12 ------- .../Middleware/FileUploadMiddleware.php | 33 ------------------- src/Tqdev/PhpCrudApi/Request.php | 18 ++-------- 4 files changed, 5 insertions(+), 81 deletions(-) delete mode 100644 examples/clients/upload/form.html delete mode 100644 src/Tqdev/PhpCrudApi/Middleware/FileUploadMiddleware.php diff --git a/README.md b/README.md index 105bf7ae..e13e291b 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ These features match features in v1 (see branch "v1"): - [x] Supports POST variables as input (x-www-form-urlencoded) - [x] Supports a JSON object as input - [x] Supports a JSON array as input (batch insert) - - [x] Supports file upload from web forms (multipart/form-data) + - [ ] ~~Supports file upload from web forms (multipart/form-data)~~ - [ ] ~~Condensed JSON output: first row contains field names~~ - [x] Sanitize and validate input using callbacks - [x] Permission system for databases, tables, columns and records @@ -734,26 +734,7 @@ The above example will add a header "X-Time-Taken" with the number of seconds th ### File uploads -The 'fileUpload' middleware allows you to upload a file using a web form (multipart/form-data) like this: - -``` -
- Select image to upload: - - -
-``` - -Then this is handled as if you would have sent: - -``` -POST http://localhost/api.php/records/categories -{"icon_name":"not.gif","icon_type":"image\/gif","icon":"ZGF0YQ==","icon_error":0,"icon_size":4} -``` - -As you can see the "xxx_name", "xxx_type", "xxx_error" and "xxx_size" meta fields are added (where "xxx" is the name of the file field). - -NB: You cannot edit a file using this method, because browsers do not support the "PUT" method in these forms. +File uploads are supported through the [FileReader API](https://caniuse.com/#feat=filereader). ## OpenAPI specification diff --git a/examples/clients/upload/form.html b/examples/clients/upload/form.html deleted file mode 100644 index 1607f831..00000000 --- a/examples/clients/upload/form.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - -
- - - -
- - - diff --git a/src/Tqdev/PhpCrudApi/Middleware/FileUploadMiddleware.php b/src/Tqdev/PhpCrudApi/Middleware/FileUploadMiddleware.php deleted file mode 100644 index 7a400fbd..00000000 --- a/src/Tqdev/PhpCrudApi/Middleware/FileUploadMiddleware.php +++ /dev/null @@ -1,33 +0,0 @@ -getUploadedFiles(); - if (!empty($files)) { - $body = $request->getBody(); - foreach ($files as $fieldName => $file) { - if (isset($file['error']) && $file['error']) { - return $this->responder->error(ErrorCode::FILE_UPLOAD_FAILED, $fieldName); - } - foreach ($file as $key => $value) { - if ($key == 'tmp_name') { - $value = base64_encode(file_get_contents($value)); - $key = $fieldName; - } else { - $key = $fieldName . '_' . $key; - } - $body->$key = $value; - } - } - $request->setBody($body); - } - return $this->next->handle($request); - } -} diff --git a/src/Tqdev/PhpCrudApi/Request.php b/src/Tqdev/PhpCrudApi/Request.php index 9853e954..70f74fdb 100644 --- a/src/Tqdev/PhpCrudApi/Request.php +++ b/src/Tqdev/PhpCrudApi/Request.php @@ -99,17 +99,10 @@ private function decodeBody(String $body) /*: ?object*/ private function parseBody(String $body = null) /*: void*/ { - if ($body) { - $object = $this->decodeBody($body); - } else { - if (!empty($_FILES)) { - $object = (object) $_POST; - } else { - $input = file_get_contents('php://input'); - $object = $this->decodeBody($input); - } + if (!$body) { + $body = file_get_contents('php://input'); } - $this->body = $object; + $this->body = $this->decodeBody($body); } public function getMethod(): String @@ -187,9 +180,4 @@ public static function fromString(String $request): Request } return new Request($method, $path, $query, $headers, $body); } - - public function getUploadedFiles(): array - { - return $_FILES; - } } From c6d216c9297bb6e140555b81fa4e11bf4515754a Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Mon, 22 Oct 2018 00:11:19 +0200 Subject: [PATCH 3/8] Remove fileupload middleware --- api.php | 74 +++++++++++++----------------------- src/Tqdev/PhpCrudApi/Api.php | 4 -- 2 files changed, 27 insertions(+), 51 deletions(-) diff --git a/api.php b/api.php index 2a46b6d4..bf55cfea 100644 --- a/api.php +++ b/api.php @@ -3188,35 +3188,6 @@ public function handle(Request $request): Response } } -// file: src/Tqdev/PhpCrudApi/Middleware/FileUploadMiddleware.php - -class FileUploadMiddleware extends Middleware -{ - public function handle(Request $request): Response - { - $files = $request->getUploadedFiles(); - if (!empty($files)) { - $body = $request->getBody(); - foreach ($files as $fieldName => $file) { - if (isset($file['error']) && $file['error']) { - return $this->responder->error(ErrorCode::FILE_UPLOAD_FAILED, $fieldName); - } - foreach ($file as $key => $value) { - if ($key == 'tmp_name') { - $value = base64_encode(file_get_contents($value)); - $key = $fieldName; - } else { - $key = $fieldName . '_' . $key; - } - $body->$key = $value; - } - } - $request->setBody($body); - } - return $this->next->handle($request); - } -} - // file: src/Tqdev/PhpCrudApi/Middleware/FirewallMiddleware.php class FirewallMiddleware extends Middleware @@ -3266,6 +3237,30 @@ public function handle(Request $request): Response } } +// file: src/Tqdev/PhpCrudApi/Middleware/FormMiddleware.php + +class FormMiddleware extends Middleware +{ + public function handle(Request $request): Response + { + $body = $request->getBody(); + if (!$body) { + $body = file_get_contents('php://input'); + if ($body) { + parse_str($body, $input); + foreach ($input as $key => $value) { + if (substr($key, -9) == '__is_null') { + $input[substr($key, 0, -9)] = null; + unset($input[$key]); + } + } + $request->setBody((object) $input); + } + } + return $this->next->handle($request); + } +} + // file: src/Tqdev/PhpCrudApi/Middleware/JwtAuthMiddleware.php class JwtAuthMiddleware extends Middleware @@ -5070,9 +5065,6 @@ public function __construct(Config $config) case 'jwtAuth': new JwtAuthMiddleware($router, $responder, $properties); break; - case 'fileUpload': - new FileUploadMiddleware($router, $responder, $properties); - break; case 'validation': new ValidationMiddleware($router, $responder, $properties, $reflection); break; @@ -5405,17 +5397,10 @@ private function decodeBody(String $body) /*: ?object*/ private function parseBody(String $body = null) /*: void*/ { - if ($body) { - $object = $this->decodeBody($body); - } else { - if (!empty($_FILES)) { - $object = (object) $_POST; - } else { - $input = file_get_contents('php://input'); - $object = $this->decodeBody($input); - } + if (!$body) { + $body = file_get_contents('php://input'); } - $this->body = $object; + $this->body = $this->decodeBody($body); } public function getMethod(): String @@ -5493,11 +5478,6 @@ public static function fromString(String $request): Request } return new Request($method, $path, $query, $headers, $body); } - - public function getUploadedFiles(): array - { - return $_FILES; - } } // file: src/Tqdev/PhpCrudApi/Response.php diff --git a/src/Tqdev/PhpCrudApi/Api.php b/src/Tqdev/PhpCrudApi/Api.php index 534c94f8..cbc35e76 100644 --- a/src/Tqdev/PhpCrudApi/Api.php +++ b/src/Tqdev/PhpCrudApi/Api.php @@ -14,7 +14,6 @@ use Tqdev\PhpCrudApi\Middleware\BasicAuthMiddleware; use Tqdev\PhpCrudApi\Middleware\CorsMiddleware; use Tqdev\PhpCrudApi\Middleware\CustomizationMiddleware; -use Tqdev\PhpCrudApi\Middleware\FileUploadMiddleware; use Tqdev\PhpCrudApi\Middleware\FirewallMiddleware; use Tqdev\PhpCrudApi\Middleware\JwtAuthMiddleware; use Tqdev\PhpCrudApi\Middleware\MultiTenancyMiddleware; @@ -60,9 +59,6 @@ public function __construct(Config $config) case 'jwtAuth': new JwtAuthMiddleware($router, $responder, $properties); break; - case 'fileUpload': - new FileUploadMiddleware($router, $responder, $properties); - break; case 'validation': new ValidationMiddleware($router, $responder, $properties, $reflection); break; From 5aebfb709a415b91d490b0934c615a135619f825 Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Tue, 23 Oct 2018 08:05:55 +0200 Subject: [PATCH 4/8] Update README.md --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index e13e291b..65ff3247 100644 --- a/README.md +++ b/README.md @@ -858,6 +858,36 @@ To run the functional tests locally you may run the following command: This runs the functional tests from the "tests" directory. It uses the database dumps (fixtures) and database configuration (config) from the corresponding subdirectories. +## Nginx config example +``` +server { + listen 80 default_server; + listen [::]:80 default_server; + + root /var/www/html; + index index.php index.html index.htm index.nginx-debian.html; + server_name server_domain_or_IP; + + location / { + try_files $uri $uri/ =404; + } + + location ~ [^/]\.php(/|$) { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + try_files $fastcgi_script_name =404; + set $path_info $fastcgi_path_info; + fastcgi_param PATH_INFO $path_info; + fastcgi_index index.php; + include fastcgi.conf; + fastcgi_pass unix:/run/php/php7.0-fpm.sock; + } + + location ~ /\.ht { + deny all; + } +} +``` + ### Docker Install docker using the following commands and then logout and login for the changes to take effect: From f802f2b873812d48ce06c33678ff2a9dc8bcc24e Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Thu, 25 Oct 2018 09:41:37 +0200 Subject: [PATCH 5/8] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 65ff3247..c4ecf194 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ There are also proof-of-concept ports of this script that only support basic RES - PostGIS 2.0 or higher for spatial features in PostgreSQL 9.1 or higher - SQL Server 2012 or higher (2017 for Linux support) +## Known issues + +- Seeing integers as strings? Make sure to enable the nd_pdo_mysql extension and disable pdo_mysql. + ## Installation This is a single file application! Upload "`api.php`" somewhere and enjoy! From f865cabfa023e15a3d483232db8d578366155bf2 Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Thu, 25 Oct 2018 09:42:17 +0200 Subject: [PATCH 6/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4ecf194..ce0c71af 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ There are also proof-of-concept ports of this script that only support basic RES ## Known issues -- Seeing integers as strings? Make sure to enable the nd_pdo_mysql extension and disable pdo_mysql. +- Seeing integers as strings? Make sure to enable the `nd_pdo_mysql` extension and disable `pdo_mysql`. ## Installation From 57a26a0427958a9e9a3043e9a7bac0a1bf585f5d Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Wed, 31 Oct 2018 06:24:42 +0100 Subject: [PATCH 7/8] Fix #459 --- src/Tqdev/PhpCrudApi/Record/Condition/Condition.php | 3 +++ .../001_records/055_filter_category_on_null_icon.log | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php b/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php index 6d1e5174..577131fe 100644 --- a/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php +++ b/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php @@ -33,6 +33,9 @@ public static function fromString(ReflectedTable $table, String $value): Conditi if (count($parts) < 2) { return null; } + if (count($parts) < 3) { + $parts[2] = ''; + } $field = $table->getColumn($parts[0]); $command = $parts[1]; $negate = false; diff --git a/tests/functional/001_records/055_filter_category_on_null_icon.log b/tests/functional/001_records/055_filter_category_on_null_icon.log index 302e7f60..5ea443ae 100644 --- a/tests/functional/001_records/055_filter_category_on_null_icon.log +++ b/tests/functional/001_records/055_filter_category_on_null_icon.log @@ -5,3 +5,11 @@ Content-Type: application/json Content-Length: 94 {"records":[{"id":1,"name":"announcement","icon":null},{"id":2,"name":"article","icon":null}]} +=== +GET /records/categories?filter=icon,is +=== +200 +Content-Type: application/json +Content-Length: 94 + +{"records":[{"id":1,"name":"announcement","icon":null},{"id":2,"name":"article","icon":null}]} From 6d9d724a723dd5e1a6b120b1f175248038e1d2e0 Mon Sep 17 00:00:00 2001 From: Maurits van der Schee Date: Wed, 31 Oct 2018 06:29:28 +0100 Subject: [PATCH 8/8] cleanup --- .../PhpCrudApi/Record/Condition/Condition.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php b/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php index 577131fe..adc044cc 100644 --- a/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php +++ b/src/Tqdev/PhpCrudApi/Record/Condition/Condition.php @@ -50,15 +50,13 @@ public static function fromString(ReflectedTable $table, String $value): Conditi $command = substr($command, 1); } } - if (count($parts) == 3 || (count($parts) == 2 && in_array($command, ['ic', 'is', 'iv']))) { - if ($spatial) { - if (in_array($command, ['co', 'cr', 'di', 'eq', 'in', 'ov', 'to', 'wi', 'ic', 'is', 'iv'])) { - $condition = new SpatialCondition($field, $command, $parts[2]); - } - } else { - if (in_array($command, ['cs', 'sw', 'ew', 'eq', 'lt', 'le', 'ge', 'gt', 'bt', 'in', 'is'])) { - $condition = new ColumnCondition($field, $command, $parts[2]); - } + if ($spatial) { + if (in_array($command, ['co', 'cr', 'di', 'eq', 'in', 'ov', 'to', 'wi', 'ic', 'is', 'iv'])) { + $condition = new SpatialCondition($field, $command, $parts[2]); + } + } else { + if (in_array($command, ['cs', 'sw', 'ew', 'eq', 'lt', 'le', 'ge', 'gt', 'bt', 'in', 'is'])) { + $condition = new ColumnCondition($field, $command, $parts[2]); } } if ($negate) {