diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index f7a82ee..cba0a22 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -134,7 +134,7 @@ class CRUDComponent extends Component }); $metaTemplates = $metaQuery->all(); } - $this->Controller->set('metaTemplates', $metaTemplates); + // $this->Controller->set('metaTemplates', $metaTemplates->toArray()); return $metaTemplates; } @@ -232,7 +232,11 @@ class CRUDComponent extends Component foreach ($data->getErrors() as $field => $errorData) { $errorMessages = []; foreach ($errorData as $key => $value) { - $errorMessages[] = $value; + if (is_array($value)) { + $errorMessages[] = implode('& ', Hash::extract($value, "{s}.{s}")); + } else { + $errorMessages[] = $value; + } } $validationMessage .= __('{0}: {1}', $field, implode(',', $errorMessages)); } @@ -281,28 +285,50 @@ class CRUDComponent extends Component 'uuid' => Text::uuid(), ]); $entity->meta_fields[] = $metaField; + $entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[] = $metaField; + // $entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields['new'][] = $metaField; } } } else { $new_value = $meta_field['value']; - $index = $metaFieldsIndex[$meta_field_id]; if (!empty($new_value)) { - // update meta_field - $metaFieldsTable->patchEntity($entity->meta_fields[$index], [ - 'value' => $new_value, - ]); + // update meta_field and attach validation errors + if (!empty($metaFieldsIndex[$meta_field_id])) { + $index = $metaFieldsIndex[$meta_field_id]; + $metaFieldsTable->patchEntity($entity->meta_fields[$index], [ + 'value' => $new_value, 'meta_template_field_id' => $rawMetaTemplateField->id + ], ['value']); + $metaFieldsTable->patchEntity( + $entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[$meta_field_id], + ['value' => $new_value, 'meta_template_field_id' => $rawMetaTemplateField->id], + ['value'] + ); + } else { // metafield comes from a second post where the temporary entity has already been created + $metaField = $metaFieldsTable->newEmptyEntity(); + $metaFieldsTable->patchEntity($metaField, [ + 'value' => $new_value, + 'scope' => $this->Table->metaFields, // get scope from behavior + 'field' => $rawMetaTemplateField->field, + 'meta_template_id' => $rawMetaTemplateField->meta_template_id, + 'meta_template_field_id' => $rawMetaTemplateField->id, + 'parent_id' => $entity->id, + 'uuid' => Text::uuid(), + ]); + $entity->meta_fields[] = $metaField; + $entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[] = $metaField; + } } else { - // remove meta field - Just checking if not having it will have it deleted or if it should actually be deleted - // Maybe Table->unlink ? + // Metafield value is empty, indicating the field should be removed + $index = $metaFieldsIndex[$meta_field_id]; $metaFieldsToDelete[] = $entity->meta_fields[$index]; unset($entity->meta_fields[$index]); + unset($entity->MetaTemplates[$template_id]->meta_template_fields[$meta_template_field_id]->metaFields[$meta_field_id]); } } } } } - // $input['metaFields'] = $metaFields; $entity->setDirty('meta_fields', true); return ['entity' => $entity, 'metafields_to_delete' => $metaFieldsToDelete]; } @@ -348,7 +374,6 @@ class CRUDComponent extends Component } $data = $this->Table->get($id, $getParam); $data = $this->attachMetaTemplates($data, $metaTemplates->toArray()); - // $data = $this->getMetaFields($id, $data); if (!empty($params['fields'])) { $this->Controller->set('fields', $params['fields']); } @@ -360,10 +385,11 @@ class CRUDComponent extends Component if (!empty($params['fields'])) { $patchEntityParams['fields'] = $params['fields']; } + $massagedData = $this->massageMetaFields($data, $input, $metaTemplates); + unset($input['MetaTemplates']); // Avoid MetaTemplates to be overriden when patching entity + $data = $massagedData['entity']; + $metaFieldsToDelete = $massagedData['metafields_to_delete']; $data = $this->Table->patchEntity($data, $input, $patchEntityParams); - $massaged = $this->massageMetaFields($data, $input, $metaTemplates); - $data = $massaged['entity']; - $metaFieldsToDelete = $massaged['metafields_to_delete']; if (isset($params['beforeSave'])) { $data = $params['beforeSave']($data); } @@ -389,8 +415,10 @@ class CRUDComponent extends Component } } } else { + // debug($data['MetaTemplates']); $validationErrors = $data->getErrors(); - $validationMessage = $this->prepareValidationMessage($validationErrors); + // $validationMessage = $this->prepareValidationMessage($validationErrors); + $validationMessage = $this->prepareValidationError($data); $message = __( '{0} could not be modified.{1}', $this->ObjectAlias, diff --git a/src/Model/Table/MetaFieldsTable.php b/src/Model/Table/MetaFieldsTable.php index e26e5f9..ec12e80 100644 --- a/src/Model/Table/MetaFieldsTable.php +++ b/src/Model/Table/MetaFieldsTable.php @@ -5,6 +5,7 @@ namespace App\Model\Table; use App\Model\Table\AppTable; use Cake\ORM\Table; use Cake\Validation\Validator; +use Cake\ORM\RulesChecker; class MetaFieldsTable extends AppTable { @@ -34,7 +35,69 @@ class MetaFieldsTable extends AppTable ->notEmptyString('meta_template_field_id') ->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_id', 'meta_template_field_id'], 'create'); - // add validation regex + $validator->add('value', 'validMetaField', [ + 'rule' => 'isValidMetaField', + 'message' => __('The provided value doesn\'t satisfy the validation defined by the meta-fields\'s meta-template'), + 'provider' => 'table', + ]); + return $validator; } + + // public function buildRules(RulesChecker $rules): RulesChecker + // { + // $rules->add([$this, 'isValidMetaField'], 'validMetaField', [ + // // 'errorField' => 'meta_field.value', + // 'errorField' => 'value', + // 'message' => __('The provided value doesn\'t satisfy the validation defined by the meta-fields\'s meta-template'), + // ]); + // return $rules; + // } + + public function isValidMetaField($value, array $context) + { + $metaFieldsTable = $context['providers']['table']; + $entityData = $context['data']; + $metaTemplateField = $metaFieldsTable->MetaTemplateFields->get($entityData['meta_template_field_id']); + $typeValid = $this->isValidType($value, $metaTemplateField['type']); + if ($typeValid !== true) { + return $typeValid; + } + if (!empty($metaTemplateField['regex'])) { + return $this->isValidRegex($value, $metaTemplateField); + } + return true; + } + + public function isValidMetaFieldOLD($entity, $options) + { + debug($entity['value']); + $metaFieldsTable = $options['repository']; + $metaTemplateField = $metaFieldsTable->MetaTemplateFields->get($entity['meta_template_field_id']); + $typeValid = $this->isValidType($entity['value'], $metaTemplateField['type']); + if ($typeValid !== true) { + return $typeValid; + } + $metaTemplateField['regex'] = '/123/'; + if (!empty($metaTemplateField['regex'])) { + return $this->isValidRegex($entity['value'], $metaTemplateField); + } + return true; + } + + public function isValidType($value, string $type) + { + if (empty($value)) { + return __('Metafield value cannot be empty.'); + } + return true; + } + + public function isValidRegex($value, $metaTemplateField) + { + if (!preg_match($metaTemplateField['regex'], $value)) { + return __('Metafield value `{0}` for `{1}` doesn\'t pass regex validation', $value, $metaTemplateField->field); + } + return true; + } } diff --git a/templates/Individuals/add.php b/templates/Individuals/add.php index 935c2de..6c7be05 100644 --- a/templates/Individuals/add.php +++ b/templates/Individuals/add.php @@ -26,7 +26,7 @@ 'type' => 'tags' ), ), - 'metaTemplates' => empty($metaTemplates) ? [] : $metaTemplates, + // 'metaTemplates' => empty($metaTemplates) ? [] : $metaTemplates, 'submit' => array( 'action' => $this->request->getParam('action') ) diff --git a/templates/element/genericElements/Form/fieldScaffold.php b/templates/element/genericElements/Form/fieldScaffold.php index f485b77..9d916ed 100644 --- a/templates/element/genericElements/Form/fieldScaffold.php +++ b/templates/element/genericElements/Form/fieldScaffold.php @@ -41,6 +41,8 @@ $params[$k] = $fd; } } + // debug($fieldData); + // debug($params); $temp = $this->element('genericElements/Form/Fields/' . $fieldTemplate, array( 'fieldData' => $fieldData, 'params' => $params diff --git a/templates/element/genericElements/Form/genericForm.php b/templates/element/genericElements/Form/genericForm.php index 91f8a19..ba4146e 100644 --- a/templates/element/genericElements/Form/genericForm.php +++ b/templates/element/genericElements/Form/genericForm.php @@ -12,6 +12,7 @@ - use these to define dynamic form fields, or anything that will feed into the regular fields via JS population * - submit: The submit button itself. By default it will simply submit to the form as defined via the 'model' field */ + // debug(\Cake\Utility\Hash::extract($entity, 'MetaTemplates.{n}.meta_template_fields.{n}.metaFields.{n}.value')); $this->Form->setConfig('errorClass', 'is-invalid'); $modelForForm = empty($data['model']) ? h(\Cake\Utility\Inflector::singularize(\Cake\Utility\Inflector::classify($this->request->getParam('controller')))) : @@ -21,7 +22,6 @@ $simpleFieldWhitelist = [ 'default', 'type', 'placeholder', 'label', 'empty', 'rows', 'div', 'required', 'templates' ]; - //$fieldsArrayForPersistence = array(); if (empty($data['url'])) { $data['url'] = ["controller" => $this->request->getParam('controller'), "action" => $this->request->getParam('url')]; } @@ -78,11 +78,10 @@ } } $metaTemplateString = ''; - if (!empty($data['metaTemplates']) && $data['metaTemplates']->count() > 0) { + if (!empty($entity['MetaTemplates']) && count($entity['MetaTemplates']) > 0) { $metaTemplateString = $this->element( 'genericElements/Form/metaTemplateScaffold', [ - 'metaTemplatesData' => $data['metaTemplates'], 'form' => $this->Form, ] ); diff --git a/templates/element/genericElements/Form/metaTemplateScaffold.php b/templates/element/genericElements/Form/metaTemplateScaffold.php index 92d3fca..204b82d 100644 --- a/templates/element/genericElements/Form/metaTemplateScaffold.php +++ b/templates/element/genericElements/Form/metaTemplateScaffold.php @@ -10,7 +10,7 @@ $default_template = [ $this->Form->setTemplates($default_template); $backupTemplates = $this->Form->getTemplates(); $tabData = []; -foreach ($metaTemplatesData as $i => $metaTemplate) { +foreach ($entity->MetaTemplates as $i => $metaTemplate) { if ($metaTemplate->is_default) { $tabData['navs'][$i] = [ 'html' => $this->element('/genericElements/MetaTemplates/metaTemplateNav', ['metaTemplate' => $metaTemplate]) @@ -21,6 +21,7 @@ foreach ($metaTemplatesData as $i => $metaTemplate) { ]; } $fieldsHtml = ''; + // debug($metaTemplate['meta_template_fields']); foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { $metaTemplateField->label = Inflector::humanize($metaTemplateField->field); if (!empty($metaTemplateField->metaFields)) { diff --git a/templates/element/genericElements/Form/multiFieldButton.php b/templates/element/genericElements/Form/multiFieldButton.php index 8c4b324..c73c6d3 100644 --- a/templates/element/genericElements/Form/multiFieldButton.php +++ b/templates/element/genericElements/Form/multiFieldButton.php @@ -39,10 +39,14 @@ $seed = 'mfb-' . mt_rand(); function addNewField() { const $clicked = $(this); - const $lastInputContainer = $clicked.closest('.multi-metafields-container').children().not('.template-container').find('input').last().closest('.multi-metafield-container') - const $templateContainer = $clicked.closest('.multi-metafields-container').find('.template-container').children() - const $clonedContainer = $templateContainer.clone() - $clonedContainer.removeClass('d-none', 'template-container') + let $lastInputContainer = $clicked.closest('.multi-metafields-container').children().not('.template-container').find('input').last().closest('.multi-metafield-container') + if ($lastInputContainer.length == 0) { + $lastInputContainer = $clicked.closest('.multi-metafields-container').find('input').last().closest('.multi-metafield-container') + } + const $clonedContainer = $lastInputContainer.clone() + $clonedContainer + .removeClass('has-error') + .find('.error-message ').remove() const $clonedInput = $clonedContainer.find('input, select') if ($clonedInput.length > 0) { const injectedTemplateId = $clicked.closest('.multi-metafields-container').find('.new-metafield').length @@ -63,6 +67,34 @@ $seed = 'mfb-' . mt_rand(); .attr('field', dottedPathStr) .attr('name', brackettedPathStr) .val('') + .removeClass('is-invalid') } + // function addNewField() { + // const $clicked = $(this); + // const $lastInputContainer = $clicked.closest('.multi-metafields-container').children().not('.template-container').find('input').last().closest('.multi-metafield-container') + // const $templateContainer = $clicked.closest('.multi-metafields-container').find('.template-container').children() + // const $clonedContainer = $templateContainer.clone() + // $clonedContainer.removeClass('d-none', 'template-container') + // const $clonedInput = $clonedContainer.find('input, select') + // if ($clonedInput.length > 0) { + // const injectedTemplateId = $clicked.closest('.multi-metafields-container').find('.new-metafield').length + // $clonedInput.addClass('new-metafield') + // adjustClonedInputAttr($clonedInput, injectedTemplateId) + // $clonedContainer.insertAfter($lastInputContainer) + // } + // } + + // 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('') + // } })() \ No newline at end of file diff --git a/templates/element/genericElements/Form/multiFieldScaffold.php b/templates/element/genericElements/Form/multiFieldScaffold.php index ce3c830..fec3778 100644 --- a/templates/element/genericElements/Form/multiFieldScaffold.php +++ b/templates/element/genericElements/Form/multiFieldScaffold.php @@ -4,20 +4,48 @@ use Cake\Utility\Inflector; $default_template = [ 'inputContainer' => '
', - 'inputContainerError' => ' ', + 'inputContainerError' => ' ', 'formGroup' => ' ', ]; $form->setTemplates($default_template); $fieldsHtml = ''; $labelPrintedOnce = false; +$newMetaFields = []; +$newFieldIndex = 0; +// $newMetaFieldPrinted = false; +// debug($entity); +// debug($entity->getErrors()); if (!empty($metaFieldsEntities)) { foreach ($metaFieldsEntities as $i => $metaFieldsEntity) { $metaFieldsEntity->label = Inflector::humanize($metaFieldsEntity->field); $fieldData = [ 'label' => $metaFieldsEntity->label, - 'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', $metaFieldsEntity->meta_template_id, $metaFieldsEntity->meta_template_field_id, $metaFieldsEntity->id), + 'field' => sprintf( + 'MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', + $metaFieldsEntity->meta_template_id, + $metaFieldsEntity->meta_template_field_id, + $metaFieldsEntity->id + ), ]; + if($metaFieldsEntity->isNew()) { + $fieldData['field'] = sprintf( + 'MetaTemplates.%s.meta_template_fields.%s.metaFields.%s.value', + $metaFieldsEntity->meta_template_id, + $metaFieldsEntity->meta_template_field_id, + $i + ); + $fieldData['class'] = 'new-metafield'; + // $fieldData['field'] = sprintf( + // 'MetaTemplates.%s.meta_template_fields.%s.metaFields.new.%s', + // $metaFieldsEntity->meta_template_id, + // $metaFieldsEntity->meta_template_field_id, + // $newFieldIndex + // ); + // $fieldData['class'] = 'new-metafield'; + // $newMetaFieldPrinted = true; + // $newFieldIndex += 1; + } if ($labelPrintedOnce) { // Only the first input can have a label $fieldData['label'] = false; } @@ -35,17 +63,17 @@ if (!empty($metaTemplateField) && !empty($multiple)) { // Add multiple field but $metaTemplateField->label = Inflector::humanize($metaTemplateField->field); $emptyMetaFieldInput = ''; if (empty($metaFieldsEntities)) { - $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', - ], - 'form' => $form, - ] - ); + // $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', + // ], + // 'form' => $form, + // ] + // ); } $emptyInputForSecurityComponent = $this->element( 'genericElements/Form/fieldScaffold', @@ -53,10 +81,24 @@ if (!empty($metaTemplateField) && !empty($multiple)) { // Add multiple field but 'fieldData' => [ 'label' => false, 'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new[]', $metaTemplateField->meta_template_id, $metaTemplateField->id), + 'value' => '', ], 'form' => $form, ] ); + // $emptyInputForSecurityComponent = ''; + // if (!$newMetaFieldPrinted) { + // $emptyInputForSecurityComponent = $this->element( + // 'genericElements/Form/fieldScaffold', + // [ + // 'fieldData' => [ + // 'label' => false, + // 'field' => sprintf('MetaTemplates.%s.meta_template_fields.%s.metaFields.new[]', $metaTemplateField->meta_template_id, $metaTemplateField->id), + // ], + // 'form' => $form, + // ] + // ); + // } $multiFieldButtonHtml = sprintf( ' ', $this->element( @@ -66,7 +108,7 @@ if (!empty($metaTemplateField) && !empty($multiple)) { // Add multiple field but ] ) ); - $fieldsHtml .= $emptyMetaFieldInput; + // $fieldsHtml .= $emptyMetaFieldInput; $fieldsHtml .= sprintf('