Skip to content

Commit

Permalink
Fix for #881
Browse files Browse the repository at this point in the history
  • Loading branch information
mevdschee committed May 10, 2022
1 parent e988026 commit 34c4f9f
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 4 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -664,6 +664,7 @@ You can enable the following middleware using the "middlewares" config parameter
- "multiTenancy": Restricts tenants access in a multi-tenant scenario
- "pageLimits": Restricts list operations to prevent database scraping
- "joinLimits": Restricts join parameters to prevent database scraping
- "textSearch": Search in all text fields with a simple paramater
- "customization": Provides handlers for request and response customization
- "json": Support read/write of JSON strings as JSON objects/arrays
- "xml": Translates all input and output from JSON to XML
Expand Down Expand Up @@ -742,6 +743,7 @@ You can tune the middleware behavior using middleware specific configuration par
- "joinLimits.depth": The maximum depth (length) that is allowed in a join path ("3")
- "joinLimits.tables": The maximum number of tables that you are allowed to join ("10")
- "joinLimits.records": The maximum number of records returned for a joined entity ("1000")
- "textSearch.parameter": The name of the parameter used for the search term ("search")
- "customization.beforeHandler": Handler to implement request customization ("")
- "customization.afterHandler": Handler to implement response customization ("")
- "json.controllers": Controllers to process JSON strings for ("records,geojson")
Expand Down
66 changes: 65 additions & 1 deletion api.include.php
Expand Up @@ -3850,6 +3850,11 @@ public function isInteger(): bool
return in_array($this->type, ['integer', 'bigint', 'smallint', 'tinyint']);
}

public function isText(): bool
{
return in_array($this->type, ['varchar', 'clob']);
}

