diff --git a/app/Http/Controllers/Api/OAuth2/OAuth2GroupApiController.php b/app/Http/Controllers/Api/OAuth2/OAuth2GroupApiController.php new file mode 100644 index 00000000..e3288265 --- /dev/null +++ b/app/Http/Controllers/Api/OAuth2/OAuth2GroupApiController.php @@ -0,0 +1,79 @@ +repository = $repository; + } + + protected function getAllSerializerType(): string + { + return SerializerRegistry::SerializerType_Public; + } + + /** + * @return array + */ + protected function getFilterRules(): array + { + return [ + 'slug' => ['=='], + ]; + } + + /** + * @return array + */ + protected function getFilterValidatorRules(): array + { + return [ + 'slug' => 'sometimes|required|string', + ]; + } + + public function getOrderRules(): array + { + return [ + 'id', + 'name', + 'slug' + ]; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Traits/GetAllTrait.php b/app/Http/Controllers/Traits/GetAllTrait.php index 35ce80c2..08aad8e3 100644 --- a/app/Http/Controllers/Traits/GetAllTrait.php +++ b/app/Http/Controllers/Traits/GetAllTrait.php @@ -19,7 +19,6 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; use utils\Filter; -use utils\FilterParser; use utils\OrderParser; use utils\PagingInfo; use Exception; diff --git a/app/Http/Controllers/Traits/ParseFilter.php b/app/Http/Controllers/Traits/ParseFilter.php index 3fe21dd1..44a5564b 100644 --- a/app/Http/Controllers/Traits/ParseFilter.php +++ b/app/Http/Controllers/Traits/ParseFilter.php @@ -13,7 +13,7 @@ * limitations under the License. **/ -use Illuminate\Support\Facades\Request; +use App\Http\Utils\Filters\FiltersParams; use utils\Filter; use utils\FilterParser; @@ -34,15 +34,18 @@ public function getFilter(array $filterRules, array $filterValidationRules = []) { $filter = null; - if (Request::has(Filter::FilterRequestParam)) { - $filter = FilterParser::parse(Request::input(Filter::FilterRequestParam), $filterRules); + if (FiltersParams::hasFilterParam()) { + $filter = FilterParser::parse + ( + FiltersParams::getFilterParam(), + $filterRules + ); } - if (is_null($filter)) $filter = new Filter(); + if(is_null($filter)) $filter = new Filter(); - if (count($filterValidationRules)) { + if(count($filterValidationRules)) { $filter->validate($filterValidationRules); - } return $filter; diff --git a/app/Http/Utils/Filters/AbstractFilterElement.php b/app/Http/Utils/Filters/AbstractFilterElement.php index 028a3f62..d89c14d4 100644 --- a/app/Http/Utils/Filters/AbstractFilterElement.php +++ b/app/Http/Utils/Filters/AbstractFilterElement.php @@ -19,6 +19,10 @@ abstract class AbstractFilterElement */ protected $operator; + const OperatorMappings = [ + 'start_like' => 'like' + ]; + /** * @param string $operator */ @@ -28,9 +32,10 @@ protected function __construct($operator) } /** - * @return string + * @return string|array */ public function getOperator(){ - return $this->operator; + if(is_array($this->operator)) return $this->operator; + return isset(self::OperatorMappings[$this->operator]) ? self::OperatorMappings[$this->operator] : $this->operator; } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineCollectionFieldsFilterMapping.php b/app/Http/Utils/Filters/DoctrineCollectionFieldsFilterMapping.php new file mode 100644 index 00000000..53499f2c --- /dev/null +++ b/app/Http/Utils/Filters/DoctrineCollectionFieldsFilterMapping.php @@ -0,0 +1,179 @@ +allowed_collection_fields = $allowed_collection_fields; + $this->joins = $joins; + parent::__construct($table, $alias, ""); + } + + /** + * @param FilterElement $filter + * @param array $bindings + * @return string + */ + public function toRawSQL(FilterElement $filter, array $bindings = []):string + { + throw new \Exception; + } + + /** + * @param string $exp + * @return FilterElement|null + * @throws FilterParserException + */ + private function parseFilter(string $exp):?FilterElement + { + list ($field, $op, $value) = FilterParser::filterExpresion($exp); + if (!key_exists($field, $this->allowed_collection_fields)) + throw new FilterParserException(sprintf("Field %s is not allowed as filter", $field)); + + return FilterParser::buildFilter($this->allowed_collection_fields[$field], $op, $value); + } + + /** + * @param QueryBuilder $query + * @param FilterElement $filter + * @return QueryBuilder + * @throws \models\exceptions\ValidationException + */ + public function apply(QueryBuilder $query, FilterElement $filter):QueryBuilder + { + $value = $filter->getValue(); + + if (is_array($value)) { + + $inner_where = ''; + + foreach ($value as $val) { + + $filterElement = $this->parseFilter($val); + $param_count = $query->getParameters()->count() + 1; + if (!empty($inner_where)) + $inner_where .= sprintf(" %s ", $filter->getSameFieldOp()); + $inner_where .= sprintf("%s %s %s", $filterElement->getField(), $filterElement->getOperator(), ":value_" . $param_count); + $query->setParameter(":value_" . $param_count, $filterElement->getValue()); + } + + if (!in_array($this->alias, $query->getAllAliases())) + $query->innerJoin($this->table, $this->alias, Join::WITH); + + foreach ($this->joins as $join => $join_alias){ + if (!in_array($join_alias, $query->getAllAliases())) + $query->innerJoin(sprintf("%s.%s", $this->alias, $join), $join_alias, Join::WITH); + } + + $inner_where = sprintf("( %s )", $inner_where); + + if($this->main_operator === Filter::MainOperatorAnd) + return $query->andWhere($inner_where); + else + return $query->orWhere($inner_where); + } + + $param_count = $query->getParameters()->count() + 1; + $filterElement = $this->parseFilter($value); + $where = sprintf("%s %s %s", $filterElement->getField(), $filterElement->getOperator(), ":value_" . $param_count); + $query->setParameter(":value_" . $param_count, $filterElement->getValue()); + if (!in_array($this->alias, $query->getAllAliases())) + $query->innerJoin($this->table, $this->alias, Join::WITH); + + foreach ($this->joins as $join => $join_alias){ + if (!in_array($join_alias, $query->getAllAliases())) + $query->innerJoin(sprintf("%s.%s", $this->alias, $join), $join_alias, Join::WITH); + } + + if($this->main_operator === Filter::MainOperatorAnd) + return $query->andWhere($where); + else + return $query->orWhere($where); + } + + /** + * @param QueryBuilder $query + * @param FilterElement $filter + * @return string + */ + public function applyOr(QueryBuilder $query, FilterElement $filter):string + { + $value = $filter->getValue(); + + if (is_array($value)) { + $inner_where = ''; + + foreach ($value as $val) { + $filterElement = $this->parseFilter($val); + $param_count = $query->getParameters()->count() + 1; + if (!empty($inner_where)) + $inner_where .= sprintf(" %s ", $filter->getSameFieldOp()); + $inner_where .= sprintf("%s %s %s", $filterElement->getField(), $filterElement->getOperator(), ":value_" . $param_count); + $query->setParameter(":value_" . $param_count, $filterElement->getValue()); + } + + $inner_where = sprintf("( %s )", $inner_where); + + if (!in_array($this->alias, $query->getAllAliases())) + $query->innerJoin($this->table, $this->alias, Join::WITH); + + foreach ($this->joins as $join => $join_alias){ + if (!in_array($join_alias, $query->getAllAliases())) + $query->innerJoin(sprintf("%s.%s", $this->alias, $join), $join_alias, Join::WITH); + } + + return $inner_where; + } + + $param_count = $query->getParameters()->count() + 1; + $filterElement = $this->parseFilter($value); + $where = sprintf("%s %s %s", $filterElement->getField(), $filterElement->getOperator(), ":value_" . $param_count); + $query->setParameter(":value_" . $param_count, $filterElement->getValue()); + if (!in_array($this->alias, $query->getAllAliases())) + $query->innerJoin($this->table, $this->alias, Join::WITH); + + foreach ($this->joins as $join => $join_alias){ + if (!in_array($join_alias, $query->getAllAliases())) + $query->innerJoin(sprintf("%s.%s", $this->alias, $join), $join_alias, Join::WITH); + } + + return $where; + } + +} \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineFilterMapping.php b/app/Http/Utils/Filters/DoctrineFilterMapping.php index cd7a8f8f..0d7bf556 100644 --- a/app/Http/Utils/Filters/DoctrineFilterMapping.php +++ b/app/Http/Utils/Filters/DoctrineFilterMapping.php @@ -11,29 +11,34 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -use Doctrine\ORM\Query\Expr\Join; + +use App\Http\Utils\Filters\IQueryApplyable; use Doctrine\ORM\QueryBuilder; + /** * Class DoctrineFilterMapping * @package utils */ -class DoctrineFilterMapping extends FilterMapping +class DoctrineFilterMapping extends FilterMapping implements IQueryApplyable { + protected $main_operator; /** * DoctrineFilterMapping constructor. * @param string $condition */ public function __construct($condition) { + $this->main_operator = Filter::MainOperatorAnd; parent::__construct("", $condition); } /** * @param FilterElement $filter + * @param array $bindings * @return string */ - public function toRawSQL(FilterElement $filter) + public function toRawSQL(FilterElement $filter, array $bindings = []):string { throw new \Exception; } @@ -43,22 +48,69 @@ public function toRawSQL(FilterElement $filter) * @param FilterElement $filter * @return QueryBuilder */ - public function apply(QueryBuilder $query, FilterElement $filter){ - $param_count = $query->getParameters()->count() + 1; - $where = $this->where; - $has_param = false; - if(strstr($where,":value")) { - $where = str_replace(":value", ":value_" . $param_count, $where); - $has_param = true; - } + public function apply(QueryBuilder $query, FilterElement $filter):QueryBuilder + { + $value = $filter->getValue(); + if (is_array($value)) { + $inner_where = '( '; - if(strstr($where,":operator")) - $where = str_replace(":operator", $filter->getOperator(), $where); + foreach ($value as $idx => $val) { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; - $query = $query->andWhere($where); + $has_param = false; + if (strstr($where, ":i")) { + $where = str_replace(':i', $param_count, $where); + } - if($has_param){ - $query = $query->setParameter(":value_".$param_count, $filter->getValue()); + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) { + $operator = $filter->getOperator(); + if(is_array($operator)){ + $operator = $operator[$idx]; + } + $where = str_replace(":operator", $operator, $where); + } + + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $val); + } + $inner_where .= $where . " " . $filter->getSameFieldOp() . " "; + } + $inner_where = substr($inner_where, 0, (strlen($filter->getSameFieldOp()) + 1) * -1); + $inner_where .= ' )'; + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andWhere($inner_where); + else + $query = $query->orWhere($inner_where); + } else { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; + $has_param = false; + if (strstr($where, ":i")) { + $where = str_replace(':i', $param_count, $where); + } + + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andWhere($where); + else + $query = $query->orWhere($where); + + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $filter->getValue()); + } } return $query; } @@ -68,23 +120,65 @@ public function apply(QueryBuilder $query, FilterElement $filter){ * @param FilterElement $filter * @return string */ - public function applyOr(QueryBuilder $query, FilterElement $filter){ + public function applyOr(QueryBuilder $query, FilterElement $filter):string + { + $value = $filter->getValue(); + if (is_array($value)) { + + $inner_where = '( '; + + foreach ($value as $val) { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; + $has_param = false; + if (strstr($where, ":i")) { + $where = str_replace(':i', $param_count, $where); + } + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if ($has_param) { + $query->setParameter(":value_" . $param_count, $val); + } + + $inner_where .= $where . " " . $filter->getSameFieldOp() . " "; + } + + $inner_where = substr($inner_where, 0, (strlen($filter->getSameFieldOp()) + 1) * -1); + $inner_where .= ' )'; + + return $inner_where; + } + $param_count = $query->getParameters()->count() + 1; - $where = $this->where; - $has_param = false; + $where = $this->where; + $has_param = false; + + if (strstr($where, ":i")) { + $where = str_replace(':i', $param_count, $where); + } - if(strstr($where,":value")) { + if (strstr($where, ":value")) { $where = str_replace(":value", ":value_" . $param_count, $where); $has_param = true; } - if(strstr($where,":operator")) + if (strstr($where, ":operator")) $where = str_replace(":operator", $filter->getOperator(), $where); - if($has_param){ - $query = $query->setParameter(":value_".$param_count, $filter->getValue()); + if ($has_param) { + $query->setParameter(":value_" . $param_count, $filter->getValue()); } - return $where; } + + public function setMainOperator(string $op): void + { + $this->main_operator = $op; + } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineHavingFilterMapping.php b/app/Http/Utils/Filters/DoctrineHavingFilterMapping.php new file mode 100644 index 00000000..e1b5022f --- /dev/null +++ b/app/Http/Utils/Filters/DoctrineHavingFilterMapping.php @@ -0,0 +1,138 @@ +groupBy = $groupBy; + $this->having = $having; + } + + /** + * @param QueryBuilder $query + * @param FilterElement $filter + * @return QueryBuilder + */ + public function apply(QueryBuilder $query, FilterElement $filter): QueryBuilder + { + + + $param_count = $query->getParameters()->count(); + $where = $this->where; + $has_param = false; + $value = $filter->getValue(); + + + Log::debug + ( + sprintf + ( + "DoctrineHavingFilterMapping::apply where %s groupBy %s having %s value %s", + $this->where, + $this->groupBy, + $this->having, + $value + ) + ); + + if (!is_numeric($value) && is_string($value) && ( trim($value) == '' || empty($value))) + { + Log::debug("DoctrineHavingFilterMapping::apply value is empty"); + return $query; + } + + if (is_array($value) && count($value) == 0) { + Log::debug("DoctrineHavingFilterMapping::apply value is empty"); + return $query; + } + + if (!empty($where)) { + if (strstr($where, ":value")) { + ++$param_count; + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andWhere($where); + else + $query = $query->orWhere($where); + + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $value); + } + } + + if (!empty($this->groupBy)) { + $query = $query->addGroupBy($this->groupBy); + } + + if (!empty($this->having)) { + $has_param = false; + if (strstr($this->having, ":value_count") && is_array($value)) { + $this->having = str_replace(":value_count", count($value), $this->having); + } + + if (strstr($this->having, ":value_count")) { + $this->having = str_replace(":value_count", 1, $this->having); + } + + if (strstr($this->having, ":value")) { + ++$param_count; + $this->having = str_replace(":value", ":value_" . $param_count, $this->having); + $has_param = true; + } + + if (strstr($this->having, ":operator")) + $this->having = str_replace(":operator", $filter->getOperator(), $this->having); + + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $value); + } + + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andHaving($this->having); + else + $query = $query->orHaving($this->having); + } + + return $query; + } +} \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineInFilterMapping.php b/app/Http/Utils/Filters/DoctrineInFilterMapping.php new file mode 100644 index 00000000..d98a8f01 --- /dev/null +++ b/app/Http/Utils/Filters/DoctrineInFilterMapping.php @@ -0,0 +1,88 @@ +main_operator = Filter::MainOperatorAnd; + $this->operator = 'IN'; + parent::__construct("", $condition); + } + + /** + * @param FilterElement $filter + * @param array $bindings + * @return string + */ + public function toRawSQL(FilterElement $filter, array $bindings = []):string + { + throw new \Exception; + } + + private function buildWhere(QueryBuilder $query, FilterElement $filter):string{ + $value = $filter->getValue(); + if (!is_array($value)) { + $value = [$value]; + } + $param_count = $query->getParameters()->count() + 1; + $query->setParameter(":value_" . $param_count, $value); + return sprintf("%s %s ( :value_%s )", $this->where, static::Operator, $param_count); + } + + /** + * @param QueryBuilder $query + * @param FilterElement $filter + * @return QueryBuilder + */ + public function apply(QueryBuilder $query, FilterElement $filter): QueryBuilder + { + if($this->main_operator === Filter::MainOperatorAnd) + return $query->andWhere($this->buildWhere($query, $filter)); + else + return $query->orWhere($this->buildWhere($query, $filter)); + } + + /** + * @param QueryBuilder $query + * @param FilterElement $filter + * @return string + */ + public function applyOr(QueryBuilder $query, FilterElement $filter): string + { + return $this->buildWhere($query, $filter); + } + + public function setMainOperator(string $op): void + { + $this->main_operator = $op; + } +} \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineInstanceOfFilterMapping.php b/app/Http/Utils/Filters/DoctrineInstanceOfFilterMapping.php index af4ef544..2bd4424f 100644 --- a/app/Http/Utils/Filters/DoctrineInstanceOfFilterMapping.php +++ b/app/Http/Utils/Filters/DoctrineInstanceOfFilterMapping.php @@ -11,46 +11,73 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ -use Doctrine\ORM\Query\Expr\Join; + +use App\Http\Utils\Filters\IQueryApplyable; use Doctrine\ORM\QueryBuilder; + /** * Class DoctrineInstanceOfFilterMapping * @package utils */ -final class DoctrineInstanceOfFilterMapping extends FilterMapping +final class DoctrineInstanceOfFilterMapping extends FilterMapping implements IQueryApplyable { + /** + * @var string + */ + protected $main_operator; private $class_names = []; public function __construct($alias, $class_names = []) { + $this->main_operator = Filter::MainOperatorAnd; $this->class_names = $class_names; parent::__construct($alias, sprintf("%s %s :class_name", $alias, self::InstanceOfDoctrine)); } /** * @param FilterElement $filter - * @throws \Exception + * @param array $bindings + * @return string */ - public function toRawSQL(FilterElement $filter) + public function toRawSQL(FilterElement $filter, array $bindings = []):string { throw new \Exception; } const InstanceOfDoctrine = 'INSTANCE OF'; - private function translateClassName($value){ - if(isset($this->class_names[$value])) return $this->class_names[$value]; + private function translateClassName($value) + { + if (isset($this->class_names[$value])) return $this->class_names[$value]; return $value; } + + private function buildWhere(QueryBuilder $query, FilterElement $filter):string{ + $value = $filter->getValue(); + + if (is_array($value)) { + $where_components = []; + // see @https://github.com/doctrine/orm/issues/4462 + foreach ($value as $val) { + $where_components[] = str_replace(":class_name", $this->translateClassName($val), $this->where); + } + return implode(sprintf(" %s ", $filter->getSameFieldOp()), $where_components); + } + return str_replace(":class_name", $this->translateClassName($filter->getValue()), $this->where); + } + /** * @param QueryBuilder $query * @param FilterElement $filter * @return QueryBuilder */ - public function apply(QueryBuilder $query, FilterElement $filter){ - $where = str_replace(":class_name", $this->translateClassName($filter->getValue()), $this->where); - return $query->andWhere($where); + public function apply(QueryBuilder $query, FilterElement $filter): QueryBuilder + { + if($this->main_operator === Filter::MainOperatorAnd) + return $query->andWhere($this->buildWhere($query, $filter)); + else + return $query->orWhere($this->buildWhere($query, $filter)); } /** @@ -58,9 +85,14 @@ public function apply(QueryBuilder $query, FilterElement $filter){ * @param FilterElement $filter * @return string */ - public function applyOr(QueryBuilder $query, FilterElement $filter){ - $where = str_replace(":class_name", $this->translateClassName($filter->getValue()), $this->where); - return $where; + public function applyOr(QueryBuilder $query, FilterElement $filter): string + { + return $this->buildWhere($query, $filter); + } + + public function setMainOperator(string $op): void + { + $this->main_operator = $op; } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineJoinFilterMapping.php b/app/Http/Utils/Filters/DoctrineJoinFilterMapping.php index 6654a45d..53d450a5 100644 --- a/app/Http/Utils/Filters/DoctrineJoinFilterMapping.php +++ b/app/Http/Utils/Filters/DoctrineJoinFilterMapping.php @@ -11,14 +11,22 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ + +use App\Http\Utils\Filters\IQueryApplyable; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; + /** * Class DoctrineJoinFilterMapping * @package utils */ -class DoctrineJoinFilterMapping extends FilterMapping +class DoctrineJoinFilterMapping extends FilterMapping implements IQueryApplyable { + /** + * @var string + */ + protected $main_operator; + /** * @var string */ @@ -33,14 +41,16 @@ class DoctrineJoinFilterMapping extends FilterMapping public function __construct($table, $alias, $where) { parent::__construct($table, $where); + $this->main_operator = Filter::MainOperatorAnd; $this->alias = $alias; } /** * @param FilterElement $filter - * @throws \Exception + * @param array $bindings + * @return string */ - public function toRawSQL(FilterElement $filter) + public function toRawSQL(FilterElement $filter, array $bindings = []):string { throw new \Exception; } @@ -50,26 +60,65 @@ public function toRawSQL(FilterElement $filter) * @param FilterElement $filter * @return QueryBuilder */ - public function apply(QueryBuilder $query, FilterElement $filter){ - $param_count = $query->getParameters()->count() + 1; - $where = $this->where; - $has_param = false; + public function apply(QueryBuilder $query, FilterElement $filter): QueryBuilder + { + $value = $filter->getValue(); - if(strstr($where,":value")) { - $where = str_replace(":value", ":value_" . $param_count, $where); - $has_param = true; - } + if (is_array($value)) { + $inner_where = '( '; - if(strstr($where,":operator")) - $where = str_replace(":operator", $filter->getOperator(), $where); + foreach ($value as $val) { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; + $has_param = false; - if(!in_array($this->alias, $query->getAllAliases())) - $query->innerJoin($this->table, $this->alias, Join::WITH); + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $val); + } + $inner_where .= $where . " " . $filter->getSameFieldOp() . " "; + } + $inner_where = substr($inner_where, 0, (strlen($filter->getSameFieldOp()) + 1) * -1); + $inner_where .= ' )'; + + if (!in_array($this->alias, $query->getAllAliases())) + $query->innerJoin($this->table, $this->alias, Join::WITH); + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andWhere($inner_where); + else + $query = $query->orWhere($inner_where); + + } else { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; + $has_param = false; - $query = $query->andWhere($where); + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } - if($has_param){ - $query = $query->setParameter(":value_".$param_count, $filter->getValue()); + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if (!in_array($this->alias, $query->getAllAliases())) + $query->innerJoin($this->table, $this->alias, Join::WITH); + + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andWhere($where); + else + $query = $query->orWhere($where); + + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $filter->getValue()); + } } return $query; @@ -81,25 +130,65 @@ public function apply(QueryBuilder $query, FilterElement $filter){ * @param FilterElement $filter * @return string */ - public function applyOr(QueryBuilder $query, FilterElement $filter){ + public function applyOr(QueryBuilder $query, FilterElement $filter): string + { + $value = $filter->getValue(); + if (is_array($value)) { + $inner_where = '( '; + + foreach ($value as $val) { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; + $has_param = false; + + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if ($has_param) { + $query->setParameter(":value_" . $param_count, $value); + } + + $inner_where .= $where . " " . $filter->getSameFieldOp() . " "; + } + + $inner_where = substr($inner_where, 0, (strlen($filter->getSameFieldOp()) + 1) * -1); + $inner_where .= ' )'; + + if (!in_array($this->alias, $query->getAllAliases())) + $query->innerJoin($this->table, $this->alias, Join::WITH); + + return $inner_where; + } + $param_count = $query->getParameters()->count() + 1; - $where = $this->where; - $has_param = false; + $where = $this->where; + $has_param = false; - if(strstr($where,":value")) { + if (strstr($where, ":value")) { $where = str_replace(":value", ":value_" . $param_count, $where); $has_param = true; } - if(strstr($where,":operator")) + if (strstr($where, ":operator")) $where = str_replace(":operator", $filter->getOperator(), $where); - if(!in_array($this->alias, $query->getAllAliases())) + if (!in_array($this->alias, $query->getAllAliases())) $query->innerJoin($this->table, $this->alias, Join::WITH); - if($has_param){ - $query->setParameter(":value_".$param_count, $filter->getValue()); + if ($has_param) { + $query->setParameter(":value_" . $param_count, $value); } + return $where; } + + public function setMainOperator(string $op): void + { + $this->main_operator = $op; + } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineLeftJoinFilterMapping.php b/app/Http/Utils/Filters/DoctrineLeftJoinFilterMapping.php index 89982311..42a86126 100644 --- a/app/Http/Utils/Filters/DoctrineLeftJoinFilterMapping.php +++ b/app/Http/Utils/Filters/DoctrineLeftJoinFilterMapping.php @@ -11,8 +11,10 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ + use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; + /** * Class DoctrineLeftJoinFilterMapping * @package utils @@ -24,28 +26,70 @@ class DoctrineLeftJoinFilterMapping extends DoctrineJoinFilterMapping * @param FilterElement $filter * @return QueryBuilder */ - public function apply(QueryBuilder $query, FilterElement $filter){ + public function apply(QueryBuilder $query, FilterElement $filter): QueryBuilder + { + $value = $filter->getValue(); + if (is_array($value)) { + + $inner_where = '( '; + + foreach ($value as $val) { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; + $has_param = false; + + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $val); + } + $inner_where .= $where . " " . $filter->getSameFieldOp() . " "; + } + $inner_where = substr($inner_where, 0, (strlen($filter->getSameFieldOp()) + 1) * -1); + $inner_where .= ' )'; + + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andWhere($inner_where); + else + $query = $query->orWhere($inner_where); + + if (!in_array($this->alias, $query->getAllAliases())) + $query->leftJoin($this->table, $this->alias, Join::WITH); + + return $query; + + } + $param_count = $query->getParameters()->count() + 1; - $where = $this->where; - $has_param = false; + $where = $this->where; + $has_param = false; - if(strstr($where,":value")) { + if (strstr($where, ":value")) { $where = str_replace(":value", ":value_" . $param_count, $where); $has_param = true; } - if(strstr($where,":operator")) + if (strstr($where, ":operator")) $where = str_replace(":operator", $filter->getOperator(), $where); - if(!in_array($this->alias, $query->getAllAliases())) - $query->leftJoin($this->table, $this->alias, Join::WITH); - - $query = $query->andWhere($where); + if($this->main_operator === Filter::MainOperatorAnd) + $query = $query->andWhere($where); + else + $query = $query->orWhere($where); - if($has_param){ - $query = $query->setParameter(":value_".$param_count, $filter->getValue()); + if ($has_param) { + $query = $query->setParameter(":value_" . $param_count, $value); } + if (!in_array($this->alias, $query->getAllAliases())) + $query->leftJoin($this->table, $this->alias, Join::WITH); + return $query; } @@ -54,28 +98,60 @@ public function apply(QueryBuilder $query, FilterElement $filter){ * @param FilterElement $filter * @return string */ - public function applyOr(QueryBuilder $query, FilterElement $filter){ + public function applyOr(QueryBuilder $query, FilterElement $filter): string + { + $value = $filter->getValue(); + if (is_array($value)) { + $inner_where = '( '; + + foreach ($value as $val) { + $param_count = $query->getParameters()->count() + 1; + $where = $this->where; + $has_param = false; + + if (strstr($where, ":value")) { + $where = str_replace(":value", ":value_" . $param_count, $where); + $has_param = true; + } + + if (strstr($where, ":operator")) + $where = str_replace(":operator", $filter->getOperator(), $where); + + if ($has_param) { + $query->setParameter(":value_" . $param_count, $value); + } + + $inner_where .= $where . " " . $filter->getSameFieldOp() . " "; + } + + $inner_where = substr($inner_where, 0, (strlen($filter->getSameFieldOp()) + 1) * -1); + $inner_where .= ' )'; + + if (!in_array($this->alias, $query->getAllAliases())) + $query->leftJoin($this->table, $this->alias, Join::WITH); + + return $inner_where; + } + $param_count = $query->getParameters()->count() + 1; - $where = $this->where; - $has_param = false; + $where = $this->where; + $has_param = false; - if(strstr($where,":value")) { + if (strstr($where, ":value")) { $where = str_replace(":value", ":value_" . $param_count, $where); $has_param = true; } - if(strstr($where,":operator")) + if (strstr($where, ":operator")) $where = str_replace(":operator", $filter->getOperator(), $where); - if(!in_array($this->alias, $query->getAllAliases())) - $query->leftJoin($this->table, $this->alias, Join::WITH); + if ($has_param) { + $query->setParameter(":value_" . $param_count, $value); + } - if(!in_array($this->alias, $query->getAllAliases())) + if (!in_array($this->alias, $query->getAllAliases())) $query->leftJoin($this->table, $this->alias, Join::WITH); - if($has_param){ - $query->setParameter(":value_".$param_count, $filter->getValue()); - } return $where; } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/DoctrineNotInFilterMapping.php b/app/Http/Utils/Filters/DoctrineNotInFilterMapping.php new file mode 100644 index 00000000..172ed86e --- /dev/null +++ b/app/Http/Utils/Filters/DoctrineNotInFilterMapping.php @@ -0,0 +1,22 @@ +case_statements = $case_statements; + $this->main_operator = Filter::MainOperatorAnd; } /** * @param FilterElement $filter + * @param array $bindings * @return string */ - public function toRawSQL(FilterElement $filter) + public function toRawSQL(FilterElement $filter, array $bindings = []):string { throw new \Exception; } + /** * @param QueryBuilder $query * @param FilterElement $filter * @return QueryBuilder */ - public function apply(QueryBuilder $query, FilterElement $filter){ - if(!isset($this->case_statements[$filter->getValue()])) return $query; - $case_statement = $this->case_statements[$filter->getValue()]; - return $query->andWhere($case_statement->getCondition()); + public function apply(QueryBuilder $query, FilterElement $filter): QueryBuilder + { + $value = $filter->getValue(); + if(!is_array($value)) $value = [$value]; + $condition = ''; + foreach ($value as $v) { + if (!isset($this->case_statements[$v])) continue; + $case_statement = $this->case_statements[$v]; + if(!empty($condition)) $condition .= ' OR '; + $condition .= ' ( '.$case_statement->getCondition().' ) '; + } + if(!empty($condition)) + $condition = ' ( '.$condition.' ) '; + if($this->main_operator === Filter::MainOperatorAnd) + return $query->andWhere($condition); + else + return $query->orWhere($condition); } /** @@ -55,9 +78,22 @@ public function apply(QueryBuilder $query, FilterElement $filter){ * @param FilterElement $filter * @return string */ - public function applyOr(QueryBuilder $query, FilterElement $filter){ - if(!isset($this->case_statements[$filter->getValue()])) return $query; - $case_statement = $this->case_statements[$filter->getValue()]; - return $case_statement->getCondition(); + public function applyOr(QueryBuilder $query, FilterElement $filter): string + { + $value = $filter->getValue(); + if(!is_array($value)) $value = [$value]; + $condition = ''; + foreach ($value as $v) { + if (!isset($this->case_statements[$filter->getValue()])) continue; + $case_statement = $this->case_statements[$v]; + if(!empty($condition)) $condition .= ' OR '; + $condition .= ' ( '.$case_statement->getCondition().' ) '; + } + return $condition; + } + + public function setMainOperator(string $op): void + { + $this->main_operator = $op; } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/Filter.php b/app/Http/Utils/Filters/Filter.php index 9fb61bc0..bb285797 100644 --- a/app/Http/Utils/Filters/Filter.php +++ b/app/Http/Utils/Filters/Filter.php @@ -1,4 +1,5 @@ filters = $filters; + $this->originalExp = $originalExp; + $this->ops = $ops; } /** - * @param FilterElement $filter + * @param FilterElement|array $filter + * @param string $op * @return $this */ - public function addFilterCondition(FilterElement $filter) + public function addFilterCondition($filter, string $op = Filter::MainOperatorAnd) { $this->filters[] = $filter; + $this->ops[] = $op; return $this; } @@ -62,8 +114,7 @@ public function getFilter($field) if ($filter instanceof FilterElement && $filter->getField() === $field) { $res[] = $filter; - } - else if (is_array($filter)) { + } else if (is_array($filter)) { // OR $or_res = []; foreach ($filter as $e) { @@ -71,7 +122,9 @@ public function getFilter($field) $or_res[] = $e; } } - if (count($or_res)) $res[] = $or_res; + foreach ($or_res as $e) { + $res[] = $e; + } } } return $res; @@ -81,17 +134,18 @@ public function getFilter($field) * @param string $field * @return null|FilterElement */ - public function getUniqueFilter($field){ + public function getUniqueFilter($field) + { $res = $this->getFilter($field); - return count($res) == 1 ? $res[0]:null; + return count($res) == 1 ? $res[0] : null; } - /** * @param string $field * @return bool */ - public function hasFilter($field){ + public function hasFilter($field) + { return count($this->getFilter($field)) > 0; } @@ -106,8 +160,7 @@ public function getFlatFilter($field) if ($filter instanceof FilterElement && $filter->getField() === $field) { $res[] = $filter; - } - else if (is_array($filter)) { + } else if (is_array($filter)) { // OR foreach ($filter as $e) { if ($e instanceof FilterElement && $e->getField() === $field) { @@ -123,18 +176,24 @@ public function getFlatFilter($field) /** * @return array */ - public function getFiltersKeyValues(){ + public function getFiltersKeyValues() + { $res = []; foreach ($this->filters as $filter) { if ($filter instanceof FilterElement) { $res[$filter->getField()] = $filter->getValue(); - } - else if (is_array($filter)) { + } else if (is_array($filter)) { // OR foreach ($filter as $e) { if ($e instanceof FilterElement) { - if(!isset($res[$e->getField()])) $res[$e->getField()] = []; + if (!isset($res[$e->getField()])){ + $res[$e->getField()] = []; + } + $former_filter_value = $res[$e->getField()]; + if(!is_array($former_filter_value)){ + $res[$e->getField()] = [$former_filter_value]; + } $res[$e->getField()][] = $e->getValue(); } } @@ -148,75 +207,160 @@ public function getFiltersKeyValues(){ * @param array $messages * @throws ValidationException */ - public function validate(array $rules, array $messages = []){ + public function validate(array $rules, array $messages = []) + { $filter_key_values = $this->getFiltersKeyValues(); - foreach($rules as $field => $rule) { - if(!isset($filter_key_values[$field])) continue; + foreach ($rules as $field => $rule) { + if (!isset($filter_key_values[$field])) { + continue; + } $values = $filter_key_values[$field]; - if(!is_array($values)) $values = [$values]; + if (!is_array($values)) $values = [$values]; foreach ($values as $val) { - $validation = Validator::make - ( - [$field => $val], - [$field => $rule], - $messages - ); - if ($validation->fails()) { - $ex = new ValidationException(); - throw $ex->setMessages($validation->messages()->toArray()); + if (is_array($val)) { + foreach ($val as $sub_val) { + self::_validate($field, $sub_val, $rule, $messages); + } + } else { + self::_validate($field, $val, $rule, $messages); } } } } + private static function _validate($field, $val, $rule, $messages) + { + $validation = Validator::make + ( + [$field => $val], + [$field => $rule], + $messages + ); + if ($validation->fails()) { + $ex = new ValidationException(); + throw $ex->setMessages($validation->messages()->toArray()); + } + } + /** - * @param Criteria $criteria - * @param array $mappings - * @return Criteria + * @param FilterElement $filter + * @param string $mapping + * @param int $param_idx + * @return string */ - public function apply2Criteria(Criteria $criteria, array $mappings) + private function applyCondition(FilterElement $filter, string $mapping, int &$param_idx): string { - foreach ($this->filters as $filter) { - if ($filter instanceof FilterElement) { - if (isset($mappings[$filter->getField()])) { - $mapping = $mappings[$filter->getField()]; + $mapping_parts = explode(':', $mapping); + $value = $filter->getValue(); + $op = $filter->getOperator(); + $sameOp = $filter->getSameFieldOp(); + Log::debug(sprintf("Filter::applyCondition mapping %s op %s value %s", $mapping_parts[0], json_encode($op), json_encode($value))); + if (count($mapping_parts) > 1) { + $filter->setValue($this->convertValue($filter->getRawValue(), $mapping_parts[1])); + $value = $filter->getValue(); + Log::debug(sprintf("Filter::applyCondition converted converted value %s", json_encode($value))); + } - if ($mapping instanceof FilterMapping) { - continue; - } + if (is_array($value)) { // multiple values + $inner_condition = '( '; + foreach ($value as $idx => $val) { + $cond = is_array($op) ? $op[$idx] : $op; + $inner_condition .= sprintf("%s %s :%s %s ", self::cleanMapping($mapping_parts[0]), $cond, sprintf(self::ParamPrefix, $param_idx), $sameOp); + $this->bindings[sprintf(self::ParamPrefix, $param_idx)] = $val; + ++$param_idx; + } + $inner_condition = substr($inner_condition, 0, (strlen($sameOp) + 1) * -1); + $inner_condition .= ' )'; + } else { + $inner_condition = sprintf("%s %s :%s ", self::cleanMapping($mapping_parts[0]), $op, sprintf(self::ParamPrefix, $param_idx)); + $this->bindings[sprintf(self::ParamPrefix, $param_idx)] = $value; + ++$param_idx; + } - $mapping = explode(':', $mapping); - $value = $filter->getValue(); + return $inner_condition; + } - if (count($mapping) > 1) { - $value = $this->convertValue($value, $mapping[1]); + /** + * @param array $mappings + * @return string + */ + public function toRawSQL(array $mappings, int $param_idx = 1) + { + $sql = ''; + $this->bindings = []; + + foreach ($this->filters as $idx => $filter) { + if ($filter instanceof FilterElement && isset($mappings[$filter->getField()])) { + $condition = ''; + $mapping = $mappings[$filter->getField()]; + if ($mapping instanceof FilterMapping) { + $condition = $mapping->toRawSQL($filter, $this->bindings); + $local_bindings = $mapping->getBindings(); + if(count($local_bindings) > 0 ){ + $this->bindings = array_merge($this->bindings, $local_bindings); + $param_idx = count($this->bindings) + 1; } - $criteria->andWhere(Criteria::expr()->eq($mapping[0], $value)); } - } else if (is_array($filter)) { - // OR + else if (is_array($mapping)) { + foreach ($mapping as $mapping_or) { + if (!empty($condition)) $condition .= ' OR '; + $condition .= $this->applyCondition($filter, $mapping_or, $param_idx); + } + } else { + $condition = $this->applyCondition($filter, $mapping, $param_idx); + } + if (!empty($sql) && !empty($condition)) + $sql .= sprintf(" %s ", $this->getMainOp($idx)); + if(!empty($condition)) $sql .= '('. $condition. ')'; + } else if (is_array($filter)) { + // an array is a OR + $condition = ''; foreach ($filter as $e) { if ($e instanceof FilterElement && isset($mappings[$e->getField()])) { $mapping = $mappings[$e->getField()]; if ($mapping instanceof FilterMapping) { - continue; + $condition = $mapping->toRawSQL($e, $this->bindings); + $local_bindings = $mapping->getBindings(); + if(count($local_bindings) > 0 ){ + $this->bindings = array_merge($this->bindings, $local_bindings); + $param_idx = count($this->bindings) + 1; + } } - $mapping = explode(':', $mapping); - $value = $filter->getValue(); - if (count($mapping) > 1) { - $value = $this->convertValue($value, $mapping[1]); + else if (is_array($mapping)) { + foreach ($mapping as $mapping_or) { + if (!empty($condition)) $condition .= ' OR '; + $condition .= $this->applyCondition($e, $mapping_or, $param_idx); + } + } else { + if (!empty($condition)) $condition .= ' OR '; + $condition .= $this->applyCondition($e, $mapping, $param_idx);; } - $criteria->orWhere(Criteria::expr()->eq($mapping[0], $value)); - } } + if (!empty($sql) && !empty($condition)) $sql .= sprintf(" %s ", $this->getMainOp($idx)); + if(!empty($condition)) $sql .= '('. $condition. ')'; } } - return $criteria; + + $sql = trim($sql); + + Log::debug(sprintf("Filter::toRawSQL SQL %s", $sql)); + + return $sql; } + /** + * @param int $idx + * @return string + */ + private function getMainOp(int $idx):string{ + Log::debug(sprintf("Filter::getMainOp idx %s ops %s", $idx, json_encode($this->ops))); + if(count($this->ops) == 0) return Filter::MainOperatorAnd; + if((count($this->ops) - 1) < $idx) return Filter::MainOperatorAnd; + return $this->ops[$idx]; + } /** * @param QueryBuilder $query * @param array $mappings @@ -224,150 +368,164 @@ public function apply2Criteria(Criteria $criteria, array $mappings) */ public function apply2Query(QueryBuilder $query, array $mappings) { - $param_prefix = "param_%s"; - $param_idx = 1; - $bindings = []; - foreach ($this->filters as $filter) { + $param_idx = 1; + $this->bindings = []; + + foreach ($this->filters as $idx => $filter) { + Log::debug(sprintf("Filter::apply2Query idx %s filter %s", $idx, json_encode($filter))); if ($filter instanceof FilterElement && isset($mappings[$filter->getField()])) { + // single filter element $mapping = $mappings[$filter->getField()]; - - if ($mapping instanceof DoctrineJoinFilterMapping) { - $query = $mapping->apply($query, $filter); - continue; - } - if ($mapping instanceof DoctrineSwitchFilterMapping) { - $query = $mapping->apply($query, $filter); - continue; - } - if ($mapping instanceof DoctrineFilterMapping) { - $query = $mapping->apply($query, $filter); - continue; - } - if ($mapping instanceof DoctrineInstanceOfFilterMapping) { + Log::debug(sprintf("Filter::apply2Query single filter idx %s field %s mapping %s", $idx, $filter->getField(), json_encode($mapping))); + if ($mapping instanceof IQueryApplyable) { + Log::debug(sprintf("Filter::apply2Query single filter idx %s field %s mapping is IQueryApplyable", $idx, $filter->getField())); + $mapping->setMainOperator($this->getMainOp($idx)); $query = $mapping->apply($query, $filter); - continue; - } - else if(is_array($mapping)){ + } else if (is_array($mapping)) { + Log::debug(sprintf("Filter::apply2Query single filter idx %s field %s mapping is array", $idx, $filter->getField())); $condition = ''; - foreach ($mapping as $mapping_or){ - $mapping_or = explode(':', $mapping_or); - $value = $filter->getValue(); - if (count($mapping_or) > 1) { - $value = $this->convertValue($value, $mapping_or[1]); - } - - if(!empty($condition)) $condition .= ' OR '; - $bindings[sprintf($param_prefix, $param_idx)] = $value; - $condition .= sprintf("%s %s :%s", $mapping_or[0], $filter->getOperator(), sprintf($param_prefix, $param_idx)); - ++$param_idx; - } - $query->andWhere($condition); - } - else { - $mapping = explode(':', $mapping); - $value = $filter->getValue(); - - if (count($mapping) > 1) { - $value = $this->convertValue($value, $mapping[1]); + // OR Criteria + foreach ($mapping as $mapping_or) { + if (!empty($condition)) $condition .= ' OR '; + $condition .= $this->applyCondition($filter, $mapping_or, $param_idx); } - $bindings[sprintf($param_prefix, $param_idx)] = $value; - $query = $query->andWhere(sprintf("%s %s :%s", $mapping[0], $filter->getOperator(), sprintf($param_prefix, $param_idx))); - ++$param_idx; + if($this->getMainOp($idx) == Filter::MainOperatorAnd) + $query = $query->andWhere($condition); + else + $query = $query->orWhere($condition); + } else { + Log::debug(sprintf("Filter::apply2Query single filter idx %s field %s mapping is raw", $idx, $filter->getField())); + $condition = $this->applyCondition($filter, $mapping, $param_idx); + if($this->getMainOp($idx) == Filter::MainOperatorAnd) + $query = $query->andWhere($condition); + else + $query = $query->orWhere($condition); } - } - else if (is_array($filter)) { + } else if (is_array($filter)) { + Log::debug(sprintf("Filter::apply2Query single filter idx %s mapping is OR", $idx)); // OR $sub_or_query = ''; foreach ($filter as $e) { if ($e instanceof FilterElement && isset($mappings[$e->getField()])) { $mapping = $mappings[$e->getField()]; - if ($mapping instanceof DoctrineJoinFilterMapping) { - $condition = $mapping->applyOr($query, $e); - if(!empty($sub_or_query)) $sub_or_query .= ' OR '; - $sub_or_query .= $condition; - continue; - } - if ($mapping instanceof DoctrineSwitchFilterMapping) { + if ($mapping instanceof IQueryApplyable) { + Log::debug(sprintf("Filter::apply2Query single filter idx %s field %s mapping is OR", $idx, $e->getField())); + $mapping->setMainOperator($this->getMainOp($idx)); $condition = $mapping->applyOr($query, $e); - if(!empty($sub_or_query)) $sub_or_query .= ' OR '; + if (!empty($sub_or_query)) $sub_or_query .= ' OR '; $sub_or_query .= $condition; - continue; - } - if ($mapping instanceof DoctrineFilterMapping) { - $condition = $mapping->applyOr($query, $e); - if(!empty($sub_or_query)) $sub_or_query .= ' OR '; - $sub_or_query .= $condition; - continue; - } - if ($mapping instanceof DoctrineInstanceOfFilterMapping) { - $condition = $mapping->applyOr($query, $e); - if(!empty($sub_or_query)) $sub_or_query .= ' OR '; - $sub_or_query .= $condition; - continue; - } - else if(is_array($mapping)){ + } else if (is_array($mapping)) { $condition = ''; - foreach ($mapping as $mapping_or){ - $mapping_or = explode(':', $mapping_or); - $value = $e->getValue(); - if (count($mapping_or) > 1) { - $value = $this->convertValue($value, $mapping_or[1]); - } - - if(!empty($condition)) $condition .= ' OR '; - $bindings[sprintf($param_prefix, $param_idx)] = $value; - $condition .= sprintf(" %s %s :%s ", $mapping_or[0], $e->getOperator(), sprintf($param_prefix, $param_idx)); - ++$param_idx; - } - if(!empty($sub_or_query)) $sub_or_query .= ' OR '; - $sub_or_query .= ' ( '.$condition.' ) '; - } - else { - $mapping = explode(':', $mapping); - $value = $e->getValue(); - - if (count($mapping) > 1) { - $value = $this->convertValue($value, $mapping[1]); + foreach ($mapping as $mapping_or) { + if (!empty($condition)) $condition .= ' OR '; + $condition .= $this->applyCondition($e, $mapping_or, $param_idx); } + if (!empty($sub_or_query)) $sub_or_query .= ' OR '; + $sub_or_query .= ' ( ' . $condition . ' ) '; + } else { - if(!empty($sub_or_query)) $sub_or_query .= ' OR '; - - $bindings[sprintf($param_prefix, $param_idx)] = $value; - $sub_or_query .= sprintf(" %s %s :%s ", $mapping[0], $e->getOperator(), sprintf($param_prefix, $param_idx)); - ++$param_idx; + if (!empty($sub_or_query)) $sub_or_query .= ' OR '; + $sub_or_query .= $this->applyCondition($e, $mapping, $param_idx); } } } - $query->andWhere($sub_or_query); + + if($this->getMainOp($idx) == Filter::MainOperatorAnd) + $query = $query->andWhere($sub_or_query); + else + $query = $query->orWhere($sub_or_query); } } - foreach($bindings as $param => $value) + + foreach ($this->bindings as $param => $value) $query->setParameter($param, $value); + + Log::debug(sprintf("Filter::apply2Query DQL %s", $query->getDQL())); return $this; } /** - * @param string $value + * @param $value + * @param string|null $strTimeZone + * @return array|string + * @throws \Exception + */ + public static function convertToDateTime($value, ?string $strTimeZone = null) + { + $timezone = null; + + if (!empty($strTimeZone)) { + $timezone = new \DateTimeZone($strTimeZone); + } + + if (is_array($value)) { + $res = []; + foreach ($value as $val) { + $datetime = new \DateTime("@$val", $timezone); + if (!is_null($timezone)) + $datetime = $datetime->setTimezone($timezone); + $res[] = $datetime->format("Y-m-d H:i:s"); + } + return $res; + } + // single value + $datetime = new \DateTime("@$value"); + Log::debug(sprintf("Filter::convertToDateTime original date value %s", $datetime->format("Y-m-d H:i:s"))); + if (!is_null($timezone)) + $datetime = $datetime->setTimezone($timezone); + Log::debug(sprintf("Filter::convertToDateTime final date %s", $datetime->format("Y-m-d H:i:s"))); + return $datetime->format("Y-m-d H:i:s"); + } + + /** + * @param $value * @param string $original_format - * @return mixed + * @return array|int|mixed|string + * @throws \Exception */ - private function convertValue(string $value, string $original_format) + private function convertValue($value, string $original_format) { - switch ($original_format) { - case 'datetime_epoch': - $datetime = new \DateTime("@$value"); - return sprintf("%s", $datetime->format("Y-m-d H:i:s")); + $original_format_parts = explode('|', $original_format); + switch ($original_format_parts[0]) { + case self::Email: + if (is_array($value)) { + $res = []; + foreach ($value as $val) { + $res[] = sprintf("%s", PunnyCodeHelper::encodeEmail($val)); + } + return $res; + } + return sprintf("%s", PunnyCodeHelper::encodeEmail($value)); + case self::DateTimeEpoch: + Log::debug(sprintf("Filter::convertValue datetime_epoch %s", $original_format)); + $strTimeZone = count($original_format_parts) > 1 ? $original_format_parts[1] : null; + return self::convertToDateTime($value, $strTimeZone); + break; + case self::Boolean: + return to_boolean($value) ? 1 : 0; break; - case 'json_int': + case self::Int: + if (is_array($value)) { + $res = []; + foreach ($value as $val) { + $res[] = intval($val); + } + return $res; + } return intval($value); break; - case 'json_string': + case self::String: + if (is_array($value)) { + $res = []; + foreach ($value as $val) { + $res[] = sprintf("%s", $val); + } + return $res; + } return sprintf("%s", $value); break; - case 'json_email': - return PunnyCodeHelper::encodeEmail($value); default: return $value; break; @@ -383,76 +541,146 @@ public function getSQLBindings() } /** - * @param array $mappings - * @return string + * @param string $field + * @return array */ - public function toRawSQL(array $mappings) + public function getFilterCollectionByField($field) { - $sql = ''; - $this->bindings = []; - $param_prefix = "param_%s"; - $param_idx = 1; + $list = []; + $filter = $this->getFilter($field); + + if (is_array($filter)) { + if (is_array($filter[0])) { + foreach ($filter[0] as $filter_element) + $list[] = intval($filter_element->getValue()); + } else { + $list[] = intval($filter[0]->getValue()); + } + } + return $list; + } + public function __toString(): string + { + $res = ""; foreach ($this->filters as $filter) { + if ($filter instanceof FilterElement) { - if (isset($mappings[$filter->getField()])) { - - $mapping = $mappings[$filter->getField()]; - $mapping = explode(':', $mapping); - $value = $filter->getValue(); - $op = $filter->getOperator(); - if (count($mapping) > 1) { - $filter->setValue($this->convertValue($value, $mapping[1])); - } - $cond = sprintf(' %s %s :%s', $mapping[0], $op, sprintf($param_prefix, $param_idx)); - $this->bindings[sprintf($param_prefix, $param_idx)] = $filter->getValue(); - ++$param_idx; - if (!empty($sql)) $sql .= " AND "; - $sql .= $cond; - } + $res .= '(' . $filter . ')'; } else if (is_array($filter)) { // OR - $sql .= " ( "; - $sql_or = ''; + $or_res = []; foreach ($filter as $e) { - if ($e instanceof FilterElement && isset($mappings[$e->getField()])) { - $mapping = $mappings[$e->getField()]; - $mapping = explode(':', $mapping); - $value = $e->getValue(); - $op = $e->getOperator(); - if (count($mapping) > 1) { - $e->setValue($this->convertValue($value, $mapping[1])); - } - $cond = sprintf(" %s %s :%s", $mapping[0], $op, sprintf($param_prefix, $param_idx)); - $this->bindings[sprintf($param_prefix, $param_idx)] = $e->getValue(); - ++$param_idx; - if (!empty($sql_or)) $sql_or .= " OR "; - $sql_or .= $cond; + if ($e instanceof FilterElement) { + $or_res[] = '(' . $e . ')'; } } - $sql .= $sql_or . " ) "; + if (count($or_res)) $res = '(' . implode("|", $or_res) . ')'; } } - return $sql; + return $res; } /** * @param string $field - * @return array + * @param string $type + * @return string */ - public function getFilterCollectionByField($field){ - $list = []; - $filter = $this->getFilter($field); + private static function buildField(string $field, string $type): string + { + return sprintf("%s:%s", $field, $type); + } - if(is_array($filter)){ - if(is_array($filter[0])){ - foreach ($filter[0] as $filter_element) - $list[] = intval($filter_element->getValue()); - } - else{ - $list[] = intval($filter[0]->getValue()); + /** + * @param string $field + * @return string + */ + public static function buildStringField(string $field): string + { + return self::buildField($field, self::String); + } + + /** + * @param string $field + * @return string + */ + public static function buildIntField(string $field): string + { + return self::buildField($field, self::Int); + } + + /** + * @param string $field + * @return string + */ + public static function buildBooleanField(string $field): string + { + return self::buildField($field, self::Boolean); + } + + /** + * @param string $field + * @return string + */ + public static function buildDateTimeEpochField(string $field): string + { + return self::buildField($field, self::DateTimeEpoch); + } + + /** + * @param string $field + * @return string + */ + public static function buildEmailField(string $field): string + { + return self::buildField($field, self::Email); + } + + public static function buildLowerCaseStringField(string $field): string + { + return sprintf("LOWER(%s)",$field); + } + + public static function buildConcatStringFields(array $fields): string + { + $res = []; + foreach ($fields as $field) { + $res[] = sprintf("LOWER(%s)", $field); + } + return sprintf("CONCAT(%s)", implode(",' ',",$res)); + } + /** + * @return mixed|null + */ + public function getOriginalExp() + { + return $this->originalExp; + } + + /** + * @param string $filter_name + * @return array + */ + public function getValue(string $filter_name): array { + $e = $this->getFilter($filter_name); + $v = []; + foreach($e as $f){ + if(is_array($f->getValue())){ + foreach ($f->getValue() as $iv){ + $v[] = $iv; + } } + else + $v[] = $f->getValue(); } - return $list; + return $v; + } + + public static function buildEpochDefaultOperators():array{ + return ['>', '<', '<=', '>=', '==','[]']; + } + + public static function buildStringDefaultOperators():array{ + return ['=@', '@@', '==']; } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/FilterElement.php b/app/Http/Utils/Filters/FilterElement.php index bb9a0f8a..c66ec93b 100644 --- a/app/Http/Utils/Filters/FilterElement.php +++ b/app/Http/Utils/Filters/FilterElement.php @@ -1,5 +1,4 @@ '']; + /** * @var mixed */ @@ -23,16 +24,23 @@ class FilterElement extends AbstractFilterElement */ private $field; + /** + * @var string + */ + private $same_field_op; + /** * @param $field * @param $value * @param $operator + * @param $same_field_op */ - protected function __construct($field, $value, $operator) + protected function __construct($field, $value, $operator, $same_field_op) { parent::__construct($operator); $this->field = $field; $this->value = $value; + $this->same_field_op = $same_field_op; } /** @@ -53,54 +61,128 @@ public function getField() return $this->field; } + public function getRawValue(){ + return $this->value; + } + + public function getBooleanValue(){ + if(is_array($this->value)){ + $res = []; + foreach ($this->value as $val){ + $res[]= empty($val) ? '' : to_boolean($val); + } + return $res; + } + return to_boolean($this->value); + } + + public static function mapValueSymbols(string $val):string{ + if(isset(self::$symbols[strtoupper($val)])) return self::$symbols[strtoupper($val)]; + return $val; + } + /** - * @return string + * @return mixed */ public function getValue() { + $value = $this->value; + if(is_array($value)){ + $res = []; + foreach ($value as $val){ + $res[]= empty($val) ? '' : self::mapValueSymbols($val); + } + $value = $res; + } + else { + $value = self::mapValueSymbols($value); + } + switch($this->operator) { case 'like': - return empty($this->value) ? '' : "%".$this->value."%"; - break; + if(is_array($value)){ + $res = []; + foreach ($value as $val){ + $res[]= empty($val) ? '' : "%".$val."%"; + } + return $res; + } + return empty($value) ? '' : "%".$value."%"; + case 'start_like': + if(is_array($value)){ + $res = []; + foreach ($value as $val){ + $res[]= empty($val) ? '' : $val."%"; + } + return $res; + } + return empty($value) ? '' : $value."%"; default: - return $this->value; - break; + return $value; } } - public static function makeEqual($field, $value) + public function getSameFieldOp():?string { + return $this->same_field_op; + } + + public static function makeEqual($field, $value, $same_field_op = null) + { + return new self($field, $value, '=', $same_field_op); + } + + public static function makeGreather($field, $value, $same_field_op = null) + { + return new self($field, $value, '>', $same_field_op); + } + + public static function makeGreatherOrEqual($field, $value, $same_field_op = null) + { + return new self($field, $value, '>=', $same_field_op); + } + + public static function makeBetween($field, $value, $same_field_op = null) + { + if(!is_array($value)) throw new \InvalidArgumentException("Value must be an array."); + if(count($value) !=2 ) throw new \InvalidArgumentException("Value must be an array of 2 elements."); + return new self($field, $value, ['>=','<='], $same_field_op); + } + + public static function makeBetweenStrict($field, $value, $same_field_op = null) { - return new self($field, $value, '='); + if(!is_array($value)) throw new \InvalidArgumentException("Value must be an array."); + if(count($value) !=2 ) throw new \InvalidArgumentException("Value must be an array of 2 elements."); + return new self($field, $value, ['>','<'], $same_field_op); } - public static function makeGreather($field, $value) + public static function makeLower($field, $value, $same_field_op = null) { - return new self($field, $value, '>'); + return new self($field, $value, '<', $same_field_op); } - public static function makeGreatherOrEqual($field, $value) + public static function makeLowerOrEqual($field, $value, $same_field_op = null) { - return new self($field, $value, '>='); + return new self($field, $value, '<=', $same_field_op); } - public static function makeLower($field, $value) + public static function makeNotEqual($field, $value, $same_field_op = null) { - return new self($field, $value, '<'); + return new self($field, $value, '<>', $same_field_op); } - public static function makeLowerOrEqual($field, $value) + public static function makeLike($field, $value, $same_field_op = null) { - return new self($field, $value, '<='); + return new self($field, $value, 'like', $same_field_op); } - public static function makeNotEqual($field, $value) + public static function makeLikeStart($field, $value, $same_field_op = null) { - return new self($field, $value, '<>'); + return new self($field, $value, 'start_like', $same_field_op); } - public static function makeLike($field, $value) + public function __toString():string { - return new self($field, $value, 'like'); + return sprintf("%s%s%s", $this->field, is_array($this->operator) ? json_encode($this->operator): $this->operator, is_array($this->value)? json_encode($this->value):$this->value); } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/FilterMapping.php b/app/Http/Utils/Filters/FilterMapping.php index dd95ead9..8bfae41d 100644 --- a/app/Http/Utils/Filters/FilterMapping.php +++ b/app/Http/Utils/Filters/FilterMapping.php @@ -1,4 +1,4 @@ -table = $table; $this->where = $where; + $this->bindings = []; } /** * @param FilterElement $filter + * @param array $bindings * @return string */ - public abstract function toRawSQL(FilterElement $filter); + public abstract function toRawSQL(FilterElement $filter, array $bindings = []):string; + + public function getBindings(): array + { + return $this->bindings; + } } \ No newline at end of file diff --git a/app/Http/Utils/Filters/FilterParser.php b/app/Http/Utils/Filters/FilterParser.php index aeaec48c..7181775e 100644 --- a/app/Http/Utils/Filters/FilterParser.php +++ b/app/Http/Utils/Filters/FilterParser.php @@ -1,6 +1,4 @@ 1) { $f = []; foreach ($or_filters as $of) { //single filter - preg_match('/[=<>][=>@]{0,1}/', $of, $matches); + if (empty($of)) continue; - if (count($matches) != 1) - throw new FilterParserException(sprintf("invalid OR filter format %s (should be [:FIELD_NAME:OPERAND:VALUE])", $of)); + list($field, $op, $value) = self::filterExpresion($of); - $op = $matches[0]; - $operands = explode($op, $of); - $field = $operands[0]; - $value = $operands[1]; - - if (!isset($allowed_fields[$field])){ + if (!isset($allowed_fields[$field])) { throw new FilterParserException(sprintf("filter by field %s is not allowed", $field)); } - if (!in_array($op, $allowed_fields[$field])){ - throw new FilterParserException(sprintf("%s op is not allowed for filter by field %s",$op, $field)); + if (!in_array($op, $allowed_fields[$field])) { + throw new FilterParserException(sprintf("%s op is not allowed for filter by field %s", $op, $field)); + } + // check if value has AND or OR values on same field + $same_field_op = null; + if (str_contains($value, '&&')) { + $values = explode('&&', $value); + if (count($values) > 1) { + $value = $values; + $same_field_op = 'AND'; + } + } else if (str_contains($value, '||')) { + $values = explode('||', $value); + if (count($values) > 1) { + $value = $values; + $same_field_op = 'OR'; + } } - $f_or = self::buildFilter($field, $op, $value); + $f_or = self::buildFilter($field, $op, $value, $same_field_op); if (!is_null($f_or)) $f[] = $f_or; } } else { //single filter - preg_match('/[=<>][=>@]{0,1}/', $filter, $matches); - if (count($matches) != 1) - throw new FilterParserException(sprintf("invalid filter format %s (should be [:FIELD_NAME:OPERAND:VALUE])", $filter)); + list($field, $op, $value) = self::filterExpresion($filter); - $op = $matches[0]; - $operands = explode($op, $filter); - $field = $operands[0]; - $value = $operands[1]; + // check if value has AND or OR values on same field + $same_field_op = null; + if (str_contains($value, '&&')) { + $values = explode('&&', $value); + if (count($values) > 1) { + $value = $values; + $same_field_op = 'AND'; + } + } else if (str_contains($value, '||')) { + $values = explode('||', $value); + if (count($values) > 1) { + $value = $values; + $same_field_op = 'OR'; + } + } - if (!isset($allowed_fields[$field])){ - throw new FilterParserException(sprintf("filter by field %s is not allowed", $field)); + if (!isset($allowed_fields[$field])) { + throw new FilterParserException(sprintf("Filter by field %s is not allowed.", $field)); } - if (!in_array($op, $allowed_fields[$field])){ - throw new FilterParserException(sprintf("%s op is not allowed for filter by field %s",$op, $field)); + + if (!is_array($allowed_fields[$field])) { + throw new FilterParserException(sprintf("Filter by field %s is not an array.", $field)); } - if(in_array($field, $and_fields)) - throw new FilterParserException(sprintf("filter by field %s is already on an and expression", $field)); + if (!in_array($op, $allowed_fields[$field])) { + throw new FilterParserException(sprintf("%s op is not allowed for filter by field %s.", $op, $field)); + } - $and_fields[] = $field; - $f = self::buildFilter($field, $op, $value); + $f = self::buildFilter($field, $op, $value, $same_field_op); } if (!is_null($f)) $res[] = $f; } - return new Filter($res); + + $res = new Filter($res, $filters, $ops); + Log::debug(sprintf("FilterParser::parse result %s", $res)); + return $res; + } + + /** + * @param string $exp + * @return array + * @throws FilterParserException + */ + public static function filterExpresion(string $exp) + { + + Log::debug(sprintf("FilterParser::filterExpresion %s", $exp)); + if (!preg_match('/\[\]|\(\)|>=|<=|<>|==|=\@|\@\@|<|>/i', $exp, $matches)) + throw new FilterParserException(sprintf("Invalid filter format %s (should be [:FIELD_NAME:OPERAND:VALUE]).", $exp)); + + $op = $matches[0]; + $operands = explode($op, $exp, 2); + $field = strtolower(trim($operands[0])); + $value = str_replace(['\\,','\\;'], [',',';'], urldecode($operands[1])); + Log::debug(sprintf("FilterParser::filterExpresion field %s op %s value %s", $field, $op, json_encode($value))); + return [$field, $op, $value]; } /** @@ -100,32 +166,54 @@ public static function parse($filters, $allowed_fields = []) * * @param string $field * @param string $op - * @param string $value + * @param mixed $value + * @param string $same_field_op * @return FilterElement|null */ - public static function buildFilter($field, $op, $value) + public static function buildFilter($field, $op, $value, $same_field_op = null) { + Log::debug + ( + sprintf + ( + "FilterParser::buildFilter field %s op %s value %s same_field_op %s", + $field, + $op, + json_encode($value), + $same_field_op + ) + ); + switch ($op) { case '==': - return FilterElement::makeEqual($field, $value); + return FilterElement::makeEqual($field, $value, $same_field_op); break; case '=@': - return FilterElement::makeLike($field, $value); + return FilterElement::makeLike($field, $value, $same_field_op); + break; + case '@@': + return FilterElement::makeLikeStart($field, $value, $same_field_op); break; case '>': - return FilterElement::makeGreather($field, $value); + return FilterElement::makeGreather($field, $value, $same_field_op); break; case '>=': - return FilterElement::makeGreatherOrEqual($field, $value); + return FilterElement::makeGreatherOrEqual($field, $value, $same_field_op); + break; + case '[]': + return FilterElement::makeBetween($field, $value, $same_field_op); + break; + case '()': + return FilterElement::makeBetweenStrict($field, $value, $same_field_op); break; case '<': - return FilterElement::makeLower($field, $value); + return FilterElement::makeLower($field, $value, $same_field_op); break; case '<=': - return FilterElement::makeLowerOrEqual($field, $value); + return FilterElement::makeLowerOrEqual($field, $value, $same_field_op); break; case '<>': - return FilterElement::makeNotEqual($field, $value); + return FilterElement::makeNotEqual($field, $value, $same_field_op); break; } return null; diff --git a/app/Http/Utils/FiltersParams.php b/app/Http/Utils/Filters/FiltersParams.php similarity index 100% rename from app/Http/Utils/FiltersParams.php rename to app/Http/Utils/Filters/FiltersParams.php diff --git a/app/Http/Utils/Filters/IQueryApplyable.php b/app/Http/Utils/Filters/IQueryApplyable.php new file mode 100644 index 00000000..c17e4dac --- /dev/null +++ b/app/Http/Utils/Filters/IQueryApplyable.php @@ -0,0 +1,38 @@ +main_operator = Filter::MainOperatorAnd; + $this->operator = 'IN'; + parent::__construct($alias, ''); + } + + /** + * @param FilterElement $filter + * @param array $bindings + * @return string + */ + public function toRawSQL(FilterElement $filter, array $bindings = []):string + { + $value = $filter->getValue(); + if (!is_array($value)) { + $value = [$value]; + } + // construct named params one by one bc raw sql does not support array binding + $named_params = []; + $param_idx = count($bindings) + 1; + foreach($value as $v){ + $named_params[] = ":".sprintf(Filter::ParamPrefix, $param_idx); + $this->bindings[sprintf(Filter::ParamPrefix, $param_idx)] = $v; + $param_idx++; + } + + return sprintf("%s %s (%s)", $this->table, $this->operator, implode(',', $named_params)); + } +} \ No newline at end of file diff --git a/app/Http/Utils/Filters/SQL/SQLInstanceOfFilterMapping.php b/app/Http/Utils/Filters/SQL/SQLInstanceOfFilterMapping.php new file mode 100644 index 00000000..452f0ce7 --- /dev/null +++ b/app/Http/Utils/Filters/SQL/SQLInstanceOfFilterMapping.php @@ -0,0 +1,66 @@ +class_names = $class_names; + parent::__construct($alias, sprintf("%s.ClassName = ':class_name'", $alias)); + } + + private function translateClassName(string $value):string + { + if (isset($this->class_names[$value])) { + $parts = explode("\\", $this->class_names[$value]); + return $parts[count(($parts))-1]; + } + return $value; + } + + /** + * @param FilterElement $filter + * @param array $bindings + * @return string + */ + public function toRawSQL(FilterElement $filter, array $bindings = []):string + { + $value = $filter->getValue(); + + if (is_array($value)) { + $where_components = []; + foreach ($value as $val) { + $where_components[] = str_replace(":class_name", $this->translateClassName($val), $this->where); + } + return implode(sprintf(" %s ", $filter->getSameFieldOp()), $where_components); + } + + return str_replace(":class_name", $this->translateClassName($filter->getValue()), $this->where); + } + +} \ No newline at end of file diff --git a/app/Http/Utils/Filters/SQL/SQLNotInFilterMapping.php b/app/Http/Utils/Filters/SQL/SQLNotInFilterMapping.php new file mode 100644 index 00000000..ccc44da4 --- /dev/null +++ b/app/Http/Utils/Filters/SQL/SQLNotInFilterMapping.php @@ -0,0 +1,30 @@ +operator = 'NOT IN'; + } +} \ No newline at end of file diff --git a/app/libs/Auth/Models/IGroupSlugs.php b/app/libs/Auth/Models/IGroupSlugs.php index 2ca6a1f0..fb7fef55 100644 --- a/app/libs/Auth/Models/IGroupSlugs.php +++ b/app/libs/Auth/Models/IGroupSlugs.php @@ -26,4 +26,7 @@ interface IGroupSlugs public const RawUsersGroup = 'raw-users'; public const ChatQAGroup = 'chat-qa'; public const ChatHelpGroup = 'chat-help'; + public const SponsorServicesGroup = 'sponsors-services'; + public const SponsorUsersGroup = 'sponsors'; + public const SponsorExternalUsersGroup = 'sponsors-external-users'; } \ No newline at end of file diff --git a/app/libs/OAuth2/IGroupScopes.php b/app/libs/OAuth2/IGroupScopes.php new file mode 100644 index 00000000..be858843 --- /dev/null +++ b/app/libs/OAuth2/IGroupScopes.php @@ -0,0 +1,25 @@ +findOneBy(['slug' => IGroupSlugs::SponsorServicesGroup]); + if(is_null($group)){ + $group = new Group(); + $group->setName('Sponsors Services'); + $group->setSlug(IGroupSlugs::SponsorServicesGroup); + $group->setDefault(false); + $group->setActive(true); + EntityManager::persist($group); + EntityManager::flush(); + } + + $group = EntityManager::getRepository(Group::class)->findOneBy(['slug' => IGroupSlugs::SponsorUsersGroup]); + if(is_null($group)){ + $group = new Group(); + $group->setName('Sponsors Users'); + $group->setSlug(IGroupSlugs::SponsorUsersGroup); + $group->setDefault(false); + $group->setActive(true); + EntityManager::persist($group); + EntityManager::flush(); + } + + $group = EntityManager::getRepository(Group::class)->findOneBy(['slug' => IGroupSlugs::SponsorExternalUsersGroup]); + if(is_null($group)){ + $group = new Group(); + $group->setName('Sponsors External Users'); + $group->setSlug(IGroupSlugs::SponsorExternalUsersGroup); + $group->setDefault(false); + $group->setActive(true); + EntityManager::persist($group); + EntityManager::flush(); + } + + if(!SeedUtils::seedApi("groups", "Groups Info API")) return; + + SeedUtils::seedScopes([ + [ + 'name' => IGroupScopes::ReadAll, + 'short_description' => 'Allows access to Groups info.', + 'description' => 'Allows access to Groups info.', + 'system' => false, + 'default' => false, + 'groups' => false, + ], + [ + 'name' => IGroupScopes::Write, + 'short_description' => 'Allows access to write Groups info.', + 'description' => 'Allows access to write Groups info.', + 'system' => false, + 'default' => false, + 'groups' => false, + ], + ], 'groups'); + + SeedUtils::seedApiEndpoints('groups', [ + [ + 'name' => 'get-groups', + 'active' => true, + 'route' => '/api/v1/groups', + 'http_method' => 'GET', + 'scopes' => [ + \App\libs\OAuth2\IGroupScopes::ReadAll + ], + ], + ]); + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema):void + { + + } +} diff --git a/database/seeds/ApiEndpointSeeder.php b/database/seeds/ApiEndpointSeeder.php index bcc91bef..87d00e25 100644 --- a/database/seeds/ApiEndpointSeeder.php +++ b/database/seeds/ApiEndpointSeeder.php @@ -21,12 +21,12 @@ class ApiEndpointSeeder extends Seeder public function run() { - DB::table('oauth2_api_endpoint_api_scope')->delete(); DB::table('oauth2_api_endpoint')->delete(); $this->seedUsersEndpoints(); $this->seedRegistrationEndpoints(); $this->seedSSOEndpoints(); + $this->seedGroupEndpoints(); } private function seedUsersEndpoints() @@ -187,4 +187,18 @@ private function seedSSOEndpoints(){ ]); } + private function seedGroupEndpoints(){ + SeedUtils::seedApiEndpoints('groups', [ + [ + 'name' => 'get-groups', + 'active' => true, + 'route' => '/api/v1/groups', + 'http_method' => 'GET', + 'scopes' => [ + \App\libs\OAuth2\IGroupScopes::ReadAll + ], + ], + ]); + } + } \ No newline at end of file diff --git a/database/seeds/ApiScopeSeeder.php b/database/seeds/ApiScopeSeeder.php index 88b42837..a81c8973 100644 --- a/database/seeds/ApiScopeSeeder.php +++ b/database/seeds/ApiScopeSeeder.php @@ -11,6 +11,8 @@ * See the License for the specific language governing permissions and * limitations under the License. **/ + +use App\libs\OAuth2\IGroupScopes; use OAuth2\OAuth2Protocol; use Models\OAuth2\Api; use Models\OAuth2\ApiScope; @@ -31,6 +33,7 @@ public function run() $this->seedUsersScopes(); $this->seedRegistrationScopes(); $this->seedSSOScopes(); + $this->seedGroupScopes(); } private function seedUsersScopes(){ @@ -138,4 +141,26 @@ private function seedSSOScopes(){ ], 'sso'); } + + private function seedGroupScopes(){ + SeedUtils::seedScopes([ + [ + 'name' => IGroupScopes::ReadAll, + 'short_description' => 'Allows access to Groups info.', + 'description' => 'Allows access to Groups info.', + 'system' => false, + 'default' => false, + 'groups' => false, + ], + [ + 'name' => IGroupScopes::Write, + 'short_description' => 'Allows access to write Groups info.', + 'description' => 'Allows access to write Groups info.', + 'system' => false, + 'default' => false, + 'groups' => false, + ], + + ], 'groups'); + } } \ No newline at end of file diff --git a/database/seeds/ApiSeeder.php b/database/seeds/ApiSeeder.php index dc3d30b1..5d6255ce 100644 --- a/database/seeds/ApiSeeder.php +++ b/database/seeds/ApiSeeder.php @@ -62,5 +62,15 @@ public function run() EntityManager::persist($api); EntityManager::flush(); + + $api = new Api(); + $api->setName('groups'); + $api->setActive(true); + $api->setDescription('Groups Info API'); + $api->setResourceServer($rs); + + EntityManager::persist($api); + + EntityManager::flush(); } } \ No newline at end of file diff --git a/database/seeds/TestSeeder.php b/database/seeds/TestSeeder.php index e77e7339..1b5ceb79 100644 --- a/database/seeds/TestSeeder.php +++ b/database/seeds/TestSeeder.php @@ -212,6 +212,18 @@ private function createTestGroups(){ [ 'name' => IGroupSlugs::RawUsersGroup, 'slug' => IGroupSlugs::RawUsersGroup, + ], + [ + 'name' => IGroupSlugs::SponsorServicesGroup, + 'slug' => IGroupSlugs::SponsorServicesGroup, + ], + [ + 'name' => IGroupSlugs::SponsorUsersGroup, + 'slug' => IGroupSlugs::SponsorUsersGroup, + ], + [ + 'name' => IGroupSlugs::SponsorExternalUsersGroup, + 'slug' => IGroupSlugs::SponsorExternalUsersGroup, ] ]; diff --git a/docker-compose.yml b/docker-compose.yml index 9ded13e0..aa856815 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.7" services: app: build: diff --git a/routes/api.php b/routes/api.php index 0fc90ef4..4df79fda 100644 --- a/routes/api.php +++ b/routes/api.php @@ -53,6 +53,10 @@ }); }); +Route::group(['prefix' => 'groups'], function () { + Route::get('', 'OAuth2GroupApiController@getAll'); +}); + // 3rd Party SSO integrations Route::group(['prefix' => 'sso'], function () { diff --git a/tests/OAuth2GroupApiTest.php b/tests/OAuth2GroupApiTest.php new file mode 100644 index 00000000..57078f26 --- /dev/null +++ b/tests/OAuth2GroupApiTest.php @@ -0,0 +1,52 @@ + 'slug==sponsors-services||sponsors||sponsors-external-users', + 'order' => '-slug' + ]; + + $response = $this->action( + "GET", + "Api\\OAuth2\\OAuth2GroupApiController@getAll", + $params, + [], + [], + [], + array("HTTP_Authorization" => " Bearer " .$this->access_token)); + + $content = $response->getContent(); + $this->assertResponseStatus(200); + $page = json_decode($content); + $this->assertTrue($page->total > 0); + } + + protected function getScopes() + { + return [ + IGroupScopes::ReadAll, + IGroupScopes::Write + ]; + } +} \ No newline at end of file