From 46bafa045fe139105c4f2143fb008cc1c1c508a2 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Thu, 16 Nov 2023 09:02:40 +0100 Subject: [PATCH 1/9] chg: [crud:index] Include all meta-fields regardless of user's preference when in REST context --- src/Controller/Component/CRUDComponent.php | 10 +++++++--- src/Model/Entity/AppModel.php | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index 8774d32..2a74da4 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -96,8 +96,9 @@ class CRUDComponent extends Component $query->order($sort . ' ' . $direction); } } + $isRestOrCSV = $this->Controller->ParamHandler->isRest() || $this->request->is('csv'); if ($this->metaFieldsSupported()) { - $query = $this->includeRequestedMetaFields($query); + $query = $this->includeRequestedMetaFields($query, $isRestOrCSV); } if (!$this->Controller->ParamHandler->isRest()) { @@ -107,7 +108,7 @@ class CRUDComponent extends Component } $data = $this->Controller->paginate($query, $this->Controller->paginate ?? []); $totalCount = $this->Controller->getRequest()->getAttribute('paging')[$this->TableAlias]['count']; - if ($this->Controller->ParamHandler->isRest() || $this->request->is('csv')) { + if ($isRestOrCSV) { if (isset($options['hidden'])) { $data->each(function($value, $key) use ($options) { $hidden = is_array($options['hidden']) ? $options['hidden'] : [$options['hidden']]; @@ -777,8 +778,11 @@ class CRUDComponent extends Component return $data; } - protected function includeRequestedMetaFields($query) + protected function includeRequestedMetaFields($query, $isREST=false) { + if (!empty($isREST)) { + return $query->contain(['MetaFields']); + } $user = $this->Controller->ACL->getUser(); $tableSettings = IndexSetting::getTableSetting($user, $this->Table); if (empty($tableSettings['visible_meta_column'])) { diff --git a/src/Model/Entity/AppModel.php b/src/Model/Entity/AppModel.php index 1cf31bc..0b95b9e 100644 --- a/src/Model/Entity/AppModel.php +++ b/src/Model/Entity/AppModel.php @@ -78,7 +78,7 @@ class AppModel extends Entity $this->meta_fields[$i]['template_namespace'] = $templates[$templateDirectoryId]['namespace']; } } - if (!empty($options['smartFlattenMetafields'])) { + if (!empty($this->meta_fields) && !empty($options['smartFlattenMetafields'])) { $smartFlatten = []; foreach ($this->meta_fields as $metafield) { $key = "{$metafield['template_name']}_v{$metafield['template_version']}:{$metafield['field']}"; From 96e4aceb286e684e71e012c92df8ef909b6b3eb1 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 12 Dec 2023 09:02:43 +0100 Subject: [PATCH 2/9] fix: [behavior:notifyAdmins] Fixed typo in date serialization --- src/Model/Behavior/NotifyAdminsBehavior.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Behavior/NotifyAdminsBehavior.php b/src/Model/Behavior/NotifyAdminsBehavior.php index 767d106..ad83888 100644 --- a/src/Model/Behavior/NotifyAdminsBehavior.php +++ b/src/Model/Behavior/NotifyAdminsBehavior.php @@ -322,7 +322,7 @@ class NotifyAdminsBehavior extends Behavior } else if (is_object($fieldValue)) { switch (get_class($fieldValue)) { case 'Cake\I18n\FrozenTime': - return $fieldValue->i18nFormat('yyyy-mm-dd HH:mm:ss'); + return $fieldValue->i18nFormat('yyyy-MM-dd HH:mm:ss'); } } else { return strval($fieldValue); From 42a5bd03c6d24fa6f4b9d8e8d9ca2b9d214580c7 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 12 Dec 2023 09:13:02 +0100 Subject: [PATCH 3/9] fix: [individual:validation] Enforce email format to be a valid email address --- src/Model/Table/IndividualsTable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Model/Table/IndividualsTable.php b/src/Model/Table/IndividualsTable.php index aa13727..e3ab184 100644 --- a/src/Model/Table/IndividualsTable.php +++ b/src/Model/Table/IndividualsTable.php @@ -57,7 +57,7 @@ class IndividualsTable extends AppTable public function validationDefault(Validator $validator): Validator { $validator - ->notEmptyString('email') + ->email('email') ->requirePresence(['email'], 'create'); return $validator; } From 2274fff424475a4e503480d6d357eed34c79ef6d Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 12 Dec 2023 09:51:38 +0100 Subject: [PATCH 4/9] fix: [component:CRUD] Make sure not to override table aliases when paginating --- src/Controller/Component/CRUDComponent.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index 2a74da4..f8a227d 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -90,7 +90,9 @@ class CRUDComponent extends Component if ($this->_validOrderFields($sort) && ($direction === 'asc' || $direction === 'desc')) { $sort = explode('.', $sort); if (count($sort) > 1) { - $sort[0] = Inflector::camelize(Inflector::pluralize($sort[0])); + if ($sort[0] != $this->Table->getAlias()) { + $sort[0] = Inflector::camelize(Inflector::pluralize($sort[0])); + } } $sort = implode('.', $sort); $query->order($sort . ' ' . $direction); From d823190624f7a3a98b590c4042e39d764f7ba663 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 12 Dec 2023 09:52:33 +0100 Subject: [PATCH 5/9] fix: [inboxes:index] Fixed pagination target key --- src/Controller/InboxController.php | 2 +- templates/Inbox/index.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Controller/InboxController.php b/src/Controller/InboxController.php index 2abfb47..77fac8f 100644 --- a/src/Controller/InboxController.php +++ b/src/Controller/InboxController.php @@ -23,7 +23,7 @@ class InboxController extends AppController public $paginate = [ 'order' => [ 'Inbox.created' => 'desc' - ] + ], ]; public function beforeFilter(EventInterface $event) diff --git a/templates/Inbox/index.php b/templates/Inbox/index.php index bee8244..a22e106 100644 --- a/templates/Inbox/index.php +++ b/templates/Inbox/index.php @@ -44,12 +44,12 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'fields' => [ [ 'name' => '#', - 'sort' => 'id', + 'sort' => 'Inbox.id', 'data_path' => 'id', ], [ 'name' => 'created', - 'sort' => 'created', + 'sort' => 'Inbox.created', 'data_path' => 'created', 'element' => 'datetime' ], @@ -87,7 +87,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], [ 'name' => 'user', - 'sort' => 'user_id', + 'sort' => 'Inbox.user_id', 'data_path' => 'user', 'element' => 'user' ], From cadb56eb077d3e77db32c49f7457b1ab014ad0d9 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 13 Dec 2023 14:30:53 +0100 Subject: [PATCH 6/9] new: [CRUD:Filtering] Added support of options in index filtering modal --- src/Controller/Component/CRUDComponent.php | 18 ++++++++++++-- templates/genericTemplates/filters.php | 29 ++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index f8a227d..4922209 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -51,7 +51,9 @@ class CRUDComponent extends Component $options['filters'][] = 'filteringTags'; } $optionFilters = []; - $optionFilters += empty($options['filters']) ? [] : $options['filters']; + $optionFilters += array_map(function($filter) { + return is_array($filter) ? $filter['name'] : $filter; + }, empty($options['filters']) ? [] : $options['filters']); foreach ($optionFilters as $i => $filter) { $optionFilters[] = "{$filter} !="; $optionFilters[] = "{$filter} >="; @@ -259,6 +261,13 @@ class CRUDComponent extends Component foreach ($filtersConfigRaw as $fieldConfig) { if (is_array($fieldConfig)) { $filtersConfig[$fieldConfig['name']] = $fieldConfig; + if (!empty($fieldConfig['options'])) { + if (is_string($fieldConfig['options'])) { + $filtersConfig[$fieldConfig['name']]['options'] = $this->Table->{$fieldConfig['options']}($this->Controller->ACL->getUser()); + } else { + $filtersConfig[$fieldConfig['name']]['options'] = $fieldConfig['options']; + } + } } else { $filtersConfig[$fieldConfig] = ['name' => $fieldConfig]; } @@ -1368,7 +1377,12 @@ class CRUDComponent extends Component protected function setRelatedCondition($query, $modelName, $fieldName, $filterValue) { return $query->matching($modelName, function (\Cake\ORM\Query $q) use ($fieldName, $filterValue) { - return $this->setValueCondition($q, $fieldName, $filterValue); + if (is_array($filterValue)) { + $query = $this->setInCondition($q, $fieldName, $filterValue); + } else { + $query = $this->setValueCondition($q, $fieldName, $filterValue); + } + return $query; }); } diff --git a/templates/genericTemplates/filters.php b/templates/genericTemplates/filters.php index 4cb1df0..6117b9e 100644 --- a/templates/genericTemplates/filters.php +++ b/templates/genericTemplates/filters.php @@ -56,13 +56,22 @@ $filteringForm = $this->Bootstrap->table( 'label' => '', 'class' => 'fieldValue form-control-sm' ]; - if (!empty($filtersConfig[$fieldName]['multiple'])) { - $fieldData['type'] = 'dropdown'; - $fieldData['multiple'] = true; - $fieldData['select2'] = [ - 'tags' => true, - 'tokenSeparators' => [',', ' '], - ]; + if (!empty($filtersConfig[$fieldName])) { + if (!empty($filtersConfig[$fieldName]['options'])) { + $fieldData['options'] = $filtersConfig[$fieldName]['options']; + } + if (!empty($filtersConfig[$fieldName]['select2'])) { + $fieldData['type'] = 'dropdown'; + $fieldData['select2'] = true; + } + if (!empty($filtersConfig[$fieldName]['multiple'])) { + $fieldData['type'] = 'dropdown'; + $fieldData['multiple'] = true; + $fieldData['select2'] = [ + 'tags' => true, + 'tokenSeparators' => [',', ' '], + ]; + } } $this->Form->setTemplates([ 'formGroup' => '
{{input}}
', @@ -187,6 +196,7 @@ echo $this->Bootstrap->modal([ $row = $filteringTable.find('td > span.fieldName').filter(function() { return $(this).data('fieldname') == field }).closest('tr') + $row.addClass('table-warning') $row.find('.fieldOperator').val(operator) const $formElement = $row.find('.fieldValue'); if ($formElement.attr('type') === 'datetime-local') { @@ -195,7 +205,10 @@ echo $this->Bootstrap->modal([ let newOptions = []; value.forEach(aValue => { const existingOption = $formElement.find('option').filter(function() { - return $(this).val() === aValue + if ($(this).val() === aValue) { + $(this).prop('selected', true) + return true + } }) if (existingOption.length == 0) { newOptions.push(new Option(aValue, aValue, true, true)) From eefe6b22cc2eccd1fb20f8ea35738fd35725ed5f Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 13 Dec 2023 14:31:55 +0100 Subject: [PATCH 7/9] chg: [inboxes:filtering] Populate username with eligible users in filtering modal --- src/Controller/InboxController.php | 2 +- src/Model/Table/InboxTable.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Controller/InboxController.php b/src/Controller/InboxController.php index 77fac8f..ddbe8ad 100644 --- a/src/Controller/InboxController.php +++ b/src/Controller/InboxController.php @@ -16,7 +16,7 @@ use Cake\Http\Exception\ForbiddenException; class InboxController extends AppController { - public $filterFields = ['scope', 'action', 'Inbox.created', 'severity', 'title', 'origin', 'message', 'Users.id', 'Users.username',]; + public $filterFields = ['scope', 'action', 'Inbox.created', 'severity', 'title', 'origin', 'message', 'Users.id', ['name' => 'Users.username', 'multiple' => true, 'options' => 'getAllUsername', 'select2' => true],]; public $quickFilterFields = ['scope', 'action', ['title' => true], ['message' => true], 'origin']; public $containFields = ['Users']; diff --git a/src/Model/Table/InboxTable.php b/src/Model/Table/InboxTable.php index a7626e7..4a90910 100644 --- a/src/Model/Table/InboxTable.php +++ b/src/Model/Table/InboxTable.php @@ -2,6 +2,7 @@ namespace App\Model\Table; use App\Model\Table\AppTable; +use Cake\Utility\Hash; use Cake\Database\Schema\TableSchemaInterface; use Cake\Database\Type; use Cake\ORM\Table; @@ -72,6 +73,17 @@ class InboxTable extends AppTable return $rules; } + public function getAllUsername($currentUser): array + { + $this->Users = \Cake\ORM\TableRegistry::getTableLocator()->get('Users'); + $conditions = []; + if (empty($currentUser['role']['perm_admin'])) { + $conditions['organisation_id IN'] = [$currentUser['organisation_id']]; + } + $users = $this->Users->find()->where($conditions)->all()->extract('username')->toList(); + return Hash::combine($users, '{n}', '{n}'); + } + public function checkUserBelongsToBroodOwnerOrg($user, $entryData) { $this->Broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods'); $this->Individuals = \Cake\ORM\TableRegistry::getTableLocator()->get('Individuals'); From ecc421b3267eb0dc6d64bc1c0b1234bad9e64896 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Wed, 13 Dec 2023 15:01:23 +0100 Subject: [PATCH 8/9] new: [settings:inbox.data_change_notify_for_all] Added setting to be more verbose for data changes --- src/Model/Behavior/NotifyAdminsBehavior.php | 5 ++++- .../CerebrateSettingsProvider.php | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Model/Behavior/NotifyAdminsBehavior.php b/src/Model/Behavior/NotifyAdminsBehavior.php index ad83888..d07df96 100644 --- a/src/Model/Behavior/NotifyAdminsBehavior.php +++ b/src/Model/Behavior/NotifyAdminsBehavior.php @@ -73,7 +73,10 @@ class NotifyAdminsBehavior extends Behavior public function isNotificationAllowed(EventInterface $event, EntityInterface $entity, ArrayObject $options): bool { $loggedUser = Configure::read('loggedUser'); - if (empty($loggedUser) || !empty($loggedUser['role']['perm_admin']) || !empty($loggedUser['role']['perm_sync'])) { + if ( + empty(Configure::read('inbox.data_change_notify_for_all', false)) && + (empty($loggedUser) || !empty($loggedUser['role']['perm_admin']) || !empty($loggedUser['role']['perm_sync'])) + ) { return false; } return true; diff --git a/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php b/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php index 1876c7b..dacfea9 100644 --- a/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php +++ b/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php @@ -351,7 +351,20 @@ class CerebrateSettingsProvider extends BaseSettingsProvider ], ] ] - ] + ], + 'Inbox' => [ + 'Data change notification' => [ + 'Data change notification' => [ + 'inbox.data_change_notify_for_all' => [ + 'name' => __('Notify data modification for all Users'), + 'type' => 'boolean', + 'description' => __('Turning this option ON will alert administrators whenever data is modified, irrespective of the user\'s role responsible for the modification.'), + 'default' => false, + 'severity' => 'warning', + ], + ], + ], + ], /* 'Features' => [ 'Demo Settings' => [ From 76b682e3997e009c0d6677056427527d86dae68c Mon Sep 17 00:00:00 2001 From: iglocska Date: Wed, 20 Dec 2023 10:18:16 +0100 Subject: [PATCH 9/9] chg: [version] bump --- src/VERSION.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VERSION.json b/src/VERSION.json index 94207f1..d03174e 100644 --- a/src/VERSION.json +++ b/src/VERSION.json @@ -1,4 +1,4 @@ { - "version": "1.17", + "version": "1.18", "application": "Cerebrate" }