public function setPk($value) /*: void*/
{
$this->pk = $value;
Expand All @@ -3874,7 +3879,7 @@ public function serialize()
{
$json = [
'name' => $this->realName,
'alias' => $this->name!=$this->realName?$this->name:null,
'alias' => $this->name != $this->realName ? $this->name : null,
'type' => $this->type,
'length' => $this->length,
'precision' => $this->precision,
Expand Down Expand Up @@ -9088,6 +9093,61 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}
}

// file: src/Tqdev/PhpCrudApi/Middleware/TextSearchMiddleware.php
namespace Tqdev\PhpCrudApi\Middleware {

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Tqdev\PhpCrudApi\Column\ReflectionService;
use Tqdev\PhpCrudApi\Controller\Responder;
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
use Tqdev\PhpCrudApi\Middleware\Router\Router;
use Tqdev\PhpCrudApi\RequestUtils;

class TextSearchMiddleware extends Middleware
{
private $reflection;

public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
{
parent::__construct($router, $responder, $properties);
$this->reflection = $reflection;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
{
$operation = RequestUtils::getOperation($request);
if ($operation == 'list') {
$tableName = RequestUtils::getPathSegment($request, 2);
$params = RequestUtils::getParams($request);
$parameterName = $this->getProperty('parameter', 'search');
if (isset($params[$parameterName])) {
$search = $params[$parameterName][0];
unset($params[$parameterName]);
$table = $this->reflection->getTable($tableName);
$i = 0;
foreach ($table->getColumnNames() as $columnName) {
$column = $table->getColumn($columnName);
while (isset($params["filter$i"])) {
$i++;
}
if ($i >= 10) {
break;
}
if ($column->isText()) {
$params["filter$i"] = "$columnName,cs,$search";
$i++;
}
}
}
$request = RequestUtils::setParams($request, $params);
}
return $next->handle($request);
}
}
}

// file: src/Tqdev/PhpCrudApi/Middleware/ValidationMiddleware.php
namespace Tqdev\PhpCrudApi\Middleware {

Expand Down Expand Up @@ -11533,6 +11593,7 @@ private function setHabtmValues(ReflectedTable $t1, ReflectedTable $t2, array &$
use Tqdev\PhpCrudApi\Middleware\Router\SimpleRouter;
use Tqdev\PhpCrudApi\Middleware\SanitationMiddleware;
use Tqdev\PhpCrudApi\Middleware\SslRedirectMiddleware;
use Tqdev\PhpCrudApi\Middleware\TextSearchMiddleware;
use Tqdev\PhpCrudApi\Middleware\ValidationMiddleware;
use Tqdev\PhpCrudApi\Middleware\XmlMiddleware;
use Tqdev\PhpCrudApi\Middleware\XsrfMiddleware;
Expand Down Expand Up @@ -11619,6 +11680,9 @@ public function __construct(Config $config)
case 'customization':
new CustomizationMiddleware($router, $responder, $properties, $reflection);
break;
case 'textSearch':
new TextSearchMiddleware($router, $responder, $properties, $reflection);
break;
case 'xml':
new XmlMiddleware($router, $responder, $properties, $reflection);
break;
Expand Down
66 changes: 65 additions & 1 deletion api.php
Expand Up @@ -3850,6 +3850,11 @@ public function isInteger(): bool
return in_array($this->type, ['integer', 'bigint', 'smallint', 'tinyint']);
}

public function isText(): bool
{
return in_array($this->type, ['varchar', 'clob']);
}

public function setPk($value) /*: void*/
{
$this->pk = $value;
Expand All @@ -3874,7 +3879,7 @@ public function serialize()
{
$json = [
'name' => $this->realName,
'alias' => $this->name!=$this->realName?$this->name:null,
'alias' => $this->name != $this->realName ? $this->name : null,
'type' => $this->type,
'length' => $this->length,
'precision' => $this->precision,
Expand Down Expand Up @@ -9088,6 +9093,61 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}
}

// file: src/Tqdev/PhpCrudApi/Middleware/TextSearchMiddleware.php
namespace Tqdev\PhpCrudApi\Middleware {

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Tqdev\PhpCrudApi\Column\ReflectionService;
use Tqdev\PhpCrudApi\Controller\Responder;
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
use Tqdev\PhpCrudApi\Middleware\Router\Router;
use Tqdev\PhpCrudApi\RequestUtils;

class TextSearchMiddleware extends Middleware
{
private $reflection;

public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
{
parent::__construct($router, $responder, $properties);
$this->reflection = $reflection;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
{
$operation = RequestUtils::getOperation($request);
if ($operation == 'list') {
$tableName = RequestUtils::getPathSegment($request, 2);
$params = RequestUtils::getParams($request);
$parameterName = $this->getProperty('parameter', 'search');
if (isset($params[$parameterName])) {
$search = $params[$parameterName][0];
unset($params[$parameterName]);
$table = $this->reflection->getTable($tableName);
$i = 0;
foreach ($table->getColumnNames() as $columnName) {
$column = $table->getColumn($columnName);
while (isset($params["filter$i"])) {
$i++;
}
if ($i >= 10) {
break;
}
if ($column->isText()) {
$params["filter$i"] = "$columnName,cs,$search";
$i++;
}
}
}
$request = RequestUtils::setParams($request, $params);
}
return $next->handle($request);
}
}
}

// file: src/Tqdev/PhpCrudApi/Middleware/ValidationMiddleware.php
namespace Tqdev\PhpCrudApi\Middleware {

Expand Down Expand Up @@ -11533,6 +11593,7 @@ private function setHabtmValues(ReflectedTable $t1, ReflectedTable $t2, array &$
use Tqdev\PhpCrudApi\Middleware\Router\SimpleRouter;
use Tqdev\PhpCrudApi\Middleware\SanitationMiddleware;
use Tqdev\PhpCrudApi\Middleware\SslRedirectMiddleware;
use Tqdev\PhpCrudApi\Middleware\TextSearchMiddleware;
use Tqdev\PhpCrudApi\Middleware\ValidationMiddleware;
use Tqdev\PhpCrudApi\Middleware\XmlMiddleware;
use Tqdev\PhpCrudApi\Middleware\XsrfMiddleware;
Expand Down Expand Up @@ -11619,6 +11680,9 @@ public function __construct(Config $config)
case 'customization':
new CustomizationMiddleware($router, $responder, $properties, $reflection);
break;
case 'textSearch':
new TextSearchMiddleware($router, $responder, $properties, $reflection);
break;
case 'xml':
new XmlMiddleware($router, $responder, $properties, $reflection);
break;
Expand Down
4 changes: 4 additions & 0 deletions src/Tqdev/PhpCrudApi/Api.php
Expand Up @@ -35,6 +35,7 @@
use Tqdev\PhpCrudApi\Middleware\Router\SimpleRouter;
use Tqdev\PhpCrudApi\Middleware\SanitationMiddleware;
use Tqdev\PhpCrudApi\Middleware\SslRedirectMiddleware;
use Tqdev\PhpCrudApi\Middleware\TextSearchMiddleware;
use Tqdev\PhpCrudApi\Middleware\ValidationMiddleware;
use Tqdev\PhpCrudApi\Middleware\XmlMiddleware;
use Tqdev\PhpCrudApi\Middleware\XsrfMiddleware;
Expand Down Expand Up @@ -121,6 +122,9 @@ public function __construct(Config $config)
case 'customization':
new CustomizationMiddleware($router, $responder, $properties, $reflection);
break;
case 'textSearch':
new TextSearchMiddleware($router, $responder, $properties, $reflection);
break;
case 'xml':
new XmlMiddleware($router, $responder, $properties, $reflection);
break;
Expand Down
7 changes: 6 additions & 1 deletion src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedColumn.php
Expand Up @@ -181,6 +181,11 @@ public function isInteger(): bool
return in_array($this->type, ['integer', 'bigint', 'smallint', 'tinyint']);
}

public function isText(): bool
{
return in_array($this->type, ['varchar', 'clob']);
}

public function setPk($value) /*: void*/
{
$this->pk = $value;
Expand All @@ -205,7 +210,7 @@ public function serialize()
{
$json = [
'name' => $this->realName,
'alias' => $this->name!=$this->realName?$this->name:null,
'alias' => $this->name != $this->realName ? $this->name : null,
'type' => $this->type,
'length' => $this->length,
'precision' => $this->precision,
Expand Down
54 changes: 54 additions & 0 deletions src/Tqdev/PhpCrudApi/Middleware/TextSearchMiddleware.php
@@ -0,0 +1,54 @@
<?php

namespace Tqdev\PhpCrudApi\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Tqdev\PhpCrudApi\Column\ReflectionService;
use Tqdev\PhpCrudApi\Controller\Responder;
use Tqdev\PhpCrudApi\Middleware\Base\Middleware;
use Tqdev\PhpCrudApi\Middleware\Router\Router;
use Tqdev\PhpCrudApi\RequestUtils;

class TextSearchMiddleware extends Middleware
{
private $reflection;

public function __construct(Router $router, Responder $responder, array $properties, ReflectionService $reflection)
{
parent::__construct($router, $responder, $properties);
$this->reflection = $reflection;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $next): ResponseInterface
{
$operation = RequestUtils::getOperation($request);
if ($operation == 'list') {
$tableName = RequestUtils::getPathSegment($request, 2);
$params = RequestUtils::getParams($request);
$parameterName = $this->getProperty('parameter', 'search');
if (isset($params[$parameterName])) {
$search = $params[$parameterName][0];
unset($params[$parameterName]);
$table = $this->reflection->getTable($tableName);
$i = 0;
foreach ($table->getColumnNames() as $columnName) {
$column = $table->getColumn($columnName);
while (isset($params["filter$i"])) {
$i++;
}
if ($i >= 10) {
break;
}
if ($column->isText()) {
$params["filter$i"] = "$columnName,cs,$search";
$i++;
}
}
}
$request = RequestUtils::setParams($request, $params);
}
return $next->handle($request);
}
}
3 changes: 2 additions & 1 deletion tests/config/base.php
Expand Up @@ -8,7 +8,7 @@
'password' => 'incorrect_password',
'mapping' => 'abc_posts.abc_id=posts.id,abc_posts.abc_user_id=posts.user_id,abc_posts.abc_category_id=posts.category_id,abc_posts.abc_content=posts.content',
'controllers' => 'records,columns,cache,openapi,geojson,status',
'middlewares' => 'sslRedirect,xml,cors,json,reconnect,apiKeyAuth,apiKeyDbAuth,dbAuth,jwtAuth,basicAuth,authorization,sanitation,validation,ipAddress,multiTenancy,pageLimits,joinLimits,customization',
'middlewares' => 'sslRedirect,xml,cors,json,reconnect,apiKeyAuth,apiKeyDbAuth,dbAuth,jwtAuth,basicAuth,authorization,sanitation,validation,ipAddress,multiTenancy,pageLimits,joinLimits,textSearch,customization',
'apiKeyAuth.mode' => 'optional',
'apiKeyAuth.keys' => '123456789abc',
'apiKeyDbAuth.mode' => 'optional',
Expand Down Expand Up @@ -57,6 +57,7 @@
'joinLimits.depth' => 2,
'joinLimits.tables' => 4,
'joinLimits.records' => 10,
'textSearch.parameter' => 'q',
'customization.beforeHandler' => function ($operation, $tableName, $request, $environment) {
$environment->start = 0.003/*microtime(true)*/;
},
Expand Down
8 changes: 8 additions & 0 deletions tests/functional/001_records/094_search_posts.log
@@ -0,0 +1,8 @@
===
GET /records/posts?q=Grüßgott
===
200
Content-Type: application/json; charset=utf-8
Content-Length: 209

{"records":[{"id":2,"user_id":2,"category_id":2,"content":"🦀€ Grüßgott, Вiтаю, dobrý deň, hyvää päivää, გამარჯობა, Γεια σας, góðan dag, здравствуйте"}]}

0 comments on commit 34c4f9f

Please sign in to comment.