2021-11-03 11:47:10 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Model\Behavior;
|
|
|
|
|
|
|
|
use Cake\ORM\Behavior;
|
|
|
|
use Cake\ORM\Entity;
|
|
|
|
use Cake\ORM\Query;
|
|
|
|
use Cake\ORM\Table;
|
2022-02-25 15:22:57 +01:00
|
|
|
use Cake\ORM\TableRegistry;
|
2021-11-03 11:47:10 +01:00
|
|
|
use Cake\Utility\Inflector;
|
|
|
|
use Cake\Database\Expression\QueryExpression;
|
|
|
|
|
2022-02-25 15:22:57 +01:00
|
|
|
use MetaFieldsTypes\TextType;
|
|
|
|
use MetaFieldsTypes\IPv4Type;
|
|
|
|
require_once(APP . 'Lib' . DS . 'default' . DS . 'meta_fields_types' . DS . 'TextType.php');
|
|
|
|
require_once(APP . 'Lib' . DS . 'default' . DS . 'meta_fields_types' . DS . 'IPv4Type.php');
|
2021-11-03 11:47:10 +01:00
|
|
|
|
|
|
|
class MetaFieldsBehavior extends Behavior
|
|
|
|
{
|
|
|
|
protected $_defaultConfig = [
|
|
|
|
'metaFieldsAssoc' => [
|
|
|
|
'className' => 'MetaFields',
|
|
|
|
'foreignKey' => 'parent_id',
|
|
|
|
'bindingKey' => 'id',
|
|
|
|
'dependent' => true,
|
|
|
|
'cascadeCallbacks' => true,
|
|
|
|
'saveStrategy' => 'append',
|
|
|
|
'propertyName' => 'meta_fields',
|
|
|
|
],
|
|
|
|
'modelAssoc' => [
|
|
|
|
'foreignKey' => 'parent_id',
|
|
|
|
'bindingKey' => 'id',
|
|
|
|
],
|
|
|
|
'metaTemplateFieldCounter' => ['counter'],
|
|
|
|
|
|
|
|
'implementedEvents' => [
|
|
|
|
'Model.beforeMarshal' => 'beforeMarshal',
|
|
|
|
'Model.beforeFind' => 'beforeFind',
|
|
|
|
'Model.beforeSave' => 'beforeSave',
|
|
|
|
],
|
|
|
|
'implementedMethods' => [
|
|
|
|
'normalizeMetafields' => 'normalizeMetafields',
|
2021-11-10 15:28:09 +01:00
|
|
|
'buildMetaFieldQuerySnippetForMatchingParent' => 'buildQuerySnippetForMatchingParent',
|
2021-11-03 11:47:10 +01:00
|
|
|
],
|
|
|
|
'implementedFinders' => [
|
2022-02-09 15:18:24 +01:00
|
|
|
'metafield' => 'findMetafield',
|
2021-11-03 11:47:10 +01:00
|
|
|
],
|
|
|
|
];
|
|
|
|
|
2021-11-09 08:59:17 +01:00
|
|
|
private $aliasScope = null;
|
2022-02-25 15:22:57 +01:00
|
|
|
private $typeHandlers = [];
|
2021-11-09 08:59:17 +01:00
|
|
|
|
2021-11-03 11:47:10 +01:00
|
|
|
public function initialize(array $config): void
|
|
|
|
{
|
|
|
|
$this->bindAssociations();
|
|
|
|
$this->_metaTemplateFieldTable = $this->_table;
|
|
|
|
$this->_metaTemplateTable = $this->_table;
|
2022-02-25 15:22:57 +01:00
|
|
|
$this->loadTypeHandlers();
|
|
|
|
}
|
|
|
|
|
|
|
|
private function loadTypeHandlers()
|
|
|
|
{
|
|
|
|
$typeHandlers = [
|
|
|
|
new TextType(),
|
|
|
|
new IPv4Type(),
|
|
|
|
];
|
|
|
|
foreach ($typeHandlers as $handler) {
|
|
|
|
$this->typeHandlers[$handler::TYPE] = $handler;
|
|
|
|
}
|
2021-11-03 11:47:10 +01:00
|
|
|
}
|
|
|
|
|
2022-02-25 15:36:55 +01:00
|
|
|
public function getTypeHandlers(): array
|
|
|
|
{
|
|
|
|
return $this->typeHandlers;
|
|
|
|
}
|
|
|
|
|
2021-11-09 08:59:17 +01:00
|
|
|
public function getScope()
|
|
|
|
{
|
|
|
|
if (is_null($this->aliasScope)) {
|
|
|
|
$this->aliasScope = Inflector::underscore(Inflector::singularize($this->_table->getAlias()));
|
|
|
|
}
|
|
|
|
return $this->aliasScope;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-11-03 11:47:10 +01:00
|
|
|
public function bindAssociations()
|
|
|
|
{
|
|
|
|
$config = $this->getConfig();
|
|
|
|
$metaFieldsAssoc = $config['metaFieldsAssoc'];
|
|
|
|
$modelAssoc = $config['modelAssoc'];
|
|
|
|
|
|
|
|
$table = $this->_table;
|
|
|
|
$tableAlias = $this->_table->getAlias();
|
|
|
|
|
|
|
|
$assocConditions = [
|
2021-11-09 08:59:17 +01:00
|
|
|
'MetaFields.scope' => $this->getScope()
|
2021-11-03 11:47:10 +01:00
|
|
|
];
|
|
|
|
if (!$table->hasAssociation('MetaFields')) {
|
|
|
|
$table->hasMany('MetaFields', array_merge(
|
|
|
|
$metaFieldsAssoc,
|
|
|
|
[
|
|
|
|
'conditions' => $assocConditions
|
|
|
|
]
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$table->MetaFields->hasAssociation($tableAlias)) {
|
|
|
|
$table->MetaFields->belongsTo($tableAlias, array_merge(
|
|
|
|
$modelAssoc,
|
|
|
|
[
|
|
|
|
'className' => get_class($table),
|
|
|
|
]
|
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function beforeMarshal($event, $data, $options)
|
|
|
|
{
|
|
|
|
$property = $this->getConfig('metaFieldsAssoc.propertyName');
|
|
|
|
$options['accessibleFields'][$property] = true;
|
|
|
|
$options['associated']['MetaFields']['accessibleFields']['id'] = true;
|
|
|
|
|
|
|
|
if (isset($data[$property])) {
|
|
|
|
if (!empty($data[$property])) {
|
|
|
|
$data[$property] = $this->normalizeMetafields($data[$property]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function beforeSave($event, $entity, $options)
|
|
|
|
{
|
|
|
|
if (empty($entity->metaFields)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function normalizeMetafields($metaFields)
|
|
|
|
{
|
|
|
|
return $metaFields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Usage:
|
2022-02-09 15:18:24 +01:00
|
|
|
* $this->{$model}->find('metaField', [
|
2021-11-03 11:47:10 +01:00
|
|
|
* ['meta_template_id' => 1, 'field' => 'email', 'value' => '%@domain.test'],
|
|
|
|
* ['meta_template_id' => 1, 'field' => 'country_code', 'value' => '!LU'],
|
|
|
|
* ['meta_template_id' => 1, 'field' => 'time_zone', 'value' => 'UTC+2'],
|
|
|
|
* ])
|
2022-02-09 15:18:24 +01:00
|
|
|
* $this->{$model}->find('metaField', [
|
2021-11-03 11:47:10 +01:00
|
|
|
* 'AND' => [
|
|
|
|
* ['meta_template_id' => 1, 'field' => 'email', 'value' => '%@domain.test'],
|
|
|
|
* 'OR' => [
|
|
|
|
* ['meta_template_id' => 1, 'field' => 'time_zone', 'value' => 'UTC+1'],
|
|
|
|
* ['meta_template_id' => 1, 'field' => 'time_zone', 'value' => 'UTC+2'],
|
|
|
|
* ],
|
|
|
|
* ],
|
|
|
|
* ])
|
|
|
|
*/
|
2022-02-09 15:18:24 +01:00
|
|
|
public function findMetafield(Query $query, array $filters)
|
2021-11-10 15:28:09 +01:00
|
|
|
{
|
|
|
|
$conditions = $this->buildQuerySnippetForMatchingParent($filters);
|
|
|
|
$query->where($conditions);
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function buildQuerySnippetForMatchingParent(array $filters): array
|
2021-11-03 11:47:10 +01:00
|
|
|
{
|
|
|
|
if (empty($filters)) {
|
2021-11-10 15:28:09 +01:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
if (count(array_filter(array_keys($filters), 'is_string'))) {
|
|
|
|
$filters = [$filters];
|
2021-11-03 11:47:10 +01:00
|
|
|
}
|
|
|
|
$conjugatedFilters = $this->buildConjugatedFilters($filters);
|
|
|
|
$conditions = $this->buildConjugatedQuerySnippet($conjugatedFilters);
|
2021-11-10 15:28:09 +01:00
|
|
|
return $conditions;
|
2021-11-03 11:47:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildConjugatedFilters(array $filters): array
|
|
|
|
{
|
|
|
|
$conjugatedFilters = [];
|
|
|
|
foreach ($filters as $operator => $subFilters) {
|
|
|
|
if (is_numeric($operator)) {
|
|
|
|
$conjugatedFilters[] = $subFilters;
|
|
|
|
} else {
|
|
|
|
if (!empty($subFilters)) {
|
|
|
|
$conjugatedFilters[$operator] = $this->buildConjugatedFilters($subFilters);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $conjugatedFilters;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function buildConjugatedQuerySnippet(array $conjugatedFilters, string $parentOperator='AND'): array
|
|
|
|
{
|
|
|
|
$conditions = [];
|
|
|
|
if (empty($conjugatedFilters['AND']) && empty($conjugatedFilters['OR'])) {
|
|
|
|
if (count(array_filter(array_keys($conjugatedFilters), 'is_string')) > 0) {
|
|
|
|
$conditions = $this->buildComposedQuerySnippet([$conjugatedFilters]);
|
|
|
|
} else {
|
|
|
|
$conditions = $this->buildComposedQuerySnippet($conjugatedFilters, $parentOperator);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
foreach ($conjugatedFilters as $subOperator => $subFilter) {
|
|
|
|
$conditions[$subOperator] = $this->buildConjugatedQuerySnippet($subFilter, $subOperator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $conditions;
|
|
|
|
}
|
|
|
|
|
2021-11-10 15:28:09 +01:00
|
|
|
protected function buildComposedQuerySnippet(array $filters, string $operator='AND'): array
|
2021-11-03 11:47:10 +01:00
|
|
|
{
|
|
|
|
$conditions = [];
|
|
|
|
foreach ($filters as $filterOperator => $filter) {
|
|
|
|
$subQuery = $this->buildQuerySnippet($filter, true);
|
|
|
|
$modelAlias = $this->_table->getAlias();
|
|
|
|
$conditions[$operator][] = [$modelAlias . '.id IN' => $subQuery];
|
|
|
|
}
|
|
|
|
return $conditions;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-02-25 15:22:57 +01:00
|
|
|
protected function setQueryExpressionForTextField(QueryExpression $exp, string $field, string $value): QueryExpression
|
2021-11-03 11:47:10 +01:00
|
|
|
{
|
|
|
|
if (substr($value, 0, 1) == '!') {
|
|
|
|
$value = substr($value, 1);
|
|
|
|
$exp->notEq($field, $value);
|
2021-11-10 12:06:04 +01:00
|
|
|
} else if (strpos($value, '%') !== false) {
|
2021-11-03 11:47:10 +01:00
|
|
|
$exp->like($field, $value);
|
|
|
|
} else {
|
|
|
|
$exp->eq($field, $value);
|
|
|
|
}
|
|
|
|
return $exp;
|
|
|
|
}
|
|
|
|
|
2021-11-10 15:28:09 +01:00
|
|
|
protected function buildQuerySnippet(array $filter): Query
|
2021-11-03 11:47:10 +01:00
|
|
|
{
|
2022-02-25 15:22:57 +01:00
|
|
|
$this->MetaTemplateFields = TableRegistry::getTableLocator()->get('MetaTemplateFields');
|
|
|
|
$metaTemplateField = $this->MetaTemplateFields->get($filter['meta_template_field_id']);
|
|
|
|
$whereClosure = function (QueryExpression $exp) use ($filter, $metaTemplateField) {
|
2021-11-03 11:47:10 +01:00
|
|
|
foreach ($filter as $column => $value) {
|
|
|
|
$keyedColumn = 'MetaFields.' . $column;
|
2022-02-25 15:22:57 +01:00
|
|
|
if ($column == 'value') {
|
|
|
|
$this->setQueryExpressionForField($exp, $keyedColumn, $value, $metaTemplateField);
|
|
|
|
} else {
|
|
|
|
$this->setQueryExpressionForTextField($exp, $keyedColumn, $value);
|
|
|
|
}
|
2021-11-03 11:47:10 +01:00
|
|
|
}
|
|
|
|
return $exp;
|
|
|
|
};
|
|
|
|
|
|
|
|
$foreignKey = $this->getConfig('modelAssoc.foreignKey');
|
|
|
|
$query = $this->_table->MetaFields->find()
|
|
|
|
->select('MetaFields.' . $foreignKey)
|
|
|
|
->where($whereClosure);
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
2022-02-25 15:22:57 +01:00
|
|
|
protected function setQueryExpressionForField(QueryExpression $exp, string $field, string $value, \App\Model\Entity\MetaTemplateField $metaTemplateField): QueryExpression
|
|
|
|
{
|
|
|
|
if (isset($this->typeHandlers[$metaTemplateField->type])) {
|
|
|
|
$exp = $this->typeHandlers[$metaTemplateField->type]->setQueryExpression($exp, $value, $metaTemplateField);
|
|
|
|
} else {
|
|
|
|
$exp = $this->setQueryExpressionForTextField($exp, $field, $value);
|
|
|
|
}
|
|
|
|
return $exp;
|
|
|
|
}
|
2021-11-03 11:47:10 +01:00
|
|
|
}
|