From 7d6696e0792d3c6f8c9ef2651bd629caed6f7978 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 14 Nov 2022 09:04:35 +0100 Subject: [PATCH 01/14] new: [metaFields] Adding support of sane_default + improving form & crud - WiP --- .../20221028000000_MetaFieldSaneDefault.php | 34 +++++++ src/Model/Entity/MetaTemplateField.php | 27 ++++++ src/Model/Table/MetaTemplateFieldsTable.php | 2 + templates/MetaTemplateFields/index.php | 8 ++ .../Form/Fields/dropdownField.php | 97 ++++++++++++++++--- .../genericElements/Form/metaTemplateForm.php | 13 ++- .../genericElements/Form/multiFieldButton.php | 29 +++--- .../Form/multiFieldScaffold.php | 19 +++- 8 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 config/Migrations/20221028000000_MetaFieldSaneDefault.php diff --git a/config/Migrations/20221028000000_MetaFieldSaneDefault.php b/config/Migrations/20221028000000_MetaFieldSaneDefault.php new file mode 100644 index 0000000..b59149c --- /dev/null +++ b/config/Migrations/20221028000000_MetaFieldSaneDefault.php @@ -0,0 +1,34 @@ +table('meta_template_fields')->hasColumn('sane_default'); + if (!$exists) { + $this->table('meta_template_fields') + ->addColumn('sane_default', 'text', [ + 'default' => null, + 'null' => true, + 'limit' => MysqlAdapter::TEXT_LONG, + 'comment' => 'List of sane default values to be proposed', + ]) + ->addColumn('values_list', 'text', [ + 'default' => null, + 'null' => true, + 'limit' => MysqlAdapter::TEXT_LONG, + 'comment' => 'List of values that have to be used', + ]) + ->update(); + } + } +} diff --git a/src/Model/Entity/MetaTemplateField.php b/src/Model/Entity/MetaTemplateField.php index 7baf30a..570fef9 100644 --- a/src/Model/Entity/MetaTemplateField.php +++ b/src/Model/Entity/MetaTemplateField.php @@ -7,5 +7,32 @@ use Cake\ORM\Entity; class MetaTemplateField extends AppModel { + protected $_virtual = ['form_type', 'form_options', ]; + + protected function _getFormType() + { + $formType = 'text'; + if (!empty($this->sane_default) || !empty($this->values_list)) { + $formType = 'dropdown'; + } + return $formType; + } + + protected function _getFormOptions() + { + $formOptions = []; + if ($this->formType === 'dropdown') { + $selectOptions = !empty($this->sane_default) ? $this->sane_default : $this->values_list; + $selectOptions = array_combine($selectOptions, $selectOptions); + if (!empty($this->sane_default)) { + // $selectOptions['_custom'] = __('-- custom value --'); + $selectOptions[] = ['value' => '_custom', 'text' => __('-- custom value --'), 'class' => 'custom-value']; + } + $selectOptions[''] = __('-- no value --'); + $formOptions['options'] = $selectOptions; + // $formOptions['empty'] = [['value' => '', 'text' => __('-- select an options --'), 'hidden disabled selected value']]; + } + return $formOptions; + } } diff --git a/src/Model/Table/MetaTemplateFieldsTable.php b/src/Model/Table/MetaTemplateFieldsTable.php index 626a96c..857361c 100644 --- a/src/Model/Table/MetaTemplateFieldsTable.php +++ b/src/Model/Table/MetaTemplateFieldsTable.php @@ -27,6 +27,8 @@ class MetaTemplateFieldsTable extends AppTable $this->hasMany('MetaFields'); $this->setDisplayField('field'); + $this->getSchema()->setColumnType('sane_default', 'json'); + $this->getSchema()->setColumnType('values_list', 'json'); $this->loadTypeHandlers(); } diff --git a/templates/MetaTemplateFields/index.php b/templates/MetaTemplateFields/index.php index 4071d13..5f41b21 100644 --- a/templates/MetaTemplateFields/index.php +++ b/templates/MetaTemplateFields/index.php @@ -30,6 +30,14 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'data_path' => 'multiple', 'field' => 'textarea' ], + [ + 'name' => __('Sane defaults'), + 'data_path' => 'sane_default' + ], + [ + 'name' => __('Values List'), + 'data_path' => 'values_list' + ], [ 'name' => __('Validation regex'), 'sort' => 'regex', diff --git a/templates/element/genericElements/Form/Fields/dropdownField.php b/templates/element/genericElements/Form/Fields/dropdownField.php index 26e8e88..b8d81b2 100644 --- a/templates/element/genericElements/Form/Fields/dropdownField.php +++ b/templates/element/genericElements/Form/Fields/dropdownField.php @@ -1,14 +1,87 @@ $fieldData['options'], - 'empty' => $fieldData['empty'] ?? false, - 'value' => $fieldData['value'] ?? null, - 'multiple' => $fieldData['multiple'] ?? false, - 'disabled' => $fieldData['disabled'] ?? false, - 'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select', - 'default' => ($fieldData['default'] ?? null) - ]; - if (!empty($fieldData['label'])) { - $controlParams['label'] = $fieldData['label']; +$seed = 's-' . mt_rand(); +$controlParams = [ + 'type' => 'select', + 'options' => $fieldData['options'], + 'empty' => $fieldData['empty'] ?? false, + 'value' => $fieldData['value'] ?? null, + 'multiple' => $fieldData['multiple'] ?? false, + 'disabled' => $fieldData['disabled'] ?? false, + 'class' => ($fieldData['class'] ?? '') . ' formDropdown form-select', + 'default' => $fieldData['default'] ?? '', +]; +if (!empty($fieldData['field'])) { // used for multi meta-field form + $controlParams['field'] = $fieldData['field']; +} +if (!empty($fieldData['label'])) { + $controlParams['label'] = $fieldData['label']; +} +if ($controlParams['options'] instanceof \Cake\ORM\Query) { + $controlParams['options'] = $controlParams['options']->all()->toList(); +} +if (in_array('_custom', array_keys($controlParams['options']))) { + $customInputValue = $this->Form->getSourceValue($fieldData['field']); + if (!in_array($customInputValue, $controlParams['options'])) { + $controlParams['options'] = array_map(function ($option) { + if (is_array($option) && $option['value'] == '_custom') { + $option[] = 'selected'; + } + return $option; + }, $controlParams['options']); + } else { + $customInputValue = ''; } - echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData); + $customControlParams = [ + 'value' => $fieldData['value'] ?? null, + 'class' => 'd-none', + ]; + $controlParams['class'] .= ' dropdown-custom-value' . "-$seed"; + $adaptedField = $fieldData['field'] . '_custom'; + $controlParams['templates']['formGroup'] = sprintf( + '
{{input}}{{error}}%s
', + sprintf('', h($adaptedField), h($customInputValue)) + ); +} +echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData); +?> + + + + \ No newline at end of file diff --git a/templates/element/genericElements/Form/metaTemplateForm.php b/templates/element/genericElements/Form/metaTemplateForm.php index 0a4d934..56e0ac4 100644 --- a/templates/element/genericElements/Form/metaTemplateForm.php +++ b/templates/element/genericElements/Form/metaTemplateForm.php @@ -31,7 +31,11 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { $metaField = reset($metaTemplateField->metaFields); $fieldData = [ 'label' => $metaTemplateField->label, + 'type' => $metaTemplateField->formType, ]; + if ($metaTemplateField->formType === 'dropdown') { + $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); + } if (isset($metaField->id)) { $fieldData['field'] = sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', $metaField->meta_template_id, $metaField->meta_template_field_id, $metaField->id); } else { @@ -63,7 +67,11 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { $fieldData = [ 'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new.0', $metaTemplateField->meta_template_id, $metaTemplateField->id), 'label' => $metaTemplateField->label, + 'type' => $metaTemplateField->formType, ]; + if ($metaTemplateField->formType === 'dropdown') { + $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); + } $fieldsHtml .= $this->element( 'genericElements/Form/fieldScaffold', [ @@ -74,4 +82,7 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { } } } -echo $fieldsHtml; \ No newline at end of file +$fieldContainer = $this->Bootstrap->genNode('div', [ + 'class' => [], +], $fieldsHtml); +echo $fieldContainer; \ No newline at end of file diff --git a/templates/element/genericElements/Form/multiFieldButton.php b/templates/element/genericElements/Form/multiFieldButton.php index c7c778b..9cfd348 100644 --- a/templates/element/genericElements/Form/multiFieldButton.php +++ b/templates/element/genericElements/Form/multiFieldButton.php @@ -45,6 +45,8 @@ $seed = 'mfb-' . mt_rand(); $clonedContainer .removeClass('has-error') .find('.error-message ').remove() + $clonedContainer + .find('label.form-label').text('') const $clonedInput = $clonedContainer.find('input, select') if ($clonedInput.length > 0) { const injectedTemplateId = $clicked.closest('.multi-metafields-container').find('.new-metafield').length @@ -54,18 +56,21 @@ $seed = 'mfb-' . mt_rand(); } } - function adjustClonedInputAttr($input, injectedTemplateId) { - let explodedPath = $input.attr('field').split('.').splice(0, 5) - explodedPath.push('new', injectedTemplateId) - dottedPathStr = explodedPath.join('.') - brackettedPathStr = explodedPath.map((elem, i) => { - return i == 0 ? elem : `[${elem}]` - }).join('') - $input.attr('id', dottedPathStr) - .attr('field', dottedPathStr) - .attr('name', brackettedPathStr) - .val('') - .removeClass('is-invalid') + function adjustClonedInputAttr($inputs, injectedTemplateId) { + $inputs.each(function() { + const $input = $(this) + let explodedPath = $input.attr('field').split('.').splice(0, 5) + explodedPath.push('new', injectedTemplateId) + dottedPathStr = explodedPath.join('.') + brackettedPathStr = explodedPath.map((elem, i) => { + return i == 0 ? elem : `[${elem}]` + }).join('') + $input.attr('id', dottedPathStr) + .attr('field', dottedPathStr) + .attr('name', brackettedPathStr) + .val('') + .removeClass('is-invalid') + }) } })() \ No newline at end of file diff --git a/templates/element/genericElements/Form/multiFieldScaffold.php b/templates/element/genericElements/Form/multiFieldScaffold.php index 3a73125..89b405c 100644 --- a/templates/element/genericElements/Form/multiFieldScaffold.php +++ b/templates/element/genericElements/Form/multiFieldScaffold.php @@ -22,6 +22,7 @@ if (!empty($metaFieldsEntities)) { $metaFieldsEntity->meta_template_field_id, $metaFieldsEntity->id ), + 'type' => $metaTemplateField->formType, ]; if($metaFieldsEntity->isNew()) { $fieldData['field'] = sprintf( @@ -35,6 +36,9 @@ if (!empty($metaFieldsEntities)) { if ($labelPrintedOnce) { // Only the first input can have a label $fieldData['label'] = false; } + if ($metaTemplateField->formType === 'dropdown') { + $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); + } $labelPrintedOnce = true; $fieldsHtml .= $this->element( 'genericElements/Form/fieldScaffold', @@ -49,14 +53,19 @@ if (!empty($metaTemplateField) && !empty($multiple)) { // Add multiple field but $metaTemplateField->label = Inflector::humanize($metaTemplateField->field); $emptyMetaFieldInput = ''; if (empty($metaFieldsEntities)) { // Include editable field for meta-template not containing a meta-field + $fieldData = [ + 'label' => $metaTemplateField->label, + 'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new.0', $metaTemplateField->meta_template_id, $metaTemplateField->id), + 'class' => 'new-metafield', + 'type' => $metaTemplateField->formType, + ]; + if ($metaTemplateField->formType === 'dropdown') { + $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); + } $emptyMetaFieldInput = $this->element( 'genericElements/Form/fieldScaffold', [ - 'fieldData' => [ - 'label' => $metaTemplateField->label, - 'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new.0', $metaTemplateField->meta_template_id, $metaTemplateField->id), - 'class' => 'new-metafield', - ], + 'fieldData' => $fieldData, 'form' => $form, ] ); From 1d6187198f1c9e55b51d05c88147a79927243475 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 14 Nov 2022 15:34:30 +0100 Subject: [PATCH 02/14] fix: [metafields:dropdown] Patched saving multiple fields with custom value --- .../genericElements/Form/Fields/dropdownField.php | 11 ++--------- .../element/genericElements/Form/multiFieldButton.php | 10 +++++++--- .../genericElements/Form/multiFieldScaffold.php | 2 +- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/templates/element/genericElements/Form/Fields/dropdownField.php b/templates/element/genericElements/Form/Fields/dropdownField.php index b8d81b2..b71b15b 100644 --- a/templates/element/genericElements/Form/Fields/dropdownField.php +++ b/templates/element/genericElements/Form/Fields/dropdownField.php @@ -31,10 +31,6 @@ if (in_array('_custom', array_keys($controlParams['options']))) { } else { $customInputValue = ''; } - $customControlParams = [ - 'value' => $fieldData['value'] ?? null, - 'class' => 'd-none', - ]; $controlParams['class'] .= ' dropdown-custom-value' . "-$seed"; $adaptedField = $fieldData['field'] . '_custom'; $controlParams['templates']['formGroup'] = sprintf( @@ -52,17 +48,14 @@ echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $f toggleFreetextSelectField($select[0]); $select.attr('onclick', 'toggleFreetextSelectField(this)') $select.parent().find('input.custom-value').attr('oninput', 'updateAssociatedSelect(this)') - // updateAssociatedSelect($select.parent().find('input.custom-value')[0]) - - // Multiple saves in dropdown doesn't work - // But multiple saves for custom works but save the first element as `_custom` + updateAssociatedSelect($select.parent().find('input.custom-value')[0]) }) })() function toggleFreetextSelectField(selectEl) { const $select = $(selectEl) - const show = $select.val() == '_custom' + const show = $select.find('option:selected').hasClass('custom-value') const $container = $(selectEl).parent() let $freetextInput = $container.find('input.custom-value') if (show) { diff --git a/templates/element/genericElements/Form/multiFieldButton.php b/templates/element/genericElements/Form/multiFieldButton.php index 9cfd348..ddc17ca 100644 --- a/templates/element/genericElements/Form/multiFieldButton.php +++ b/templates/element/genericElements/Form/multiFieldButton.php @@ -65,9 +65,13 @@ $seed = 'mfb-' . mt_rand(); brackettedPathStr = explodedPath.map((elem, i) => { return i == 0 ? elem : `[${elem}]` }).join('') - $input.attr('id', dottedPathStr) - .attr('field', dottedPathStr) - .attr('name', brackettedPathStr) + const attrs = ['id', 'field', 'name'] + attrs.forEach((attr) => { + if ($input.attr(attr) !== undefined) { + $input.attr(attr, attr === 'name' ? brackettedPathStr : dottedPathStr) + } + }) + $input .val('') .removeClass('is-invalid') }) diff --git a/templates/element/genericElements/Form/multiFieldScaffold.php b/templates/element/genericElements/Form/multiFieldScaffold.php index 89b405c..3099813 100644 --- a/templates/element/genericElements/Form/multiFieldScaffold.php +++ b/templates/element/genericElements/Form/multiFieldScaffold.php @@ -34,7 +34,7 @@ if (!empty($metaFieldsEntities)) { $fieldData['class'] = 'new-metafield'; } if ($labelPrintedOnce) { // Only the first input can have a label - $fieldData['label'] = false; + $fieldData['label'] = ['text' => '']; } if ($metaTemplateField->formType === 'dropdown') { $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); From b6fdf37d54b8cd40be7d3a9b4fa2ded6566547e1 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 14 Nov 2022 15:34:30 +0100 Subject: [PATCH 03/14] fix: [metafields:dropdown] Patched saving multiple fields with custom value --- src/Model/Entity/MetaTemplateField.php | 2 -- .../genericElements/Form/Fields/dropdownField.php | 11 ++--------- .../element/genericElements/Form/multiFieldButton.php | 10 +++++++--- .../genericElements/Form/multiFieldScaffold.php | 2 +- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Model/Entity/MetaTemplateField.php b/src/Model/Entity/MetaTemplateField.php index 570fef9..110f843 100644 --- a/src/Model/Entity/MetaTemplateField.php +++ b/src/Model/Entity/MetaTemplateField.php @@ -25,12 +25,10 @@ class MetaTemplateField extends AppModel $selectOptions = !empty($this->sane_default) ? $this->sane_default : $this->values_list; $selectOptions = array_combine($selectOptions, $selectOptions); if (!empty($this->sane_default)) { - // $selectOptions['_custom'] = __('-- custom value --'); $selectOptions[] = ['value' => '_custom', 'text' => __('-- custom value --'), 'class' => 'custom-value']; } $selectOptions[''] = __('-- no value --'); $formOptions['options'] = $selectOptions; - // $formOptions['empty'] = [['value' => '', 'text' => __('-- select an options --'), 'hidden disabled selected value']]; } return $formOptions; } diff --git a/templates/element/genericElements/Form/Fields/dropdownField.php b/templates/element/genericElements/Form/Fields/dropdownField.php index b8d81b2..b71b15b 100644 --- a/templates/element/genericElements/Form/Fields/dropdownField.php +++ b/templates/element/genericElements/Form/Fields/dropdownField.php @@ -31,10 +31,6 @@ if (in_array('_custom', array_keys($controlParams['options']))) { } else { $customInputValue = ''; } - $customControlParams = [ - 'value' => $fieldData['value'] ?? null, - 'class' => 'd-none', - ]; $controlParams['class'] .= ' dropdown-custom-value' . "-$seed"; $adaptedField = $fieldData['field'] . '_custom'; $controlParams['templates']['formGroup'] = sprintf( @@ -52,17 +48,14 @@ echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $f toggleFreetextSelectField($select[0]); $select.attr('onclick', 'toggleFreetextSelectField(this)') $select.parent().find('input.custom-value').attr('oninput', 'updateAssociatedSelect(this)') - // updateAssociatedSelect($select.parent().find('input.custom-value')[0]) - - // Multiple saves in dropdown doesn't work - // But multiple saves for custom works but save the first element as `_custom` + updateAssociatedSelect($select.parent().find('input.custom-value')[0]) }) })() function toggleFreetextSelectField(selectEl) { const $select = $(selectEl) - const show = $select.val() == '_custom' + const show = $select.find('option:selected').hasClass('custom-value') const $container = $(selectEl).parent() let $freetextInput = $container.find('input.custom-value') if (show) { diff --git a/templates/element/genericElements/Form/multiFieldButton.php b/templates/element/genericElements/Form/multiFieldButton.php index 9cfd348..ddc17ca 100644 --- a/templates/element/genericElements/Form/multiFieldButton.php +++ b/templates/element/genericElements/Form/multiFieldButton.php @@ -65,9 +65,13 @@ $seed = 'mfb-' . mt_rand(); brackettedPathStr = explodedPath.map((elem, i) => { return i == 0 ? elem : `[${elem}]` }).join('') - $input.attr('id', dottedPathStr) - .attr('field', dottedPathStr) - .attr('name', brackettedPathStr) + const attrs = ['id', 'field', 'name'] + attrs.forEach((attr) => { + if ($input.attr(attr) !== undefined) { + $input.attr(attr, attr === 'name' ? brackettedPathStr : dottedPathStr) + } + }) + $input .val('') .removeClass('is-invalid') }) diff --git a/templates/element/genericElements/Form/multiFieldScaffold.php b/templates/element/genericElements/Form/multiFieldScaffold.php index 89b405c..3099813 100644 --- a/templates/element/genericElements/Form/multiFieldScaffold.php +++ b/templates/element/genericElements/Form/multiFieldScaffold.php @@ -34,7 +34,7 @@ if (!empty($metaFieldsEntities)) { $fieldData['class'] = 'new-metafield'; } if ($labelPrintedOnce) { // Only the first input can have a label - $fieldData['label'] = false; + $fieldData['label'] = ['text' => '']; } if ($metaTemplateField->formType === 'dropdown') { $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); From 84069cfe4074e2ef268a2da3320a80015ab1fb00 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 14 Nov 2022 15:45:28 +0100 Subject: [PATCH 04/14] chg: [metaTemplateField] More generic way to specify form type --- src/Model/Entity/MetaTemplateField.php | 2 ++ .../element/genericElements/Form/metaTemplateForm.php | 10 ++-------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Model/Entity/MetaTemplateField.php b/src/Model/Entity/MetaTemplateField.php index 110f843..1a355d8 100644 --- a/src/Model/Entity/MetaTemplateField.php +++ b/src/Model/Entity/MetaTemplateField.php @@ -14,6 +14,8 @@ class MetaTemplateField extends AppModel $formType = 'text'; if (!empty($this->sane_default) || !empty($this->values_list)) { $formType = 'dropdown'; + } else if ($this->type === 'boolean') { + $formType = 'checkbox'; } return $formType; } diff --git a/templates/element/genericElements/Form/metaTemplateForm.php b/templates/element/genericElements/Form/metaTemplateForm.php index c09982d..f52cbb2 100644 --- a/templates/element/genericElements/Form/metaTemplateForm.php +++ b/templates/element/genericElements/Form/metaTemplateForm.php @@ -33,7 +33,7 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { 'label' => $metaTemplateField->label, 'type' => $metaTemplateField->formType, ]; - if ($metaTemplateField->formType === 'dropdown') { + if (!empty($metaTemplateField->formOptions)) { $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); } if (isset($metaField->id)) { @@ -41,9 +41,6 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { } else { $fieldData['field'] = sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', $metaField->meta_template_id, $metaField->meta_template_field_id, array_key_first($metaTemplateField->metaFields)); } - if ($metaTemplateField->type === 'boolean') { - $fieldData['type'] = 'checkbox'; - } $this->Form->setTemplates($backupTemplates); $fieldsHtml .= $this->element( 'genericElements/Form/fieldScaffold', @@ -72,12 +69,9 @@ foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { 'label' => $metaTemplateField->label, 'type' => $metaTemplateField->formType, ]; - if ($metaTemplateField->formType === 'dropdown') { + if (!empty($metaTemplateField->formOptions)) { $fieldData = array_merge_recursive($fieldData, $metaTemplateField->formOptions); } - // if ($metaTemplateField->type === 'boolean') { - // $fieldData['type'] = 'checkbox'; - // } $fieldsHtml .= $this->element( 'genericElements/Form/fieldScaffold', [ From 0b26bd629fc8e4df7050f47bb89366f9c9311c23 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 14 Nov 2022 15:55:07 +0100 Subject: [PATCH 05/14] fix: [crud:index] requestedEntryAmount doesn't reset the query anymore --- src/Controller/Component/CRUDComponent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index c4838df..f932e5a 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -115,7 +115,7 @@ class CRUDComponent extends Component if ($this->metaFieldsSupported()) { $query = $this->includeRequestedMetaFields($query); } - $query = $this->setRequestedEntryAmount($query); + $this->setRequestedEntryAmount(); $data = $this->Controller->paginate($query, $this->Controller->paginate ?? []); if (isset($options['afterFind'])) { $function = $options['afterFind']; From 62c228c44e33185c872f0fa63fade19224048d6e Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 14 Nov 2022 16:11:18 +0100 Subject: [PATCH 06/14] chg: [auditLogs:index] Added possibility to view and filter logs base on `created` field --- src/Controller/AuditLogsController.php | 3 +-- templates/AuditLogs/index.php | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Controller/AuditLogsController.php b/src/Controller/AuditLogsController.php index fd1be94..e3e4e0c 100644 --- a/src/Controller/AuditLogsController.php +++ b/src/Controller/AuditLogsController.php @@ -8,11 +8,10 @@ use Cake\ORM\TableRegistry; use \Cake\Database\Expression\QueryExpression; use Cake\Http\Exception\UnauthorizedException; use Cake\Core\Configure; -use PhpParser\Node\Stmt\Echo_; class AuditLogsController extends AppController { - public $filterFields = ['model_id', 'model', 'request_action', 'user_id', 'model_title']; + public $filterFields = ['model_id', 'model', 'request_action', 'user_id', 'model_title', 'AuditLogs.created']; public $quickFilterFields = ['model', 'request_action', 'model_title']; public $containFields = ['Users']; diff --git a/templates/AuditLogs/index.php b/templates/AuditLogs/index.php index e46892c..52526f6 100644 --- a/templates/AuditLogs/index.php +++ b/templates/AuditLogs/index.php @@ -28,6 +28,12 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'sort' => 'request_ip', 'data_path' => 'request_ip', ], + [ + 'name' => 'created', + 'sort' => 'created', + 'data_path' => 'created', + 'element' => 'datetime' + ], [ 'name' => __('Username'), 'sort' => 'user.username', From a9e85a34d3a271c88bb2b093250b33e7565afd55 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:03:11 +0100 Subject: [PATCH 07/14] fix: [instance:settings] Revert setting back to its original in case of failure --- webroot/js/settings.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/webroot/js/settings.js b/webroot/js/settings.js index 622f9a0..e91ef81 100644 --- a/webroot/js/settings.js +++ b/webroot/js/settings.js @@ -67,14 +67,21 @@ function saveAndUpdateSetting(statusNode, $input, settingName, settingValue) { settingValue = JSON.stringify(settingValue) } saveSetting(statusNode, settingName, settingValue).then((result) => { - window.settingsFlattened[settingName] = result.data - if ($input.attr('type') == 'checkbox') { - $input.prop('checked', result.data.value == true) - } else { - $input.val(result.data.value) - } + updateSettingValue($input, settingName, result.data) + }).catch((e) => { + updateSettingValue($input, settingName, window.settingsFlattened[settingName]) + }).finally(() => { handleSettingValueChange($input) - }).catch((e) => { }) + }) +} + +function updateSettingValue($input, settingName, settingValue) { + window.settingsFlattened[settingName] = settingValue + if ($input.attr('type') == 'checkbox') { + $input.prop('checked', settingValue.value == true) + } else { + $input.val(settingValue.value) + } } function handleSettingValueChange($input) { From f5b946d5a9f21e6ef6e889ae93c1234836ad6dcb Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:11:06 +0100 Subject: [PATCH 08/14] new: [element:bootstrapUI] To create HTML from BootrstrapHelper by using element --- templates/element/bootstrapUI.php | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 templates/element/bootstrapUI.php diff --git a/templates/element/bootstrapUI.php b/templates/element/bootstrapUI.php new file mode 100644 index 0000000..b21d876 --- /dev/null +++ b/templates/element/bootstrapUI.php @@ -0,0 +1,7 @@ +Bootstrap->{$element}([ + 'text' => $text, + 'variant' => $variant, + ]); +} \ No newline at end of file From be7293a5a41f1fc19477a4d11a5864ecc6102980 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:12:03 +0100 Subject: [PATCH 09/14] new: [listTopBar:contextFilters] Added support of element to generate filter content --- .../ListTopBar/group_context_filters.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/templates/element/genericElements/ListTopBar/group_context_filters.php b/templates/element/genericElements/ListTopBar/group_context_filters.php index e441515..71b97e6 100644 --- a/templates/element/genericElements/ListTopBar/group_context_filters.php +++ b/templates/element/genericElements/ListTopBar/group_context_filters.php @@ -21,7 +21,7 @@ } else { $currentFilteringContext = $filteringContext['filterCondition']; } - $contextArray[] = [ + $contextItem = [ 'active' => ( ( $currentQuery == $currentFilteringContext && // query conditions match @@ -42,9 +42,17 @@ "#table-container-${tableRandomValue}", "#table-container-${tableRandomValue} table.table", ], - 'text' => $filteringContext['label'], 'class' => 'btn-sm' ]; + if (!empty($filteringContext['viewElement'])) { + $contextItem['html'] = $this->element( + $filteringContext['viewElement'], + $filteringContext['viewElementParams'] ?? [] + ); + } else { + $contextItem['text'] = $filteringContext['label']; + } + $contextArray[] = $contextItem; } $dataGroup = [ From 1626037239625d53b28d46e08004f0884fd7d91a Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:13:40 +0100 Subject: [PATCH 10/14] fix: [outboxProcessor:generic] Added support of severity --- .../OutboxProcessors/GenericOutboxProcessor.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libraries/default/OutboxProcessors/GenericOutboxProcessor.php b/libraries/default/OutboxProcessors/GenericOutboxProcessor.php index 98df5f3..6cd9cfb 100644 --- a/libraries/default/OutboxProcessors/GenericOutboxProcessor.php +++ b/libraries/default/OutboxProcessors/GenericOutboxProcessor.php @@ -20,9 +20,14 @@ class GenericOutboxProcessor protected $validator; protected $processingTemplate = '/genericTemplates/confirm'; protected $processingTemplatesDirectory = ROOT . '/libraries/default/OutboxProcessors/templates'; + protected $defaultSeverity; + protected $severity; + public function __construct($registerActions=false) { $this->Outbox = TableRegistry::getTableLocator()->get('Outbox'); + $this->Inbox = TableRegistry::getTableLocator()->get('Inbox'); + $this->defaultSeverity = $this->Inbox::SEVERITY_INFO; if ($registerActions) { $this->registerActionInProcessor(); } @@ -55,6 +60,10 @@ class GenericOutboxProcessor { return $this->description ?? ''; } + public function getSeverity() + { + return $this->severity ?? $this->defaultSeverity; + } protected function getProcessingTemplatePath() { @@ -77,8 +86,9 @@ class GenericOutboxProcessor $builder = new ViewBuilder(); $builder->disableAutoLayout() ->setClassName('Monad') - ->setTemplate($processingTemplate); - $view = $builder->build($viewVariables); + ->setTemplate($processingTemplate) + ->setVars($viewVariables); + $view = $builder->build(); $view->setRequest($serverRequest); return $view->render(); } @@ -193,6 +203,7 @@ class GenericOutboxProcessor $user_id = Router::getRequest()->getSession()->read('Auth.id'); $requestData['scope'] = $this->scope; $requestData['action'] = $this->action; + $requestData['severity'] = $this->getSeverity(); $requestData['user_id'] = $user_id; $request = $this->generateRequest($requestData); $savedRequest = $this->Outbox->createEntry($request); From 407f827e24dcbd2479688740965446a07483e6e9 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:14:25 +0100 Subject: [PATCH 11/14] chg: [processors] Added adequate severity for some inbox/outbox processors --- libraries/default/InboxProcessors/LocalToolInboxProcessor.php | 3 +++ .../default/InboxProcessors/SynchronisationInboxProcessor.php | 1 + libraries/default/InboxProcessors/UserInboxProcessor.php | 1 + libraries/default/OutboxProcessors/BroodsOutboxProcessor.php | 1 + 4 files changed, 6 insertions(+) diff --git a/libraries/default/InboxProcessors/LocalToolInboxProcessor.php b/libraries/default/InboxProcessors/LocalToolInboxProcessor.php index 2dacab8..b2d84b0 100644 --- a/libraries/default/InboxProcessors/LocalToolInboxProcessor.php +++ b/libraries/default/InboxProcessors/LocalToolInboxProcessor.php @@ -179,6 +179,7 @@ class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor impleme public function __construct() { parent::__construct(); + $this->severity = $this->Inbox::SEVERITY_WARNING; $this->description = __('Handle Phase I of inter-connection when another cerebrate instance performs the request.'); } @@ -291,6 +292,7 @@ class AcceptedRequestProcessor extends LocalToolInboxProcessor implements Generi public function __construct() { parent::__construct(); + $this->severity = $this->Inbox::SEVERITY_WARNING; $this->description = __('Handle Phase II of inter-connection when initial request has been accepted by the remote cerebrate.'); } @@ -367,6 +369,7 @@ class DeclinedRequestProcessor extends LocalToolInboxProcessor implements Generi public function __construct() { parent::__construct(); + $this->severity = $this->Inbox::SEVERITY_WARNING; $this->description = __('Handle Phase II of MISP inter-connection when initial request has been declined by the remote cerebrate.'); } diff --git a/libraries/default/InboxProcessors/SynchronisationInboxProcessor.php b/libraries/default/InboxProcessors/SynchronisationInboxProcessor.php index 2a243cd..d95154e 100644 --- a/libraries/default/InboxProcessors/SynchronisationInboxProcessor.php +++ b/libraries/default/InboxProcessors/SynchronisationInboxProcessor.php @@ -28,6 +28,7 @@ class DataExchangeProcessor extends SynchronisationInboxProcessor implements Gen public function __construct() { parent::__construct(); + $this->severity = $this->Inbox::SEVERITY_WARNING; $this->description = __('Handle exchange of data between two cerebrate instances'); $this->Users = TableRegistry::getTableLocator()->get('Users'); } diff --git a/libraries/default/InboxProcessors/UserInboxProcessor.php b/libraries/default/InboxProcessors/UserInboxProcessor.php index abb9550..96656c1 100644 --- a/libraries/default/InboxProcessors/UserInboxProcessor.php +++ b/libraries/default/InboxProcessors/UserInboxProcessor.php @@ -31,6 +31,7 @@ class RegistrationProcessor extends UserInboxProcessor implements GenericInboxPr public function __construct() { parent::__construct(); + $this->severity = $this->Inbox::SEVERITY_WARNING; $this->description = __('Handle user account for this cerebrate instance'); } diff --git a/libraries/default/OutboxProcessors/BroodsOutboxProcessor.php b/libraries/default/OutboxProcessors/BroodsOutboxProcessor.php index 9bf79ae..8bde905 100644 --- a/libraries/default/OutboxProcessors/BroodsOutboxProcessor.php +++ b/libraries/default/OutboxProcessors/BroodsOutboxProcessor.php @@ -70,6 +70,7 @@ class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements Gene public function __construct() { parent::__construct(); $this->description = __('Handle re-sending messages that failed to be received from other cerebrate instances.'); + $this->severity = $this->Inbox::SEVERITY_WARNING; $this->Broods = TableRegistry::getTableLocator()->get('Broods'); $this->LocalTools = \Cake\ORM\TableRegistry::getTableLocator()->get('LocalTools'); } From d23cf2e2c6f280d27e5d832abca8cd88982d04da Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:21:26 +0100 Subject: [PATCH 12/14] new: [inbox:index] Added support of various context filtering - My notification includes message without user_ids - User registration - Inter-connection requests - Data changed - severity:* --- src/Controller/InboxController.php | 87 +++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/src/Controller/InboxController.php b/src/Controller/InboxController.php index 388dbfa..700e5ef 100644 --- a/src/Controller/InboxController.php +++ b/src/Controller/InboxController.php @@ -43,9 +43,94 @@ class InboxController extends AppController [ 'default' => true, 'label' => __('My Notifications'), + 'filterConditionFunction' => function ($query) { + return $query->where(function(QueryExpression $exp) { + return $exp->or(['user_id' => $this->ACL->getUser()['id']]) + ->isNull('user_id'); + }); + } + ], + [ + 'label' => __('User Registration'), 'filterConditionFunction' => function ($query) { return $query->where([ - 'user_id' => $this->ACL->getUser()['id'], + 'scope' => 'User', + 'action' => 'Registration', + ]); + } + ], + [ + 'label' => __('Inter-connection Requests'), + 'filterConditionFunction' => function ($query) { + return $query->where([ + 'scope' => 'LocalTool', + 'action IN' => ['IncomingConnectionRequest', 'AcceptedRequest', 'DeclinedRequest'], + ]); + } + ], + [ + 'label' => __('Data changed'), + 'filterConditionFunction' => function ($query) { + return $query->where([ + 'user_id' => $this->ACL->getUser()['id'], // Each admin get a message about data changes + 'scope' => 'Notification', + 'action' => 'DataChange', + ]); + } + ], + [ + 'label' => 'severity:primary', + 'viewElement' => 'bootstrapUI', + 'viewElementParams' => [ + 'element' => 'badge', + 'text' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_PRIMARY], + 'variant' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_PRIMARY], + ], + 'filterConditionFunction' => function ($query) { + return $query->where([ + 'severity' => $this->Inbox::SEVERITY_PRIMARY, + ]); + } + ], + [ + 'label' => 'severity:info', + 'viewElement' => 'bootstrapUI', + 'viewElementParams' => [ + 'element' => 'badge', + 'text' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_INFO], + 'variant' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_INFO], + ], + 'filterConditionFunction' => function ($query) { + return $query->where([ + 'severity' => $this->Inbox::SEVERITY_INFO, + ]); + } + ], + [ + 'label' => 'severity:warning', + 'viewElement' => 'bootstrapUI', + 'viewElementParams' => [ + 'element' => 'badge', + 'text' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_WARNING], + 'variant' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_WARNING], + ], + 'filterConditionFunction' => function ($query) { + return $query->where([ + 'severity' => $this->Inbox::SEVERITY_WARNING, + ]); + } + ], + [ + 'label' => 'severity:danger', + 'viewElement' => 'bootstrapUI', + 'viewElementParams' => [ + 'element' => 'badge', + 'text' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_DANGER], + 'variant' => $this->Inbox->severityVariant[$this->Inbox::SEVERITY_DANGER], + ], + 'filterConditionFunction' => function ($query) { + return $query->where([ + 'severity' => $this->Inbox::SEVERITY_DANGER, ]); } ], From 6ed9978661fe35b9ee5fea4ec7fdaf0c36a10d5b Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:22:02 +0100 Subject: [PATCH 13/14] chg: [inbox:filtering] Possibility to filter on severity --- src/Controller/InboxController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/InboxController.php b/src/Controller/InboxController.php index 700e5ef..2abfb47 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', 'title', 'origin', 'message', 'Users.id', 'Users.username',]; + public $filterFields = ['scope', 'action', 'Inbox.created', 'severity', 'title', 'origin', 'message', 'Users.id', 'Users.username',]; public $quickFilterFields = ['scope', 'action', ['title' => true], ['message' => true], 'origin']; public $containFields = ['Users']; From 14b41451acad543056407bd98bbbb7dbaac4e680 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 15 Nov 2022 11:27:12 +0100 Subject: [PATCH 14/14] fix: [genericTemplates:filters] Make sure to always return a string when fetching data --- templates/genericTemplates/filters.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/genericTemplates/filters.php b/templates/genericTemplates/filters.php index d900104..a193721 100644 --- a/templates/genericTemplates/filters.php +++ b/templates/genericTemplates/filters.php @@ -202,7 +202,7 @@ echo $this->Bootstrap->modal([ rowData['operator'] = $row.find('select.fieldOperator').val() const $formElement = $row.find('.fieldValue'); if ($formElement.attr('type') === 'datetime-local') { - rowData['value'] = moment($formElement.val()).toISOString() + rowData['value'] = $formElement.val().length > 0 ? moment($formElement.val()).toISOString() : $formElement.val() } else { rowData['value'] = $formElement.val() }