chg: [CRUD] Improved metaFields filtering capabilities
parent
5d1106e82a
commit
d4001fab18
|
@ -30,9 +30,10 @@ class CRUDComponent extends Component
|
||||||
}
|
}
|
||||||
$options['filters'][] = 'quickFilter';
|
$options['filters'][] = 'quickFilter';
|
||||||
}
|
}
|
||||||
|
$options['filters'][] = 'filteringLabel';
|
||||||
$params = $this->Controller->ParamHandler->harvestParams(empty($options['filters']) ? [] : $options['filters']);
|
$params = $this->Controller->ParamHandler->harvestParams(empty($options['filters']) ? [] : $options['filters']);
|
||||||
$query = $this->Table->find();
|
$query = $this->Table->find();
|
||||||
$query = $this->setFilters($params, $query);
|
$query = $this->setFilters($params, $query, $options);
|
||||||
$query = $this->setQuickFilters($params, $query, empty($options['quickFilters']) ? [] : $options['quickFilters']);
|
$query = $this->setQuickFilters($params, $query, empty($options['quickFilters']) ? [] : $options['quickFilters']);
|
||||||
if (!empty($options['contain'])) {
|
if (!empty($options['contain'])) {
|
||||||
$query->contain($options['contain']);
|
$query->contain($options['contain']);
|
||||||
|
@ -243,22 +244,35 @@ class CRUDComponent extends Component
|
||||||
if (empty($this->Table->metaFields)) {
|
if (empty($this->Table->metaFields)) {
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
$query = $this->MetaFields->MetaTemplates->find();
|
$metaFieldScope = $this->Table->metaFields;
|
||||||
$metaFields = $this->Table->metaFields;
|
$query = $this->MetaTemplates->find()->where(['MetaTemplates.scope' => $metaFieldScope]);
|
||||||
$query->contain('MetaTemplateFields', function ($q) use ($id, $metaFields) {
|
$query->contain(['MetaTemplateFields.MetaFields' => function ($q) use ($id, $metaFieldScope) {
|
||||||
return $q->innerJoinWith('MetaFields')
|
return $q->where(['MetaFields.scope' => $metaFieldScope, 'MetaFields.parent_id' => $id]);
|
||||||
->where(['MetaFields.scope' => $metaFields, 'MetaFields.parent_id' => $id]);
|
}]);
|
||||||
});
|
$query
|
||||||
$query->innerJoinWith('MetaTemplateFields', function ($q) {
|
->order(['MetaTemplates.is_default' => 'DESC'])
|
||||||
return $q->contain('MetaFields')->innerJoinWith('MetaFields');
|
->order(['MetaTemplates.name' => 'ASC']);
|
||||||
});
|
$metaTemplates = $query->all()->toArray();
|
||||||
$query->group(['MetaTemplates.id', 'MetaTemplates.scope', 'MetaTemplates.name', 'MetaTemplates.namespace', 'MetaTemplates.description', 'MetaTemplates.version', 'MetaTemplates.uuid', 'MetaTemplates.source', 'MetaTemplates.enabled', 'MetaTemplates.is_default'])
|
$metaTemplates = $this->pruneEmptyMetaTemplates($metaTemplates);
|
||||||
->order(['MetaTemplates.is_default' => 'DESC']);
|
|
||||||
$metaTemplates = $query->all();
|
|
||||||
$data['metaTemplates'] = $metaTemplates;
|
$data['metaTemplates'] = $metaTemplates;
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function pruneEmptyMetaTemplates($metaTemplates)
|
||||||
|
{
|
||||||
|
foreach ($metaTemplates as $i => $metaTemplate) {
|
||||||
|
foreach ($metaTemplate['meta_template_fields'] as $j => $metaTemplateField) {
|
||||||
|
if (empty($metaTemplateField['meta_fields'])) {
|
||||||
|
unset($metaTemplates[$i]['meta_template_fields'][$j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($metaTemplates[$i]['meta_template_fields'])) {
|
||||||
|
unset($metaTemplates[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $metaTemplates;
|
||||||
|
}
|
||||||
|
|
||||||
public function getMetaFields($id, $data)
|
public function getMetaFields($id, $data)
|
||||||
{
|
{
|
||||||
if (empty($this->Table->metaFields)) {
|
if (empty($this->Table->metaFields)) {
|
||||||
|
@ -361,28 +375,51 @@ class CRUDComponent extends Component
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function setFilters($params, \Cake\ORM\Query $query): \Cake\ORM\Query
|
protected function setFilters($params, \Cake\ORM\Query $query, array $options): \Cake\ORM\Query
|
||||||
{
|
{
|
||||||
$params = $this->massageFilters($params);
|
$filteringLabel = !empty($params['filteringLabel']) ? $params['filteringLabel'] : '';
|
||||||
$conditions = array();
|
unset($params['filteringLabel']);
|
||||||
if (!empty($params['simpleFilters'])) {
|
$customFilteringFunction = '';
|
||||||
foreach ($params['simpleFilters'] as $filter => $filterValue) {
|
$chosenFilter = '';
|
||||||
if ($filter === 'quickFilter') {
|
if (!empty($options['contextFilters']['custom'])) {
|
||||||
continue;
|
foreach ($options['contextFilters']['custom'] as $filter) {
|
||||||
}
|
if ($filter['label'] == $filteringLabel) {
|
||||||
if (is_array($filterValue)) {
|
$customFilteringFunction = $filter;
|
||||||
$query->where([($filter . ' IN') => $filterValue]);
|
$chosenFilter = $filter;
|
||||||
} else {
|
break;
|
||||||
$query = $this->setValueCondition($query, $filter, $filterValue);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!empty($params['relatedFilters'])) {
|
|
||||||
foreach ($params['relatedFilters'] as $filter => $filterValue) {
|
if (!empty($customFilteringFunction['filterConditionFunction'])) {
|
||||||
$filterParts = explode('.', $filter);
|
$query = $customFilteringFunction['filterConditionFunction']($query);
|
||||||
$query = $this->setNestedRelatedCondition($query, $filterParts, $filterValue);
|
} else {
|
||||||
|
if (!empty($chosenFilter)) {
|
||||||
|
$params = $this->massageFilters($chosenFilter['filterCondition']);
|
||||||
|
} else {
|
||||||
|
$params = $this->massageFilters($params);
|
||||||
|
}
|
||||||
|
$conditions = array();
|
||||||
|
if (!empty($params['simpleFilters'])) {
|
||||||
|
foreach ($params['simpleFilters'] as $filter => $filterValue) {
|
||||||
|
if ($filter === 'quickFilter') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (is_array($filterValue)) {
|
||||||
|
$query->where([($filter . ' IN') => $filterValue]);
|
||||||
|
} else {
|
||||||
|
$query = $this->setValueCondition($query, $filter, $filterValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!empty($params['relatedFilters'])) {
|
||||||
|
foreach ($params['relatedFilters'] as $filter => $filterValue) {
|
||||||
|
$filterParts = explode('.', $filter);
|
||||||
|
$query = $this->setNestedRelatedCondition($query, $filterParts, $filterValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,6 +484,57 @@ class CRUDComponent extends Component
|
||||||
$this->Controller->set('filteringContexts', $filteringContexts);
|
$this->Controller->set('filteringContexts', $filteringContexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getParentsForMetaFields($query, array $metaConditions)
|
||||||
|
{
|
||||||
|
$metaTemplates = $this->MetaFields->MetaTemplates->find('list', [
|
||||||
|
'keyField' => 'name',
|
||||||
|
'valueField' => 'id'
|
||||||
|
])->where(['name IN' => array_keys($metaConditions)])->all()->toArray();
|
||||||
|
$fieldsConditions = [];
|
||||||
|
foreach ($metaConditions as $templateName => $templateConditions) {
|
||||||
|
$metaTemplateID = isset($metaTemplates[$templateName]) ? $metaTemplates[$templateName] : -1;
|
||||||
|
foreach ($templateConditions as $conditions) {
|
||||||
|
$conditions['meta_template_id'] = $metaTemplateID;
|
||||||
|
$fieldsConditions[] = $conditions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$matchingMetaQuery = $this->getParentIDQueryForMetaANDConditions($fieldsConditions);
|
||||||
|
return $query->where(['id IN' => $matchingMetaQuery]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getParentIDQueryForMetaANDConditions(array $metaANDConditions)
|
||||||
|
{
|
||||||
|
if (empty($metaANDConditions)) {
|
||||||
|
throw new Exception('Invalid passed conditions');
|
||||||
|
}
|
||||||
|
foreach ($metaANDConditions as $i => $conditions) {
|
||||||
|
$metaANDConditions[$i]['scope'] = $this->Table->metaFields;
|
||||||
|
}
|
||||||
|
$firstCondition = $this->prefixConditions('MetaFields', $metaANDConditions[0]);
|
||||||
|
$conditionsToJoin = array_slice($metaANDConditions, 1);
|
||||||
|
$query = $this->MetaFields->find()
|
||||||
|
->select('parent_id')
|
||||||
|
->where($firstCondition);
|
||||||
|
foreach ($conditionsToJoin as $i => $conditions) {
|
||||||
|
$joinedConditions = $this->prefixConditions("m{$i}", $conditions);
|
||||||
|
$joinedConditions[] = "m{$i}.parent_id = MetaFields.parent_id";
|
||||||
|
$query->rightJoin(
|
||||||
|
["m{$i}" => 'meta_fields'],
|
||||||
|
$joinedConditions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prefixConditions(string $prefix, array $conditions)
|
||||||
|
{
|
||||||
|
$prefixedConditions = [];
|
||||||
|
foreach ($conditions as $condField => $condValue) {
|
||||||
|
$prefixedConditions["${prefix}.${condField}"] = $condValue;
|
||||||
|
}
|
||||||
|
return $prefixedConditions;
|
||||||
|
}
|
||||||
|
|
||||||
public function toggle(int $id, string $fieldName = 'enabled', array $params = []): void
|
public function toggle(int $id, string $fieldName = 'enabled', array $params = []): void
|
||||||
{
|
{
|
||||||
if (empty($id)) {
|
if (empty($id)) {
|
||||||
|
|
|
@ -15,8 +15,45 @@ class OrganisationsController extends AppController
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$this->CRUD->index([
|
$this->CRUD->index([
|
||||||
'filters' => ['name', 'uuid', 'nationality', 'sector', 'type', 'url', 'Alignments.id'],
|
'filters' => ['name', 'uuid', 'nationality', 'sector', 'type', 'url', 'Alignments.id', 'MetaFields.field', 'MetaFields.value', 'MetaFields.MetaTemplates.name'],
|
||||||
'quickFilters' => ['name', 'uuid', 'nationality', 'sector', 'type', 'url'],
|
'quickFilters' => [['name' => true], 'uuid', 'nationality', 'sector', 'type', 'url'],
|
||||||
|
'contextFilters' => [
|
||||||
|
'custom' => [
|
||||||
|
[
|
||||||
|
'label' => __('ENISA Accredited'),
|
||||||
|
'filterCondition' => [
|
||||||
|
'MetaFields.field' => 'enisa-tistatus',
|
||||||
|
'MetaFields.value' => 'Accredited',
|
||||||
|
'MetaFields.MetaTemplates.name' => 'ENISA CSIRT Network'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => __('ENISA not-Accredited'),
|
||||||
|
'filterCondition' => [
|
||||||
|
'MetaFields.field' => 'enisa-tistatus',
|
||||||
|
'MetaFields.value !=' => 'Accredited',
|
||||||
|
'MetaFields.MetaTemplates.name' => 'ENISA CSIRT Network'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'label' => __('ENISA CSIRT Network (GOV)'),
|
||||||
|
'filterConditionFunction' => function($query) {
|
||||||
|
return $this->CRUD->getParentsForMetaFields($query, [
|
||||||
|
'ENISA CSIRT Network' => [
|
||||||
|
[
|
||||||
|
'field' => 'constituency',
|
||||||
|
'value LIKE' => '%Government%',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'field' => 'csirt-network-status',
|
||||||
|
'value' => 'Member',
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
'contain' => ['Alignments' => 'Individuals']
|
'contain' => ['Alignments' => 'Individuals']
|
||||||
]);
|
]);
|
||||||
$responsePayload = $this->CRUD->getResponsePayload();
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
|
|
@ -27,6 +27,8 @@ class MetaFieldsTable extends AppTable
|
||||||
->notEmptyString('meta_template_id')
|
->notEmptyString('meta_template_id')
|
||||||
->notEmptyString('meta_template_field_id')
|
->notEmptyString('meta_template_field_id')
|
||||||
->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_id', 'meta_template_field_id'], 'create');
|
->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_id', 'meta_template_field_id'], 'create');
|
||||||
|
|
||||||
|
// add validation regex
|
||||||
return $validator;
|
return $validator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class OrganisationsTable extends AppTable
|
||||||
[
|
[
|
||||||
'dependent' => true,
|
'dependent' => true,
|
||||||
'foreignKey' => 'parent_id',
|
'foreignKey' => 'parent_id',
|
||||||
'conditions' => ['scope' => 'organisation']
|
'conditions' => ['MetaFields.scope' => 'organisation']
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->setDisplayField('name');
|
$this->setDisplayField('name');
|
||||||
|
|
|
@ -15,6 +15,10 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'type' => 'context_filters',
|
||||||
|
'context_filters' => $filteringContexts
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'type' => 'search',
|
'type' => 'search',
|
||||||
'button' => __('Filter'),
|
'button' => __('Filter'),
|
||||||
|
|
|
@ -5,10 +5,11 @@
|
||||||
$urlParams = [
|
$urlParams = [
|
||||||
'controller' => $this->request->getParam('controller'),
|
'controller' => $this->request->getParam('controller'),
|
||||||
'action' => 'index',
|
'action' => 'index',
|
||||||
'?' => $filteringContext['filterCondition']
|
'?' => array_merge($filteringContext['filterCondition'], ['filteringLabel' => $filteringContext['label']])
|
||||||
];
|
];
|
||||||
$currentQuery = $this->request->getQuery();
|
$currentQuery = $this->request->getQuery();
|
||||||
unset($currentQuery['page'], $currentQuery['limit'], $currentQuery['sort']);
|
$filteringLabel = !empty($currentQuery['filteringLabel']) ? $currentQuery['filteringLabel'] : '';
|
||||||
|
unset($currentQuery['page'], $currentQuery['limit'], $currentQuery['sort'], $currentQuery['filteringLabel']);
|
||||||
if (!empty($filteringContext['filterCondition'])) { // PHP replaces `.` by `_` when fetching the request parameter
|
if (!empty($filteringContext['filterCondition'])) { // PHP replaces `.` by `_` when fetching the request parameter
|
||||||
$currentFilteringContext = [];
|
$currentFilteringContext = [];
|
||||||
foreach ($filteringContext['filterCondition'] as $currentFilteringContextKey => $value) {
|
foreach ($filteringContext['filterCondition'] as $currentFilteringContextKey => $value) {
|
||||||
|
@ -18,7 +19,14 @@
|
||||||
$currentFilteringContext = $filteringContext['filterCondition'];
|
$currentFilteringContext = $filteringContext['filterCondition'];
|
||||||
}
|
}
|
||||||
$contextArray[] = [
|
$contextArray[] = [
|
||||||
'active' => $currentQuery == $currentFilteringContext,
|
'active' => (
|
||||||
|
(
|
||||||
|
$currentQuery == $currentFilteringContext && // query conditions match
|
||||||
|
!isset($filteringContext['filterConditionFunction']) && // not a custom filtering
|
||||||
|
empty($filteringLabel) // do not check `All` by default
|
||||||
|
) ||
|
||||||
|
$filteringContext['label'] == $filteringLabel // labels should not be duplicated
|
||||||
|
),
|
||||||
'isFilter' => true,
|
'isFilter' => true,
|
||||||
'onClick' => 'changeIndexContext',
|
'onClick' => 'changeIndexContext',
|
||||||
'onClickParams' => [
|
'onClickParams' => [
|
||||||
|
|
Loading…
Reference in New Issue