Commit 7fd53168 authored by François Agneray's avatar François Agneray
Browse files

Merge branch 'develop' into '31-php-doc'

# Conflicts:
#   src/Action/Meta/AttributeAction.php
#   src/Action/Meta/DatasetListAction.php
#   src/Action/Meta/OutputCategoryAction.php
#   src/Action/Search/SearchAction.php
#   src/Utils/Operator/Between.php
#   src/Utils/Operator/IOperatorFactory.php
#   src/Utils/Operator/OperatorFactory.php
parents cb4e61d6 1f238981
......@@ -54,7 +54,7 @@ phpunit:
phpcs:
@docker run --init -it --rm --user $(UID):$(GID) \
-v $(CURDIR):/project \
-w /project jakzal/phpqa phpcs --standard=PSR2 --extensions=php -n --colors src
-w /project jakzal/phpqa phpcs --standard=PSR2 --extensions=php --colors src
init-db:
@docker-compose exec php sh ./conf-dev/metamodel_init.sh
......
......@@ -68,8 +68,12 @@ final class TokenAction
* @param Lcobucci\JWT\Builder $jwtBuilder
* @param Lcobucci\JWT\Signer $jwtSigner
*/
public function __construct(LoggerInterface $logger, EntityManagerInterface $em, JwtBuilder $jwtBuilder, JwtSigner $jwtSigner)
{
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
JwtBuilder $jwtBuilder,
JwtSigner $jwtSigner
) {
$this->logger = $logger;
$this->em = $em;
$this->jwtBuilder = $jwtBuilder;
......
......@@ -96,21 +96,35 @@ final class AttributeAction
}
if ($request->isPut()) {
$this->editAttribute($attribute, $request->getParsedBody());
$parsedBody = $request->getParsedBody();
if (is_null($parsedBody['id_criteria_family'])) {
$criteriaFamily = null;
} else {
$criteriaFamily = $this->em->find('App\Entity\CriteriaFamily', $parsedBody['id_criteria_family']);
}
if (is_null($parsedBody['id_output_category'])) {
$outputCategory = null;
} else {
$outputCategory = $this->em->find('App\Entity\OutputCategory', $parsedBody['id_output_category']);
}
$this->editAttribute($attribute, $parsedBody, $criteriaFamily, $outputCategory);
$this->em->flush();
$newResponse = $response->withJson($attribute);
}
return $newResponse;
}
/**
* Update attribute object with setters
*
* @param Attribute $attribute The attribute to update
* @param string[] $parsedBody Contains the new values ​​of the attribute sent by the user
*/
private function editAttribute(Attribute $attribute, array $parsedBody): void
{
private function editAttribute(
Attribute $attribute,
array $parsedBody,
CriteriaFamily $criteriaFamily = null,
OutputCategory $outputCategory = null
): void {
$attribute->setLabel($parsedBody['label']);
$attribute->setFormLabel($parsedBody['form_label']);
$attribute->setDescription($parsedBody['description']);
......
......@@ -98,7 +98,7 @@ final class FamilyAction
{
if ($family instanceof \App\Entity\DatasetFamily) {
return 'dataset';
} else if ($family instanceof \App\Entity\CriteriaFamily) {
} elseif ($family instanceof \App\Entity\CriteriaFamily) {
return 'criteria';
} else {
return 'output';
......
......@@ -34,8 +34,12 @@ final class TableListAction
*/
private $encryptionKey;
public function __construct(LoggerInterface $logger, EntityManagerInterface $em, DBALConnectionFactory $dcf, string $encryptionKey)
{
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
DBALConnectionFactory $dcf,
string $encryptionKey
) {
$this->logger = $logger;
$this->em = $em;
$this->dcf = $dcf;
......
......@@ -12,10 +12,12 @@ namespace App\Action\Search;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use PDO;
use App\Utils\ActionTrait;
use App\Utils\DBALConnectionFactory;
......@@ -131,8 +133,9 @@ final class SearchAction
$database = $dataset->getProject()->getDatabase();
$decryptedPassword = $this->decryptData($database->getPassword());
$connection = $this->dcf->create($database, $decryptedPassword);
$this->operatorFactory->setDatabasePlatform($connection->getDatabasePlatform());
$queryBuilder = $connection->createQueryBuilder();
$queryBuilder->from($dataset->getTableRef());
try {
......@@ -145,7 +148,7 @@ final class SearchAction
$listOfIds = explode(';', $queryParams['a']);
if ($searchType === 'data') {
$this->select($queryBuilder, $dataset, $listOfIds);
$attributes = $this->select($queryBuilder, $dataset, $listOfIds);
if (array_key_exists('o', $queryParams)) {
$this->order($queryBuilder, $dataset, explode(';', $queryParams['o']));
......@@ -155,10 +158,11 @@ final class SearchAction
$this->limit($queryBuilder, $queryParams['p']);
}
$result = $this->fetchAll($queryBuilder);
} else if ($searchType === 'meta') {
$result = $this->fetchAll($queryBuilder, $attributes);
} elseif ($searchType === 'meta') {
$queryBuilder->select('COUNT(*) as nb');
$count = $this->fetchAll($queryBuilder);
$stmt = $queryBuilder->execute();
$count = $stmt->fetchAll();
$result = array();
$result['dataset_selected'] = $dataset->getLabel();
$attributesSelected = array();
......@@ -182,7 +186,7 @@ final class SearchAction
$this->logger->info('SQL: ' . $queryBuilder->getSQL());
return $response->withJson($result, 200, JSON_NUMERIC_CHECK);
return $response->withJson($result, 200, JSON_NUMERIC_CHECK | JSON_UNESCAPED_SLASHES);
}
/**
......@@ -210,11 +214,15 @@ final class SearchAction
private function select(QueryBuilder $queryBuilder, Dataset $dataset, array $listOfIds): void
{
$columns = array();
$attributes = array();
foreach ($listOfIds as $id) {
$attribute = $this->getAttribute($dataset, (int) $id);
$columns[] = $attribute->getTableName() . '.' . $attribute->getName() . ' as ' . $attribute->getLabel();
$attributes[] = $attribute;
}
$queryBuilder->select($columns);
return $attributes;
}
/**
......@@ -237,7 +245,13 @@ final class SearchAction
} else {
$values = array();
}
$operator = $this->operatorFactory->create($params[1], $queryBuilder->expr(), $column, $columnType, $values);
$operator = $this->operatorFactory->create(
$params[1],
$queryBuilder->expr(),
$column,
$columnType,
$values
);
$expressions[] = $operator->getExpression();
}
$queryBuilder->where(new CompositeExpression(CompositeExpression::TYPE_AND, $expressions));
......@@ -260,7 +274,7 @@ final class SearchAction
$attribute = $this->getAttribute($dataset, (int) $o[0]);
if ($o[1] === 'a') {
$aord = 'ASC';
} else if ($o[1] === 'd') {
} elseif ($o[1] === 'd') {
$aord = 'DESC';
} else {
throw SearchException::typeOfOrderDoesNotExist($o[1]);
......@@ -288,16 +302,30 @@ final class SearchAction
->setMaxResults($limit);
}
/**
* Executes the query an returns the result of the search
*
* @param QueryBuilder $queryBuilder The query to execute
* @return array
*/
private function fetchAll(QueryBuilder $queryBuilder): array
private function fetchAll(QueryBuilder $queryBuilder, array $attributes): array
{
$jsonAttributes = $this->getAttributesOfTypeJson($attributes);
$stmt = $queryBuilder->execute();
return $stmt->fetchAll();
$rows = $stmt->fetchAll();
foreach ($rows as &$row) {
foreach ($row as $key => &$column) {
if (array_search($key, $jsonAttributes)) {
if (!is_null($column)) {
$row[$key] = json_decode($column, true);
}
}
}
}
return $rows;
}
private function getAttributesOfTypeJson(array $attributes): array
{
return array_map(function ($attribute) {
return $attribute->getLabel();
}, array_filter($attributes, function ($attribute) {
return $attribute->getType() === 'json';
}));
}
/**
......
......@@ -19,9 +19,10 @@ class CorsMiddleware
public function __invoke(Request $req, Response $res, $next)
{
if ($req->isOptions()) {
$acah = 'X-Requested-With, Content-Type, Accept, Origin, Authorization';
$response = $res
->withHeader('Access-Control-Allow-Origin', '*')
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization');
->withHeader('Access-Control-Allow-Headers', $acah);
} else {
$response = $res
->withHeader('Access-Control-Allow-Origin', '*')
......
......@@ -65,7 +65,8 @@ trait ActionTrait
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
// Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
$encrypted = openssl_encrypt($data, 'aes-256-cbc', $this->encryptionKey, 0, $iv);
// The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
// The $iv is just as important as the key for decrypting
// So save it with our encrypted data using a unique separator (::)
return base64_encode($encrypted . '::' . $iv);
}
......
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Utils\Operator;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
class JsonPostgres extends Operator
{
private $path;
private $operator;
private $value;
public function __construct(
ExpressionBuilder $expr,
string $column,
string $columnType,
string $path,
string $operator,
string $value
) {
parent::__construct($expr, $column, $columnType);
$this->path = explode(',', $path);
$this->operator = $operator;
$this->value = $value;
}
public function getExpression(): string
{
switch ($this->operator) {
case 'eq':
return $this->expr->eq($this->getColumn(), $this->getValue());
break;
case 'gt':
return $this->expr->gt($this->getColumn(), $this->getValue());
break;
case 'gte':
return $this->expr->gte($this->getColumn(), $this->getValue());
break;
case 'lt':
return $this->expr->lt($this->getColumn(), $this->getValue());
break;
case 'lte':
return $this->expr->lte($this->getColumn(), $this->getValue());
break;
}
}
private function getColumn(): string
{
return $this->column . '->' . implode('->', array_map(array($this, 'getSqlValue'), $this->path));
}
private function getValue()
{
if (is_numeric($this->value)) {
$newValue = 1 * $this->value;
} elseif ($this->value === 'true') {
$newValue = true;
} elseif ($this->value === 'false') {
$newValue = false;
} else {
$newValue = $this->value;
}
return $this->getSqlValue(json_encode($newValue));
}
}
......@@ -69,4 +69,14 @@ class OperatorException extends SearchException
{
return new self("You passed a string value '" . $value . "' or the criterion value required a numeric value");
}
public static function operatorJsonNotSupported()
{
return new self("Operator json is not supported by the database platform used");
}
public static function operatorJsonNeedColumnTypeJson()
{
return new self("Operator js need a json attribute type");
}
}
......@@ -10,6 +10,8 @@
*/
namespace App\Utils\Operator;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Platforms\PostgreSQL94Platform;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
/**
......@@ -22,6 +24,13 @@ use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
*/
class OperatorFactory implements IOperatorFactory
{
private $databasePlatform;
public function setDatabasePlatform(AbstractPlatform $databasePlatform)
{
$this->databasePlatform = $databasePlatform;
}
/**
* Make the right operator and send it back
* Parameters represent values ​​sent by the user
......@@ -35,8 +44,13 @@ class OperatorFactory implements IOperatorFactory
* @throws OperatorException Could not create operator
* @return IOperator
*/
public function create(string $type, ExpressionBuilder $expr, string $column, string $columnType, array $parameters): IOperator
{
public function create(
string $type,
ExpressionBuilder $expr,
string $column,
string $columnType,
array $parameters
): IOperator {
switch ($type) {
case 'bw':
if (count($parameters) != 2) {
......@@ -106,18 +120,30 @@ class OperatorFactory implements IOperatorFactory
case 'nl':
if (count($parameters) != 0) {
throw OperatorException::operatorBadNumberOfParameters($type, 1);
throw OperatorException::operatorBadNumberOfParameters($type, 0);
}
return new OperatorNull($expr, $column, $columnType);
case 'nnl':
if (count($parameters) != 0) {
throw OperatorException::operatorBadNumberOfParameters($type, 1);
throw OperatorException::operatorBadNumberOfParameters($type, 0);
}
return new OperatorNotNull($expr, $column, $columnType);
case 'js':
if (!($this->databasePlatform instanceof PostgreSQL94Platform)) {
throw OperatorException::operatorJsonNotSupported();
}
if ($columnType !== 'json') {
throw OperatorException::operatorJsonNeedColumnTypeJson();
}
if (count($parameters) != 3) {
throw OperatorException::operatorBadNumberOfParameters($type, 3);
}
return new JsonPostgres($expr, $column, $columnType, $parameters[0], $parameters[1], $parameters[2]);
default:
throw OperatorException::unknownOperator($type);
throw OperatorException::unknownOperator();
}
}
}
......@@ -14,7 +14,11 @@ $container = $app->getContainer();
// Anis Error Handler
$container['errorHandler'] = function ($c) {
return new App\Utils\AnisErrorHandler($c->get('logger'), $c->get('mailer'), (bool) $c->get('settings')['displayErrorDetails']);
return new App\Utils\AnisErrorHandler(
$c->get('logger'),
$c->get('mailer'),
(bool) $c->get('settings')['displayErrorDetails']
);
};
// Metamodel database Doctrine 2 Entity Manager
......@@ -136,7 +140,12 @@ $container['App\Action\Meta\DatabaseAction'] = function ($c) {
$container['App\Action\Meta\TableListAction'] = function ($c) {
$settings = $c->get('settings');
return new App\Action\Meta\TableListAction($c->get('logger'), $c->get('em'), $c->get('dcf'), $settings['encryption_key']);
return new App\Action\Meta\TableListAction(
$c->get('logger'),
$c->get('em'),
$c->get('dcf'),
$settings['encryption_key']
);
};
$container['App\Action\Meta\ProjectListAction'] = function ($c) {
......@@ -165,7 +174,12 @@ $container['App\Action\Meta\OutputCategoryAction'] = function ($c) {
$container['App\Action\Meta\DatasetListAction'] = function ($c) {
$settings = $c->get('settings');
return new App\Action\Meta\DatasetListAction($c->get('logger'), $c->get('em'), $c->get('dcf'), $settings['encryption_key']);
return new App\Action\Meta\DatasetListAction(
$c->get('logger'),
$c->get('em'),
$c->get('dcf'),
$settings['encryption_key']
);
};
$container['App\Action\Meta\DatasetAction'] = function ($c) {
......@@ -215,5 +229,11 @@ $container['App\Action\Meta\UserAction'] = function ($c) {
// Search actions
$container['App\Action\Search\SearchAction'] = function ($c) {
$settings = $c->get('settings');
return new App\Action\Search\SearchAction($c->get('logger'), $c->get('em'), $c->get('dcf'), $c->get('of'), $settings['encryption_key']);
return new App\Action\Search\SearchAction(
$c->get('logger'),
$c->get('em'),
$c->get('dcf'),
$c->get('of'),
$settings['encryption_key']
);
};
......@@ -32,7 +32,11 @@ $app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/metadata/project/{name}', App\A
$app->map(['OPTIONS', 'GET', 'POST'], '/metadata/family/{type}', App\Action\Meta\FamilyListAction::class);
$app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/metadata/family/{type}/{id}', App\Action\Meta\FamilyAction::class);
$app->map(['OPTIONS', 'GET', 'POST'], '/metadata/output-category', App\Action\Meta\OutputCategoryListAction::class);
$app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/metadata/output-category/{id}', App\Action\Meta\OutputCategoryAction::class);
$app->map(
['OPTIONS', 'GET', 'PUT', 'DELETE'],
'/metadata/output-category/{id}',
App\Action\Meta\OutputCategoryAction::class
);
$app->map(['OPTIONS', 'GET', 'POST'], '/metadata/dataset', App\Action\Meta\DatasetListAction::class);
$app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/metadata/dataset/{name}', App\Action\Meta\DatasetAction::class);
$app->map(['OPTIONS', 'GET'], '/metadata/dataset/{name}/attribute', App\Action\Meta\AttributeListAction::class);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment