From 02cc0c30a3f0fc5d6d2db24231050aad269ce998 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Tue, 14 Dec 2021 15:09:40 +0100 Subject: [PATCH] chg: [metaTemplate] Major refactoring and documentation - WiP --- .../20211104072514_MoreMetaFieldColumns.php | 3 +- .../Component/Navigation/MetaTemplates.php | 44 +- src/Controller/MetaTemplatesController.php | 157 ++- src/Model/Table/MetaTemplatesTable.php | 1032 ++++++++++++----- templates/MetaTemplates/index.php | 14 +- templates/MetaTemplates/update.php | 8 +- templates/MetaTemplates/update_all.php | 4 +- .../MetaTemplates/conflictResolution.php | 72 +- .../element/MetaTemplates/conflictTable.php | 6 +- 9 files changed, 920 insertions(+), 420 deletions(-) diff --git a/config/Migrations/20211104072514_MoreMetaFieldColumns.php b/config/Migrations/20211104072514_MoreMetaFieldColumns.php index 23e7ffe..3cfbd9a 100644 --- a/config/Migrations/20211104072514_MoreMetaFieldColumns.php +++ b/config/Migrations/20211104072514_MoreMetaFieldColumns.php @@ -37,9 +37,10 @@ class MoreMetaFieldColumns extends AbstractMigration $metaTemplate = $this->table('meta_templates') ->removeIndex(['uuid']) - ->addIndex(['uuid']) + ->addIndex(['uuid', 'version']) ->update(); // TODO: Make sure FK constraints are set between meta_field, meta_template and meta_template_fields + // TODO: Make sure to add constraints on meta_template `uuid` and `version` } } \ No newline at end of file diff --git a/src/Controller/Component/Navigation/MetaTemplates.php b/src/Controller/Component/Navigation/MetaTemplates.php index 916fb25..ca83ef9 100644 --- a/src/Controller/Component/Navigation/MetaTemplates.php +++ b/src/Controller/Component/Navigation/MetaTemplates.php @@ -24,15 +24,15 @@ class MetaTemplatesNavigation extends BaseNavigation ]); $totalUpdateCount = 0; - if (!empty($this->viewVars['updateableTemplate']['updateable']) && !empty($this->viewVars['updateableTemplate']['new'])) { - $udpateCount = count($this->viewVars['updateableTemplate']['updateable']) ?? 0; - $newCount = count($this->viewVars['updateableTemplate']['new']) ?? 0; + if (!empty($this->viewVars['updateableTemplates']['automatically-updateable']) && !empty($this->viewVars['updateableTemplates']['new'])) { + $udpateCount = count($this->viewVars['updateableTemplates']['automatically-updateable']) ?? 0; + $newCount = count($this->viewVars['updateableTemplates']['new']) ?? 0; $totalUpdateCount = $udpateCount + $newCount; } $updateRouteConfig = [ 'label' => __('Update all templates'), 'icon' => 'download', - 'url' => '/metaTemplates/update', + 'url' => '/metaTemplates/updateAllTemplates', ]; if ($totalUpdateCount > 0) { $updateRouteConfig['badge'] = [ @@ -41,7 +41,17 @@ class MetaTemplatesNavigation extends BaseNavigation 'title' => __('There are {0} new meta-template(s) and {1} update(s) available', h($newCount), h($udpateCount)), ]; } - $this->bcf->addRoute('MetaTemplates', 'update', $updateRouteConfig); + $this->bcf->addRoute('MetaTemplates', 'update_all_templates', $updateRouteConfig); + $this->bcf->addRoute('MetaTemplates', 'update', [ + 'label' => __('Update template'), + 'icon' => 'download', + 'url' => '/metaTemplates/update', + ]); + $this->bcf->addRoute('MetaTemplates', 'prune_outdated_template', [ + 'label' => __('Prune outdated template'), + 'icon' => 'trash', + 'url' => '/metaTemplates/prune_outdated_template', + ]); } public function addParents() @@ -58,30 +68,34 @@ class MetaTemplatesNavigation extends BaseNavigation public function addActions() { $totalUpdateCount = 0; - if (!empty($this->viewVars['updateableTemplate']['not_up_to_date']) && !empty($this->viewVars['updateableTemplate']['new'])) { - $udpateCount = count($this->viewVars['updateableTemplate']['not_up_to_date']) ?? 0; - $newCount = count($this->viewVars['updateableTemplate']['new']) ?? 0; + if (!empty($this->viewVars['updateableTemplates']['not-up-to-date']) && !empty($this->viewVars['updateableTemplates']['new'])) { + $udpateCount = count($this->viewVars['updateableTemplates']['not-up-to-date']) ?? 0; + $newCount = count($this->viewVars['updateableTemplates']['new']) ?? 0; $totalUpdateCount = $udpateCount + $newCount; } - $updateActionConfig = [ + $updateAllActionConfig = [ 'label' => __('Update template'), - 'url' => '/metaTemplates/update/{{id}}', + 'url' => '/metaTemplates/updateAllTemplates', 'url_vars' => ['id' => 'id'], ]; if ($totalUpdateCount > 0) { - $updateActionConfig['badge'] = [ + $updateAllActionConfig['badge'] = [ 'text' => h($totalUpdateCount), 'variant' => 'warning', 'title' => __('There are {0} new meta-template(s) and {1} update(s) available', h($newCount), h($udpateCount)), ]; } - $this->bcf->addAction('MetaTemplates', 'index', 'MetaTemplates', 'update', $updateActionConfig); + $this->bcf->addAction('MetaTemplates', 'index', 'MetaTemplates', 'update_all_templates', $updateAllActionConfig); + $this->bcf->addAction('MetaTemplates', 'index', 'MetaTemplates', 'prune_outdated_template', [ + 'label' => __('Prune outdated template'), + 'url' => '/metaTemplates/prune_outdated_template', + ]); - if (empty($this->viewVars['updateableTemplate']['up-to-date'])) { + if (empty($this->viewVars['updateableTemplates']['up-to-date'])) { $this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'update', [ 'label' => __('Update template'), - 'url' => '/metaTemplates/update/{{uuid}}', - 'url_vars' => ['uuid' => 'uuid'], + 'url' => '/metaTemplates/update/{{id}}', + 'url_vars' => ['id' => 'id'], 'variant' => 'warning', 'badge' => [ 'variant' => 'warning', diff --git a/src/Controller/MetaTemplatesController.php b/src/Controller/MetaTemplatesController.php index 2548839..32bef92 100644 --- a/src/Controller/MetaTemplatesController.php +++ b/src/Controller/MetaTemplatesController.php @@ -17,27 +17,78 @@ class MetaTemplatesController extends AppController public $filterFields = ['name', 'uuid', 'scope', 'namespace']; public $containFields = ['MetaTemplateFields']; - public function update($template_uuid=null) + // public function update($template_uuid=null) + // { + // $metaTemplate = false; + // if (!is_null($template_uuid)) { + // $metaTemplate = $this->MetaTemplates->find()->where([ + // 'uuid' => $template_uuid + // ])->first(); + // if (empty($metaTemplate)) { + // throw new NotFoundException(__('Invalid {0}.', $this->MetaTemplates->getAlias())); + // } + // } + // if ($this->request->is('post')) { + // $updateStrategy = $this->request->getData('update_strategy', null); + // $result = $this->MetaTemplates->update($template_uuid, $updateStrategy); + // if ($this->ParamHandler->isRest()) { + // return $this->RestResponse->viewData($result, 'json'); + // } else { + // if ($result['success']) { + // $message = __n('{0} templates updated.', 'The template has been updated.', empty($template_uuid), $result['files_processed']); + // } else { + // $message = __n('{0} templates could not be updated.', 'The template could not be updated.', empty($template_uuid), $result['files_processed']); + // } + // $this->CRUD->setResponseForController('update', $result['success'], $message, $result['files_processed'], $result['update_errors'], ['redirect' => $this->referer()]); + // $responsePayload = $this->CRUD->getResponsePayload(); + // if (!empty($responsePayload)) { + // return $responsePayload; + // } + // } + // } else { + // if (!$this->ParamHandler->isRest()) { + // if (!is_null($template_uuid)) { + // $this->set('metaTemplate', $metaTemplate); + // $this->setUpdateStatus($metaTemplate->id); + // } else { + // $this->set('title', __('Update Meta Templates')); + // $this->set('question', __('Are you sure you wish to update the Meta Template definitions')); + // $templatesUpdateStatus = $this->MetaTemplates->getUpdateStatusForTemplates(); + // $this->set('templatesUpdateStatus', $templatesUpdateStatus); + // $this->render('updateAll'); + // } + // } + // } + // } + + /** + * Update the provided template or all templates + * + * @param int|null $template_id + */ + public function update($template_id=null) { $metaTemplate = false; - if (!is_null($template_uuid)) { - $metaTemplate = $this->MetaTemplates->find()->where([ - 'uuid' => $template_uuid - ])->first(); + if (!is_null($template_id)) { + if (!is_numeric($template_id)) { + throw new NotFoundException(__('Invalid {0} for provided ID.', $this->MetaTemplates->getAlias(), $template_id)); + } + $metaTemplate = $this->MetaTemplates->get($template_id); if (empty($metaTemplate)) { - throw new NotFoundException(__('Invalid {0}.', $this->MetaTemplates->getAlias())); + throw new NotFoundException(__('Invalid {0} {1}.', $this->MetaTemplates->getAlias(), $template_id)); } } if ($this->request->is('post')) { - $updateStrategy = $this->request->getData('update_strategy', null); - $result = $this->MetaTemplates->update($template_uuid, $updateStrategy); + $params = $this->ParamHandler->harvestParams(['update_strategy']); + $updateStrategy = $params['update_strategy'] ?? null; + $result = $this->MetaTemplates->update($metaTemplate, $updateStrategy); if ($this->ParamHandler->isRest()) { return $this->RestResponse->viewData($result, 'json'); } else { if ($result['success']) { - $message = __n('{0} templates updated.', 'The template has been updated.', empty($template_uuid), $result['files_processed']); + $message = __n('{0} templates updated.', 'The template has been updated.', empty($template_id), $result['files_processed']); } else { - $message = __n('{0} templates could not be updated.', 'The template could not be updated.', empty($template_uuid), $result['files_processed']); + $message = __n('{0} templates could not be updated.', 'The template could not be updated.', empty($template_id), $result['files_processed']); } $this->CRUD->setResponseForController('update', $result['success'], $message, $result['files_processed'], $result['update_errors'], ['redirect' => $this->referer()]); $responsePayload = $this->CRUD->getResponsePayload(); @@ -47,14 +98,15 @@ class MetaTemplatesController extends AppController } } else { if (!$this->ParamHandler->isRest()) { - if (!is_null($template_uuid)) { + if (!empty($metaTemplate)) { $this->set('metaTemplate', $metaTemplate); - $this->setUpdateStatus($metaTemplate->id); + $statuses = $this->setUpdateStatus($metaTemplate->id); + $this->set('updateStatus', $this->MetaTemplates->computeFullUpdateStatusForMetaTemplate($statuses['templateStatus'], $metaTemplate)); } else { - $this->set('title', __('Update Meta Templates')); - $this->set('question', __('Are you sure you wish to update the Meta Template definitions')); - $updateableTemplates = $this->MetaTemplates->checkForUpdates(); - $this->set('updateableTemplates', $updateableTemplates); + $this->set('title', __('Update All Meta Templates')); + $this->set('question', __('Are you sure you wish to update all the Meta Template definitions')); + $templatesUpdateStatus = $this->MetaTemplates->getUpdateStatusForTemplates(); + $this->set('templatesUpdateStatus', $templatesUpdateStatus); $this->render('updateAll'); } } @@ -65,7 +117,7 @@ class MetaTemplatesController extends AppController { $metaTemplate = $this->MetaTemplates->get($template_id); $newestMetaTemplate = $this->MetaTemplates->getNewestVersion($metaTemplate); - $entities = $this->MetaTemplates->getEntitiesWithMetaFieldsToUpdate($template_id); + $entities = $this->MetaTemplates->getEntitiesHavingMetaFieldsFromTemplate($template_id); $this->set('metaTemplate', $metaTemplate); $this->set('newestMetaTemplate', $newestMetaTemplate); $this->set('entities', $entities); @@ -77,15 +129,16 @@ class MetaTemplatesController extends AppController 'contain' => ['MetaTemplateFields'] ]); $newestMetaTemplate = $this->MetaTemplates->getNewestVersion($metaTemplate, true); - $entity = $this->MetaTemplates->migrateMetaTemplateToNewVersion($metaTemplate, $newestMetaTemplate, $entity_id); + $entity = $this->MetaTemplates->getEntity($metaTemplate, $entity_id); $conditions = [ - 'MetaFields.meta_template_id IN' => [$metaTemplate->id, $newestMetaTemplate->id] + 'MetaFields.meta_template_id IN' => [$metaTemplate->id, $newestMetaTemplate->id], + 'MetaFields.scope' => $metaTemplate->scope, ]; - $keyedMetaFields = $this->MetaTemplates->getKeyedMetaFields($metaTemplate->scope, $entity_id, $conditions); + $keyedMetaFields = $this->MetaTemplates->getKeyedMetaFieldsForEntity($entity_id, $conditions); if (empty($keyedMetaFields[$metaTemplate->id])) { throw new NotFoundException(__('Invalid {0}. This entities does not have meta-fields to be moved to a newer template.', $this->MetaTemplates->getAlias())); } - $mergedMetaFields = $this->MetaTemplates->mergeMetaFieldsInMetaTemplate($keyedMetaFields, [$metaTemplate, $newestMetaTemplate]); + $mergedMetaFields = $this->MetaTemplates->insertMetaFieldsInMetaTemplates($keyedMetaFields, [$metaTemplate, $newestMetaTemplate]); $entity['MetaTemplates'] = $mergedMetaFields; if ($this->request->is('post') || $this->request->is('put')) { $className = Inflector::camelize(Inflector::pluralize($newestMetaTemplate->scope)); @@ -126,11 +179,13 @@ class MetaTemplatesController extends AppController return $responsePayload; } } - $conflicts = $this->MetaTemplates->checkForMetaTemplateConflicts($metaTemplate, $newestMetaTemplate); + $conflicts = $this->MetaTemplates->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $newestMetaTemplate); foreach ($conflicts as $conflict) { - $existingMetaTemplateField = $conflict['existing_meta_template_field']; - foreach ($existingMetaTemplateField->metaFields as $metafield_id => $metaField) { - $metaField->setError('value', implode(', ', $existingMetaTemplateField->conflicts)); + if (!empty($conflict['existing_meta_template_field'])) { + $existingMetaTemplateField = $conflict['existing_meta_template_field']; + foreach ($existingMetaTemplateField->metaFields as $metaField) { + $metaField->setError('value', implode(', ', $existingMetaTemplateField->conflicts)); + } } } // automatically convert non-conflicting fields to new meta-template @@ -162,25 +217,29 @@ class MetaTemplatesController extends AppController public function index() { - $updateableTemplate = $this->MetaTemplates->checkForUpdates(); + $templatesUpdateStatus = $this->MetaTemplates->getUpdateStatusForTemplates(); $this->CRUD->index([ 'filters' => $this->filterFields, 'quickFilters' => $this->quickFilterFields, 'contextFilters' => [ - 'fields' => ['scope'], 'custom' => [ [ - 'label' => __('Default Templates'), - 'filterCondition' => ['is_default' => true] + 'default' => true, + 'label' => __('Newest Templates'), + 'filterConditionFunction' => function ($query) { + return $query->where([ + 'id IN' => $this->MetaTemplates->genQueryForAllNewestVersionIDs() + ]); + } ], ] ], 'contain' => $this->containFields, - 'afterFind' => function($data) use ($updateableTemplate) { + 'afterFind' => function($data) use ($templatesUpdateStatus) { foreach ($data as $i => $metaTemplate) { - if (!empty($updateableTemplate[$metaTemplate->uuid])) { - $updateStatusForTemplate = $this->MetaTemplates->checkUpdateForMetaTemplate($updateableTemplate[$metaTemplate->uuid]['template'], $metaTemplate); - $metaTemplate->set('status', $this->MetaTemplates->getTemplateStatus($updateStatusForTemplate, $metaTemplate)); + if (!empty($templatesUpdateStatus[$metaTemplate->uuid])) { + $templateStatus = $this->MetaTemplates->getStatusForMetaTemplate($templatesUpdateStatus[$metaTemplate->uuid]['template'], $metaTemplate); + $metaTemplate->set('updateStatus', $this->MetaTemplates->computeFullUpdateStatusForMetaTemplate($templateStatus, $metaTemplate)); } } return $data; @@ -190,14 +249,14 @@ class MetaTemplatesController extends AppController if (!empty($responsePayload)) { return $responsePayload; } - $updateableTemplate = [ - 'not_up_to_date' => $this->MetaTemplates->getNotUpToDateTemplates(), - 'can_be_removed' => $this->MetaTemplates->getCanBeRemovedTemplates(), + $updateableTemplates = [ + 'not-up-to-date' => $this->MetaTemplates->getNotUpToDateTemplates(), + 'can-be-removed' => $this->MetaTemplates->getCanBeRemovedTemplates(), 'new' => $this->MetaTemplates->getNewTemplates(), ]; $this->set('defaultTemplatePerScope', $this->MetaTemplates->getDefaultTemplatePerScope()); $this->set('alignmentScope', 'individuals'); - $this->set('updateableTemplate', $updateableTemplate); + $this->set('updateableTemplates', $updateableTemplates); } public function view($id) @@ -215,7 +274,7 @@ class MetaTemplatesController extends AppController public function delete($id) { $updateableTemplate = $this->getUpdateStatus($id); - if (empty($updateableTemplate['can_be_removed'])) { + if (empty($updateableTemplate['can-be-removed'])) { throw MethodNotAllowedException(__('This meta-template cannot be removed')); } $this->set('deletionText', __('The meta-template "{0}" has no meta-field and can be safely removed.', h($updateableTemplate['existing_template']->name))); @@ -247,18 +306,28 @@ class MetaTemplatesController extends AppController 'contain' => ['MetaTemplateFields'] ]); $templateOnDisk = $this->MetaTemplates->readTemplateFromDisk($metaTemplate->uuid); - $updateableTemplate = $this->MetaTemplates->checkUpdateForMetaTemplate($templateOnDisk, $metaTemplate); - return $updateableTemplate; + $templateStatus = $this->MetaTemplates->getStatusForMetaTemplate($templateOnDisk, $metaTemplate); + return $templateStatus; } - public function setUpdateStatus($id) + /** + * Retreive the template stored on disk and compute the status for the provided template id. + * + * @param [type] $id + * @return array + */ + private function setUpdateStatus($template_id): array { - $metaTemplate = $this->MetaTemplates->get($id, [ + $metaTemplate = $this->MetaTemplates->get($template_id, [ 'contain' => ['MetaTemplateFields'] ]); $templateOnDisk = $this->MetaTemplates->readTemplateFromDisk($metaTemplate->uuid); - $updateableTemplate = $this->MetaTemplates->checkUpdateForMetaTemplate($templateOnDisk, $metaTemplate); - $this->set('updateableTemplate', $updateableTemplate); + $templateStatus = $this->MetaTemplates->getStatusForMetaTemplate($templateOnDisk, $metaTemplate); $this->set('templateOnDisk', $templateOnDisk); + $this->set('templateStatus', $templateStatus); + return [ + 'templateOnDisk' => $templateOnDisk, + 'templateStatus' => $templateStatus, + ]; } } diff --git a/src/Model/Table/MetaTemplatesTable.php b/src/Model/Table/MetaTemplatesTable.php index 6e2ee09..046c9b2 100644 --- a/src/Model/Table/MetaTemplatesTable.php +++ b/src/Model/Table/MetaTemplatesTable.php @@ -9,6 +9,8 @@ use Cake\Validation\Validator; use Cake\Utility\Hash; use Cake\Utility\Inflector; use Cake\Utility\Text; +use Cake\Filesystem\File; +use Cake\Filesystem\Folder; class MetaTemplatesTable extends AppTable { @@ -16,7 +18,16 @@ class MetaTemplatesTable extends AppTable ROOT . '/libraries/default/meta_fields/', ROOT . '/libraries/custom/meta_fields/' ]; - + + public const UPDATE_STRATEGY_CREATE_NEW = 'create_new'; + public const UPDATE_STRATEGY_UPDATE_EXISTING = 'update_existing'; + public const UPDATE_STRATEGY_KEEP_BOTH = 'keep_both'; + public const UPDATE_STRATEGY_DELETE = 'delete_all'; + + public $ALLOWED_STRATEGIES = [MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW]; + + private $templatesOnDisk = null; + public function initialize(array $config): void { parent::initialize($config); @@ -46,44 +57,89 @@ class MetaTemplatesTable extends AppTable return $validator; } - public function update($template_uuid=null, $strategy=null) + public function isStrategyAllowed(string $strategy): bool + { + return in_array($strategy, $this->ALLOWED_STRATEGIES); + } + + // /** + // * Load the template stored on the disk for the provided id and update it using the optional strategy. + // * + // * @param int $template_id + // * @param string|null $strategy The strategy to be used when updating templates with conflicts + // * @return array The update result containing potential errors and the successes + // */ + // public function update($template_id, $strategy = null): array + // { + // $files_processed = []; + // $readErrors = []; + // $preUpdateChecks = []; + // $updatesErrors = []; + // $templates = $this->readTemplatesFromDisk($readErrors); + // foreach ($templates as $template) { + // $updateStatus = $this->getUpdateStatusForTemplates($template['uuid']); + // $preUpdateChecks[$template['uuid']] = $updateStatus; + // if (is_null($template_uuid) || $template_uuid == $template['uuid']) { + // $errors = []; + // $success = false; + // if ($updateStatus['up-to-date']) { + // $errors['message'] = __('Meta-template already up-to-date'); + // $success = true; + // } else if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) && $updateStatus['new']) { + // $success = $this->saveNewMetaTemplate($template, $errors); + // } else if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING) && $updateStatus['automatically-updateable']) { + // $success = $this->updateMetaTemplate($template, $errors); + // } else if (!$updateStatus['up-to-date'] && (is_null($strategy) || !$this->isStrategyAllowed($strategy))) { + // $errors['message'] = __('Cannot update meta-template, update strategy not provided or not allowed'); + // } else if (!$updateStatus['up-to-date'] && !is_null($strategy)) { + // $success = $this->updateMetaTemplateWithStrategyRouter($template, $strategy, $errors); + // } else { + // $errors['message'] = __('Could not update. Something went wrong.'); + // } + // if ($success) { + // $files_processed[] = $template['uuid']; + // } + // if (!empty($errors)) { + // $updatesErrors[] = $errors; + // } + // } + // } + // $results = [ + // 'read_errors' => $readErrors, + // 'pre_update_errors' => $preUpdateChecks, + // 'update_errors' => $updatesErrors, + // 'files_processed' => $files_processed, + // 'success' => !empty($files_processed), + // ]; + // return $results; + // } + + /** + * Load the templates stored on the disk update and create them in the database without touching at the existing ones + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return array The update result containing potential errors and the successes + */ + public function updateAllTemplates(): array { - $files_processed = []; - $readErrors = []; - $preUpdateChecks = []; $updatesErrors = []; - $templates = $this->readTemplatesFromDisk($readErrors); - foreach ($templates as $template) { - $updateStatus = $this->checkForUpdates($template['uuid']); - $preUpdateChecks[$template['uuid']] = $updateStatus; - if (is_null($template_uuid) || $template_uuid == $template['uuid']) { - $errors = []; - $success = false; - if ($updateStatus['up-to-date']) { - $errors['message'] = __('Meta-template already up-to-date'); - $success = true; - } else if ($updateStatus['new']) { - $success = $this->saveNewMetaTemplate($template, $errors); - } else if ($updateStatus['updateable']) { - $success = $this->updateMetaTemplate($template, $errors); - } else if (!$updateStatus['up-to-date'] && is_null($strategy)) { - $errors['message'] = __('Cannot update meta-template, update strategy not provided'); - } else if (!$updateStatus['up-to-date'] && !is_null($strategy)) { - $success = $this->updateMetaTemplateWithStrategy($template, $strategy, $errors); - } else { - $errors['message'] = __('Could not update. Something went wrong.'); - } - if ($success) { - $files_processed[] = $template['uuid']; - } - if (!empty($errors)) { - $updatesErrors[] = $errors; - } + $files_processed = []; + $templatesOnDisk = $this->readTemplatesFromDisk(); + $templatesUpdateStatus = $this->getUpdateStatusForTemplates(); + foreach ($templatesOnDisk as $template) { + $errors = []; + $success = false; + $updateStatus = $templatesUpdateStatus[$template['uuid']]; + if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) && $updateStatus['new']) { + $success = $this->saveNewMetaTemplate($template, $errors); + } + if ($success) { + $files_processed[] = $template['uuid']; + } else { + $updatesErrors[] = $errors; } } $results = [ - 'read_errors' => $readErrors, - 'pre_update_errors' => $preUpdateChecks, 'update_errors' => $updatesErrors, 'files_processed' => $files_processed, 'success' => !empty($files_processed), @@ -91,132 +147,267 @@ class MetaTemplatesTable extends AppTable return $results; } - public function checkForUpdates($template_uuid=null): array + /** + * Load the template stored on the disk for the provided meta-template and update it using the optional strategy. + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @param string|null $strategy The strategy to be used when updating templates with conflicts + * @return array The update result containing potential errors and the successes + */ + public function update($metaTemplate, $strategy = null): array { - $templates = $this->readTemplatesFromDisk($readErrors); - $result = []; - foreach ($templates as $template) { - if (is_null($template_uuid)) { - $result[$template['uuid']] = $this->checkUpdatesForTemplate($template); - } else if ($template['uuid'] == $template_uuid) { - $result = $this->checkUpdatesForTemplate($template); - return $result; - } + $files_processed = []; + $updatesErrors = []; + $templateOnDisk = $this->readTemplateFromDisk($metaTemplate->uuid); + $templateStatus = $this->getStatusForMetaTemplate($templateOnDisk, $metaTemplate); + $updateStatus = $this->computeFullUpdateStatusForMetaTemplate($templateStatus, $metaTemplate); + $errors = []; + $success = false; + if ($updateStatus['up-to-date']) { + $errors['message'] = __('Meta-template already up-to-date'); + $success = true; + } else if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) && $updateStatus['new']) { + $success = $this->saveNewMetaTemplate($templateOnDisk, $errors); + } else if ($this->isStrategyAllowed(MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING) && $updateStatus['automatically-updateable']) { + $success = $this->updateMetaTemplate($metaTemplate, $templateOnDisk, $errors); + } else if (!$updateStatus['up-to-date'] && (is_null($strategy) || !$this->isStrategyAllowed($strategy))) { + $errors['message'] = __('Cannot update meta-template, update strategy not provided or not allowed'); + } else if (!$updateStatus['up-to-date'] && !is_null($strategy)) { + $success = $this->updateMetaTemplateWithStrategyRouter($metaTemplate, $templateOnDisk, $strategy, $errors); + } else { + $errors['message'] = __('Could not update. Something went wrong.'); } - return $result; + if ($success) { + $files_processed[] = $templateOnDisk['uuid']; + } + if (!empty($errors)) { + $updatesErrors[] = $errors; + } + $results = [ + 'update_errors' => $updatesErrors, + 'files_processed' => $files_processed, + 'success' => !empty($files_processed), + ]; + return $results; } - public function isUpToDate(array $updateResult): bool + /** + * Load the templates stored on the disk and compute their update status. + * Only compute the result if an UUID is provided + * + * @param string|null $template_uuid + * @return array + */ + public function getUpdateStatusForTemplates(): array { - return $updateResult['up-to-date'] || $updateResult['new']; + $errors = []; + $templateUpdatesStatus = []; + $templates = $this->readTemplatesFromDisk($errors); + foreach ($templates as $template) { + $templateUpdatesStatus[$template['uuid']] = $this->getUpdateStatusForTemplate($template); + } + return $templateUpdatesStatus; } - public function isUpdateable(array $updateResult): bool + + /** + * Checks if the template is update-to-date from the provided update status + * + * @param array $updateStatus + * @return boolean + */ + public function isUpToDate(array $updateStatus): bool { - return $updateResult['updateable']; + return !empty($updateStatus['up-to-date']) || !empty($updateStatus['new']); } - public function isNew(array $updateResult): bool + /** + * Checks if the template is updateable automatically from the provided update status + * + * @param array $updateStatus + * @return boolean + */ + public function isAutomaticallyUpdateable(array $updateStatus): bool { - return $updateResult['new']; + return !empty($updateStatus['automatically-updateable']); } - public function hasNoConflict(array $updateResult): bool + /** + * Checks if the template is new (and not loaded in the database yet) from the provided update status + * + * @param array $updateStatus + * @return boolean + */ + public function isNew(array $updateStatus): bool { - return $this->hasConflict($updateResult); + return $updateStatus['new']; } - public function hasConflict(array $updateResult): bool + /** + * Checks if the template has no conflicts that would prevent an automatic update from the provided update status + * + * @param array $updateStatus + * @return boolean + */ + public function hasNoConflict(array $updateStatus): bool { - return !$updateResult['updateable'] && !$updateResult['up-to-date'] && !$updateResult['new']; + return $this->hasConflict($updateStatus); } - public function isUpdateableToExistingMetaTemplate($metaTemplate): bool + /** + * Checks if the template has conflict preventing an automatic update from the provided update status + * + * @param array $updateStatus + * @return boolean + */ + public function hasConflict(array $updateStatus): bool + { + return empty($updateStatus['automatically-updateable']) && empty($updateStatus['up-to-date']) && empty($updateStatus['new']); + } + + /** + * Checks if the metaTemplate can be updated to a newer version loaded in the database + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return boolean + */ + public function isUpdateableToExistingMetaTemplate(\App\Model\Entity\MetaTemplate $metaTemplate): bool { $newestTemplate = $this->getNewestVersion($metaTemplate); return !empty($newestTemplate); } - public function isRemovable(array $updateResult): bool + /** + * Checks if the template can be removed from the database for the provided update status. + * A template can be removed if a newer version is already loaded in the database and no meta-fields are using it. + * + * @param array $updateStatus + * @return boolean + */ + public function isRemovable(array $updateStatus): bool { - return !empty($updateResult['can_be_removed']); + return !empty($updateStatus['can-be-removed']); } - public function getTemplateStatus(array $updateResult, $metaTemplate): array + /** + * Compute the state from the provided update status and metaTemplate + * + * @param array $updateStatus + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return array + */ + public function computeFullUpdateStatusForMetaTemplate(array $updateStatus, \App\Model\Entity\MetaTemplate $metaTemplate): array { return [ - 'up_to_date' => $this->isUpToDate($updateResult), - 'updateable' => $this->isUpdateable($updateResult), - 'is_new' => $this->isNew($updateResult), - 'has_conflict' => $this->hasConflict($updateResult), - 'to_existing' => $this->isUpdateableToExistingMetaTemplate($metaTemplate), - 'can_be_removed' => $this->isRemovable($updateResult), + 'up-to-date' => $this->isUpToDate($updateStatus), + 'automatically-updateable' => $this->isAutomaticallyUpdateable($updateStatus), + 'is-new' => $this->isNew($updateStatus), + 'has-conflict' => $this->hasConflict($updateStatus), + 'to-existing' => $this->isUpdateableToExistingMetaTemplate($metaTemplate), + 'can-be-removed' => $this->isRemovable($updateStatus), ]; } - public function getUpToDateTemplates($result=null): array + /** + * Get the update status of meta-templates that are up-to-date in regards to the template stored on the disk. + * + * @param array|null $updateStatus + * @return array The list of update status for up-to-date templates + */ + public function getUpToDateTemplates($updatesStatus = null): array { - $result = is_null($result) ? $this->checkForUpdates() : $result; - foreach ($result as $i => $updateResult) { - if (!$this->isUpToDate($updateResult)) { - unset($result[$i]); + $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus; + foreach ($updatesStatus as $uuid => $updateStatus) { + if (!$this->isUpToDate($updateStatus)) { + unset($updatesStatus[$uuid]); } } - return $result; + return $updatesStatus; } - public function getNotUpToDateTemplates($result=null): array + /** + * Get the update status of meta-templates that are not up-to-date in regards to the template stored on the disk. + * + * @param array|null $updateResult + * @return array The list of update status for non up-to-date templates + */ + public function getNotUpToDateTemplates($updatesStatus = null): array { - $result = is_null($result) ? $this->checkForUpdates() : $result; - foreach ($result as $i => $updateResult) { - if ($this->isUpToDate($updateResult)) { - unset($result[$i]); + $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus; + foreach ($updatesStatus as $uuid => $updateStatus) { + if ($this->isUpToDate($updateStatus)) { + unset($updatesStatus[$uuid]); } } - return $result; + return $updatesStatus; } - public function getUpdateableTemplates($result=null): array + /** + * Get the update status of meta-templates that are automatically updateable in regards to the template stored on the disk. + * + * @param array|null $updateResult + * @return array The list of update status for non up-to-date templates + */ + public function getAutomaticallyUpdateableTemplates($updatesStatus = null): array { - $result = is_null($result) ? $this->checkForUpdates() : $result; - foreach ($result as $i => $updateResult) { - if (!$this->isUpdateable($updateResult)) { - unset($result[$i]); + $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus; + foreach ($updatesStatus as $uuid => $updateStatus) { + if (!$this->isAutomaticallyUpdateable($updateStatus)) { + unset($updatesStatus[$uuid]); } } - return $result; + return $updatesStatus; } - public function getNewTemplates($result=null): array + /** + * Get the update status of meta-templates that are new in regards to the template stored on the disk. + * + * @param array|null $updateResult + * @return array The list of update status for new templates + */ + public function getNewTemplates($updatesStatus = null): array { - $result = is_null($result) ? $this->checkForUpdates() : $result; - foreach ($result as $i => $updateResult) { - if (!$this->isNew($updateResult)) { - unset($result[$i]); + $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus; + foreach ($updatesStatus as $uuid => $updateStatus) { + if (!$this->isNew($updateStatus)) { + unset($updatesStatus[$uuid]); } } - return $result; + return $updatesStatus; } - public function getConflictTemplates($result=null): array + /** + * Get the update status of meta-templates that have conflict preventing an automatic update in regards to the template stored on the disk. + * + * @param array|null $updateResult + * @return array The list of update status for new templates + */ + public function getConflictTemplates($updatesStatus = null): array { - $result = is_null($result) ? $this->checkForUpdates() : $result; - foreach ($result as $i => $updateResult) { - if (!$this->hasConflict($updateResult)) { - unset($result[$i]); + $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus; + foreach ($updatesStatus as $uuid => $updateStatus) { + if (!$this->hasConflict($updateStatus)) { + unset($updatesStatus[$uuid]); } } - return $result; + return $updatesStatus; } - public function getNewestVersion($metaTemplate, $full=false) + /** + * Get the latest (having the higher version) meta-template loaded in the database for the provided meta-template + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @param boolean $full + * @return \App\Model\Entity\MetaTemplate|null + */ + public function getNewestVersion(\App\Model\Entity\MetaTemplate $metaTemplate, bool $full = false) { $query = $this->find()->where([ 'uuid' => $metaTemplate->uuid, 'id !=' => $metaTemplate->id, 'version >=' => $metaTemplate->version, ]) - ->order(['version' => 'DESC']); + ->order(['version' => 'DESC']); if ($full) { $query->contain(['MetaTemplateFields']); } @@ -224,19 +415,63 @@ class MetaTemplatesTable extends AppTable return $newestTemplate; } - public function getCanBeRemovedTemplates($result=null): array + /** + * Generate and return a query (to be used as a subquery) resolving to the IDs of the latest version of a saved meta-template + * + * @return \Cake\ORM\Query + */ + public function genQueryForAllNewestVersionIDs(): \Cake\ORM\Query { - $result = is_null($result) ? $this->checkForUpdates() : $result; - foreach ($result as $i => $updateResult) { - if (!$this->isRemovable($updateResult)) { - unset($result[$i]); - } - } - return $result; + /** + * SELECT a.id FROM meta_templates a INNER JOIN ( + * SELECT uuid, MAX(version) maxVersion FROM meta_templates GROUP BY uuid + * ) b on a.uuid = b.uuid AND a.version = b.maxVersion; + */ + $query = $this->find() + ->select([ + 'id' + ]) + ->join([ + 't' => [ + 'table' => '(SELECT uuid, MAX(version) AS maxVersion FROM meta_templates GROUP BY uuid)', + 'type' => 'INNER', + 'conditions' => [ + 't.uuid = MetaTemplates.uuid', + 't.maxVersion = MetaTemplates.version' + ], + ], + ]); + return $query; } - public function readTemplatesFromDisk(&$errors=[]): array + /** + * Get the update status of meta-templates that can be removed. + * + * @param array|null $updateResult + * @return array The list of update status for new templates + */ + public function getCanBeRemovedTemplates($updatesStatus = null): array { + $updatesStatus = is_null($updatesStatus) ? $this->getUpdateStatusForTemplates() : $updatesStatus; + foreach ($updatesStatus as $i => $updateStatus) { + if (!$this->isRemovable($updateStatus)) { + unset($updatesStatus[$i]); + } + } + return $updatesStatus; + } + + /** + * Reads all template stored on the disk and parse them + * + * @param array|null $errors Contains errors while parsing the meta-templates + * @return array The parsed meta-templates stored on the disk + */ + public function readTemplatesFromDisk(&$errors = []): array + { + if (!is_null($this->templatesOnDisk)) { + return $this->templatesOnDisk; + } $templates = []; $errors = []; foreach (self::TEMPLATE_PATH as $path) { @@ -245,9 +480,9 @@ class MetaTemplatesTable extends AppTable foreach ($files as $k => $file) { if (substr($file, -5) === '.json') { $errorMessage = ''; - $metaTemplate = $this->decodeTemplateFromDisk($path . $file, $errorMessage); - if (!empty($metaTemplate)) { - $templates[] = $metaTemplate; + $template = $this->decodeTemplateFromDisk($path . $file, $errorMessage); + if (!empty($template)) { + $templates[] = $template; } else { $errors[] = $errorMessage; } @@ -255,10 +490,18 @@ class MetaTemplatesTable extends AppTable } } } + $this->templatesOnDisk = $templates; return $templates; } - public function readTemplateFromDisk(string $uuid, &$error=''): ?array + /** + * Read and parse the meta-template stored on disk having the provided UUID + * + * @param string $uuid + * @param string $error Contains the error while parsing the meta-template + * @return array|null The meta-template or null if not templates matche the provided UUID + */ + public function readTemplateFromDisk(string $uuid, &$error = ''): ?array { foreach (self::TEMPLATE_PATH as $path) { if (is_dir($path)) { @@ -266,9 +509,9 @@ class MetaTemplatesTable extends AppTable foreach ($files as $k => $file) { if (substr($file, -5) === '.json') { $errorMessage = ''; - $metaTemplate = $this->decodeTemplateFromDisk($path . $file, $errorMessage); - if (!empty($metaTemplate) && $metaTemplate['uuid'] == $uuid) { - return $metaTemplate; + $template = $this->decodeTemplateFromDisk($path . $file, $errorMessage); + if (!empty($template) && $template['uuid'] == $uuid) { + return $template; } } } @@ -278,13 +521,24 @@ class MetaTemplatesTable extends AppTable return null; } - public function decodeTemplateFromDisk(string $filePath, &$errorMessage=''): ?array + /** + * Read and decode the meta-template located at the provided path + * + * @param string $filePath + * @param string $errorMessage + * @return array|null The meta-template or null if there was an error while trying to decode + */ + public function decodeTemplateFromDisk(string $filePath, &$errorMessage = ''): ?array { - if (file_exists($filePath)) { - $explodedPath = explode('/', $filePath); - $filename = $explodedPath[count($explodedPath)-1]; - $contents = file_get_contents($filePath); - $metaTemplate = json_decode($contents, true); + $file = new File($filePath, false); + if ($file->exists()) { + $filename = $file->name(); + $content = $file->read(); + if (empty($content)) { + $errorMessage = __('Could not read template file `{0}`.', $filename); + return null; + } + $metaTemplate = json_decode($content, true); if (empty($metaTemplate)) { $errorMessage = __('Could not load template file `{0}`. Error while decoding the template\'s JSON', $filename); return null; @@ -295,27 +549,34 @@ class MetaTemplatesTable extends AppTable } return $metaTemplate; } + $errorMessage = __('File does not exists'); + return null; } - public function getEntitiesWithMetaFieldsToUpdate(int $template_id): array + /** + * Collect all enties having meta-fields belonging to the provided template + * + * @param integer $template_id + * @return array List of entities + */ + public function getEntitiesHavingMetaFieldsFromTemplate(int $metaTemplateId): array { - $metaTemplate = $this->get($template_id); + $metaTemplate = $this->get($metaTemplateId); $queryParentEntities = $this->MetaTemplateFields->MetaFields->find(); $queryParentEntities ->select(['parent_id']) ->where([ - 'meta_template_id' => $template_id + 'meta_template_id' => $metaTemplateId ]) ->group(['parent_id']); - $entitiesClassName = Inflector::camelize(Inflector::pluralize($metaTemplate->scope)); - $entitiesTable = TableRegistry::getTableLocator()->get($entitiesClassName); + $entitiesTable = $this->getTableForMetaTemplateScope($metaTemplate); $entityQuery = $entitiesTable->find() ->where(['id IN' => $queryParentEntities]) ->contain([ 'MetaFields' => [ 'conditions' => [ - 'meta_template_id' => $template_id + 'meta_template_id' => $metaTemplateId ] ] ]); @@ -323,28 +584,59 @@ class MetaTemplatesTable extends AppTable return $entities; } - public function getKeyedMetaFields(string $scope, int $entity_id, array $conditions=[]) + /** + * Get the table linked to the meta-template + * + * @param \App\Model\Entity\MetaTemplate|string $metaTemplate + * @return \App\Model\Table\AppTable + */ + private function getTableForMetaTemplateScope($metaTemplateOrScope): \App\Model\Table\AppTable + { + if (is_string($metaTemplateOrScope)) { + $scope = $metaTemplateOrScope; + } else { + $scope = $metaTemplateOrScope->scope; + } + $entitiesClassName = Inflector::camelize(Inflector::pluralize($scope)); + $entitiesTable = TableRegistry::getTableLocator()->get($entitiesClassName); + return $entitiesTable; + } + + /** + * Get the meta-field keyed by their template_id and meta_template_id belonging to the provided entity + * + * @param integer $entity_id The entity for which the meta-fields belongs to + * @param array $conditions Additional conditions to be passed to the meta-fields query + * @return array The associated array containing the meta-fields keyed by their meta-template and meta-template-field IDs + */ + public function getKeyedMetaFieldsForEntity(int $entity_id, array $conditions = []): array { $query = $this->MetaTemplateFields->MetaFields->find(); $query->where(array_merge( $conditions, [ - 'MetaFields.scope' => $scope, 'MetaFields.parent_id' => $entity_id ] )); $metaFields = $query->all(); - $data = []; + $keyedMetaFields = []; foreach ($metaFields as $metaField) { - if (empty($data[$metaField->meta_template_id][$metaField->meta_template_field_id])) { - $data[$metaField->meta_template_id][$metaField->meta_template_field_id] = []; + if (empty($keyedMetaFields[$metaField->meta_template_id][$metaField->meta_template_field_id])) { + $keyedMetaFields[$metaField->meta_template_id][$metaField->meta_template_field_id] = []; } - $data[$metaField->meta_template_id][$metaField->meta_template_field_id][$metaField->id] = $metaField; + $keyedMetaFields[$metaField->meta_template_id][$metaField->meta_template_field_id][$metaField->id] = $metaField; } - return $data; + return $keyedMetaFields; } - public function mergeMetaFieldsInMetaTemplate(array $keyedMetaFields, array $metaTemplates) + /** + * Insert the keyed meta-fields into the provided meta-templates + * + * @param array $keyedMetaFields An associative array containing the meta-fields keyed by their meta-template and meta-template-field IDs + * @param array $metaTemplates List of meta-templates + * @return array The list of meta-template with the meta-fields inserted + */ + public function insertMetaFieldsInMetaTemplates(array $keyedMetaFields, array $metaTemplates): array { $merged = []; foreach ($metaTemplates as $metaTemplate) { @@ -363,28 +655,29 @@ class MetaTemplatesTable extends AppTable return $merged; } - public function migrateMetaTemplateToNewVersion(\App\Model\Entity\MetaTemplate $oldMetaTemplate, \App\Model\Entity\MetaTemplate $newMetaTemplate, int $entityId) + /** + * Retreive the entity associated for the provided meta-template and id + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @param integer $entity_id + * @return \App\Model\Entity\AppModel + */ + public function getEntity(\App\Model\Entity\MetaTemplate $metaTemplate, int $entity_id): \App\Model\Entity\AppModel { - $entitiesClassName = Inflector::camelize(Inflector::pluralize($oldMetaTemplate->scope)); - $entitiesTable = TableRegistry::getTableLocator()->get($entitiesClassName); - $entity = $entitiesTable->get($entityId, [ + $entitiesTable = $this->getTableForMetaTemplateScope($metaTemplate); + $entity = $entitiesTable->get($entity_id, [ 'contain' => 'MetaFields' ]); return $entity; } - public function getTemplate($id) - { - $query = $this->find(); - $query->where(['id' => $id]); - $template = $query->first(); - if (empty($template)) { - throw new NotFoundException(__('Invalid template ID specified.')); - } - return $template; - } - - public function getDefaultTemplatePerScope(String $scope = '') + /** + * Collect the unique default template for each scope + * + * @param string|null $scope + * @return array The list of default template + */ + public function getDefaultTemplatePerScope($scope = null): array { $query = $this->find('list', [ 'keyField' => 'scope', @@ -398,16 +691,48 @@ class MetaTemplatesTable extends AppTable return $query->all()->toArray(); } - public function removeDefaultFlag(String $scope) + /** + * Remove the default flag for all meta-templates belonging to the provided scope + * + * @param string $scope + * @return int the number of updated rows + */ + public function removeDefaultFlag(string $scope): int { - $this->updateAll( + return $this->updateAll( ['is_default' => false], ['scope' => $scope] ); } - public function saveNewMetaTemplate(array $template, array &$errors=[], &$savedMetaTemplate=null): bool + /** + * Check if the provided template can be saved in the database without creating duplicate template in regards to the UUID and version + * + * @param array $template + * @return boolean + */ + public function canBeSavedWithoutDuplicates(array $template): bool { + $query = $this->find()->where([ + 'uuid' => $template['uuid'], + 'version' => $template['version'], + ]); + return $query->count() == 0; + } + + /** + * Create and save the provided template in the database + * + * @param array $template The template to be saved + * @param array $errors The list of errors that occured during the save process + * @param \App\Model\Entity\MetaTemplate $savedMetaTemplate The metaTemplate entity that has just been saved + * @return boolean True if the save was successful, False otherwise + */ + public function saveNewMetaTemplate(array $template, array &$errors = [], \App\Model\Entity\MetaTemplate &$savedMetaTemplate = null): bool + { + if (!$this->canBeSavedWithoutDuplicates($template)) { + $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']); + } $template['meta_template_fields'] = $template['metaFields']; unset($template['metaFields']); $metaTemplate = $this->newEntity($template, [ @@ -418,19 +743,25 @@ class MetaTemplatesTable extends AppTable ]); $error = null; if (empty($tmp)) { - $error = new UpdateError(); - $error->success = false; - $error->message = __('Could not save the template.'); - $error->errors = $metaTemplate->getErrors(); - $errors[] = $error; + $errors[] = new UpdateError(false, __('Could not save the template.'), $metaTemplate->getErrors()); } $savedMetaTemplate = $tmp; return !is_null($error); } - public function updateMetaTemplate(array $template, array &$errors=[]): bool + /** + * Update an existing meta-template and save it in the database + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate The meta-template to update + * @param array $template The template to use to update the existing meta-template + * @param array $errors + * @return boolean True if the save was successful, False otherwise + */ + public function updateMetaTemplate(\App\Model\Entity\MetaTemplate $metaTemplate, array $template, array &$errors = []): bool { - $metaTemplate = $this->getMetaTemplateElligibleForUpdate($template); + if (!$this->canBeSavedWithoutDuplicates($template)) { + $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']); + } if (is_string($metaTemplate)) { $errors[] = new UpdateError(false, $metaTemplate); return false; @@ -448,77 +779,76 @@ class MetaTemplatesTable extends AppTable return true; } - public function updateMetaTemplateWithStrategy(array $template, string $strategy, array $errors=[]): bool + /** + * Update an existing meta-template with the provided strategy and save it in the database + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate The meta-template to update + * @param array $template The template to use to update the existing meta-template + * @param string $strategy The strategy to use when handling update conflicts + * @param array $errors + * @return boolean True if the save was successful, False otherwise + */ + public function updateMetaTemplateWithStrategyRouter(\App\Model\Entity\MetaTemplate $metaTemplate, array $template, string $strategy, array &$errors = []): bool { - $metaTemplate = $this->getMetaTemplateElligibleForUpdate($template); + if (!$this->canBeSavedWithoutDuplicates($template)) { + $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']); + } if (is_string($metaTemplate)) { $errors[] = new UpdateError(false, $metaTemplate); return false; } - $success = $this->executeUpdateStrategy($strategy, $template, $metaTemplate); - if (is_string($success)) { - $errors[] = new UpdateError(false, $success); + if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH) { + $result = $this->executeStrategyKeep($template, $metaTemplate); + } else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_DELETE) { + $result = $this->executeStrategyDeleteAll($template, $metaTemplate); + } else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) { + $result = $this->executeStrategyCreateNew($template, $metaTemplate); + } else { + $errors[] = new UpdateError(false, __('Invalid strategy {0}', $strategy)); + return false; + } + if (is_string($result)) { + $errors[] = new UpdateError(false, $result); return false; } return true; } - public function getMetaTemplateElligibleForUpdate($template) - { - $query = $this->find() - ->contain('MetaTemplateFields')->where([ - 'uuid' => $template['uuid'] - ]); - $metaTemplate = $query->first(); - if (empty($metaTemplate)) { - return __('Meta-template not found.'); - } - if ($metaTemplate->version >= $template['version']) { - return __('Could not update the template. Local version is newer.'); - } - return $metaTemplate; - } - - public function executeUpdateStrategy(string $strategy, array $template, \App\Model\Entity\MetaTemplate $metaTemplate) - { - if ($strategy == 'keep_both') { - $result = $this->executeStrategyKeep($template, $metaTemplate); - } else if ($strategy == 'delete_all') { - $result = $this->executeStrategyDeleteAll($template, $metaTemplate); - } else { - return __('Invalid strategy {0}', $strategy); - } - if (is_string($result)) { - return $result; - } - return true; - } - - // Old template remains untouched - // Create new template - // Migrate all non-conflicting meta-fields for one entity to the new template - // Keep all the conflicting meta-fields for one entity on the old template + /** + * Execute the `keep_both` update strategy by creating a new meta-template and moving non-conflicting entities to this one. + * Strategy: + * - Old template remains untouched + * - Create new template + * - Migrate all non-conflicting meta-fields for one entity to the new template + * - Keep all the conflicting meta-fields for one entity on the old template + * + * @param array $template + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return bool|string If the new template could be saved or the error message + */ public function executeStrategyKeep(array $template, \App\Model\Entity\MetaTemplate $metaTemplate) { - $savedMetaTemplate = null; - $conflicts = $this->checkForMetaTemplateConflicts($metaTemplate, $template); + if (!$this->canBeSavedWithoutDuplicates($template)) { + $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']); + } + $conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template); $blockingConflict = Hash::extract($conflicts, '{s}.conflicts'); $errors = []; if (empty($blockingConflict)) { // No conflict, everything can be updated without special care - $this->updateMetaTemplate($template, $errors); + $this->updateMetaTemplate($metaTemplate, $template, $errors); return !empty($errors) ? $errors[0] : true; } - $entities = $this->fetchEntitiesWithMetaFieldsForTemplate($metaTemplate); + $entities = $this->getEntitiesHavingMetaFieldsFromTemplate($metaTemplate->id); $conflictingEntities = []; foreach ($entities as $entity) { - $conflicts = $this->checkMetaFieldsValidityUnderTemplate($entity['meta_fields'], $template); + $conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity['meta_fields'], $template); if (!empty($conflicts)) { $conflictingEntities[$entity->id] = $entity->id; } } if (empty($conflictingEntities)) { - $this->updateMetaTemplate($template, $errors); + $this->updateMetaTemplate($metaTemplate, $template, $errors); return !empty($errors) ? $errors[0] : true; } $template['is_default'] = $metaTemplate['is_default']; @@ -527,14 +857,15 @@ class MetaTemplatesTable extends AppTable $metaTemplate->set('is_default', false); $this->save($metaTemplate); } - $success = $this->saveNewMetaTemplate($template, $errors, $savedMetaTemplate); - if (!empty($savedMetaTemplate)) { // conflicting entities remain untouched + $savedMetaTemplate = null; + $this->saveNewMetaTemplate($template, $errors, $savedMetaTemplate); + if (!empty($savedMetaTemplate)) { $savedMetaTemplateFieldByName = Hash::combine($savedMetaTemplate['meta_template_fields'], '{n}.field', '{n}'); foreach ($entities as $entity) { - if (empty($conflictingEntities[$entity->id])) { + if (empty($conflictingEntities[$entity->id])) { // conflicting entities remain untouched foreach ($entity['meta_fields'] as $metaField) { $savedMetaTemplateField = $savedMetaTemplateFieldByName[$metaField->field]; - $success = $this->replaceMetaTemplate($metaField, $savedMetaTemplateField); + $this->supersedeMetaFieldWithMetaTemplateField($metaField, $savedMetaTemplateField); } } } @@ -544,30 +875,79 @@ class MetaTemplatesTable extends AppTable return true; } - // Delete conflicting meta-fields - // Update template to the new version - public function executeStrategyDeleteAll($template, $metaTemplate) + /** + * Execute the `delete_all` update strategy by updating the meta-template and deleting all conflicting meta-fields. + * Strategy: + * - Delete conflicting meta-fields + * - Update template to the new version + * + * @param array $template + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return bool|string If the new template could be saved or the error message + */ + public function executeStrategyDeleteAll(array $template, \App\Model\Entity\MetaTemplate $metaTemplate) { + if (!$this->canBeSavedWithoutDuplicates($template)) { + $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']); + } $errors = []; - $conflicts = $this->checkForMetaTemplateConflicts($metaTemplate, $template); + $conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template); $blockingConflict = Hash::extract($conflicts, '{s}.conflicts'); if (empty($blockingConflict)) { // No conflict, everything can be updated without special care - $this->updateMetaTemplate($template, $errors); + $this->updateMetaTemplate($metaTemplate, $template, $errors); return !empty($errors) ? $errors[0] : true; } - $entities = $this->fetchEntitiesWithMetaFieldsForTemplate($metaTemplate); + $entities = $this->getEntitiesHavingMetaFieldsFromTemplate($metaTemplate->id); foreach ($entities as $entity) { - $conflicts = $this->checkMetaFieldsValidityUnderTemplate($entity['meta_fields'], $template); - $result = $this->MetaTemplateFields->MetaFields->deleteAll([ + $conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity['meta_fields'], $template); + $deletedCount = $this->MetaTemplateFields->MetaFields->deleteAll([ 'id IN' => $conflicts ]); } - $this->updateMetaTemplate($template, $errors); + $this->updateMetaTemplate($metaTemplate, $template, $errors); return !empty($errors) ? $errors[0] : true; } - public function replaceMetaTemplate(\App\Model\Entity\MetaField $metaField, \App\Model\Entity\MetaTemplateField $savedMetaTemplateField) + /** + * Execute the `create_new` update strategy by creating a new meta-template + * Strategy: + * - Create a new meta-template + * - Make the new meta-template `default` and `enabled` if previous template had these states + * - Turn of these states on the old meta-template + * + * @param array $template + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return bool|string If the new template could be saved or the error message + */ + public function executeStrategyCreateNew(array $template, \App\Model\Entity\MetaTemplate $metaTemplate) + { + if (!$this->canBeSavedWithoutDuplicates($template)) { + $errors[] = new UpdateError(false, __('Could not save the template. A template with this UUID and version already exists'), ['A template with UUID and version already exists']); + } + $errors = []; + $template['is_default'] = $metaTemplate->is_default; + $template['enabled'] = $metaTemplate->enabled; + $savedMetaTemplate = null; + $success = $this->saveNewMetaTemplate($template, $errors, $savedMetaTemplate); + if ($success) { + if ($metaTemplate->is_default) { + $metaTemplate->set('is_default', false); + $metaTemplate->set('enabled', false); + $this->save($metaTemplate); + } + } + return !empty($errors) ? $errors[0] : true; + } + + /** + * Supersede a meta-fields's meta-template-field with the provided one. + * + * @param \App\Model\Entity\MetaField $metaField + * @param \App\Model\Entity\MetaTemplateField $savedMetaTemplateField + * @return bool True if the replacement was a success, False otherwise + */ + public function supersedeMetaFieldWithMetaTemplateField(\App\Model\Entity\MetaField $metaField, \App\Model\Entity\MetaTemplateField $savedMetaTemplateField): bool { $metaField->set('meta_template_id', $savedMetaTemplateField->meta_template_id); $metaField->set('meta_template_field_id', $savedMetaTemplateField->id); @@ -575,34 +955,24 @@ class MetaTemplatesTable extends AppTable return !empty($metaField); } - public function checkMetaFieldsValidityUnderTemplate(array $metaFields, array $template): array + /** + * Compute the validity of the provided meta-fields under the provided meta-template + * + * @param \App\Model\Entity\MetaField[] $metaFields + * @param array|\App\Model\Entity\MetaTemplate $template + * @return \App\Model\Entity\MetaField[] The list of conflicting meta-fields under the provided template + */ + public function getMetaFieldsConflictsUnderTemplate(array $metaFields, $template): array { $conflicting = []; $metaTemplateFieldByName = []; - foreach ($template['metaFields'] as $metaField) { - $metaTemplateFieldByName[$metaField['field']] = $this->MetaTemplateFields->newEntity($metaField); + $existingMetaTemplate = !is_array($template); + $metaTemplateFields = !$existingMetaTemplate ? $template['metaFields'] : $template->meta_template_fields; + foreach ($metaTemplateFields as $metaTemplateField) { + $metaTemplateFieldByName[$metaTemplateField['field']] = $this->MetaTemplateFields->newEntity($metaTemplateField); } foreach ($metaFields as $metaField) { - $isValid = $this->MetaTemplateFields->MetaFields->isValidMetaFieldForMetaTemplateField( - $metaField->value, - $metaTemplateFieldByName[$metaField->field] - ); - if ($isValid !== true) { - $conflicting[] = $metaField; - } - } - return $conflicting; - } - - public function checkMetaFieldsValidityUnderExistingMetaTemplate(array $metaFields, \App\Model\Entity\MetaTemplate $metaTemplate): array - { - $conflicting = []; - $metaTemplateFieldByName = []; - foreach ($metaTemplate->meta_template_fields as $metaTemplateField) { - $metaTemplateFieldByName[$metaTemplateField->field] = $metaTemplateField; - } - foreach ($metaFields as $metaField) { - if ($metaField->meta_template_id != $metaTemplate->id) { + if ($existingMetaTemplate && $metaField->meta_template_id != $template->id) { continue; } $isValid = $this->MetaTemplateFields->MetaFields->isValidMetaFieldForMetaTemplateField( @@ -616,35 +986,18 @@ class MetaTemplatesTable extends AppTable return $conflicting; } - public function fetchEntitiesWithMetaFieldsForTemplate(\App\Model\Entity\MetaTemplate $metaTemplate): array - { - $entitiesIDWithMetaFields = $this->MetaTemplateFields->MetaFields->find() - ->select(['parent_id', 'scope']) - ->where(['MetaFields.meta_template_id' => $metaTemplate->id]) - ->group('parent_id') - ->all() - ->toList(); - $className = Inflector::camelize(Inflector::pluralize($entitiesIDWithMetaFields[0]->scope)); - - $table = TableRegistry::getTableLocator()->get($className); - $entities = $table->find() - ->where(['id IN' => Hash::extract($entitiesIDWithMetaFields, '{n}.parent_id')]) - ->contain([ - 'MetaFields' => [ - 'conditions' => [ - 'MetaFields.meta_template_id' => $metaTemplate->id - ] - ] - ]) - ->all()->toList(); - - return $entities; - } - - public function checkForMetaFieldConflicts(\App\Model\Entity\MetaTemplateField $metaTemplateField, array $templateField): array + /** + * Compute the potential conflict that would be introduced by updating an existing meta-template-field with the provided one. + * This will go through all instanciation of the existing meta-template-field and checking their validaty against the provided one. + * + * @param \App\Model\Entity\MetaTemplateField $metaTemplateField + * @param array $templateField + * @return array + */ + public function computeExistingMetaTemplateFieldConflictForMetaTemplateField(\App\Model\Entity\MetaTemplateField $metaTemplateField, array $templateField): array { $result = [ - 'updateable' => true, + 'automatically-updateable' => true, 'conflicts' => [], 'conflictingEntities' => [], ]; @@ -664,8 +1017,8 @@ class MetaTemplatesTable extends AppTable ->having(['count >' => 1]); $conflictingStatus = $query->all()->toList(); if (!empty($conflictingStatus)) { - $result['updateable'] = false; - $result['conflicts'][] = __('This field is no longer multiple'); + $result['automatically-updateable'] = false; + $result['conflicts'][] = __('This field is no longer multiple and is being that way'); $result['conflictingEntities'] = Hash::extract($conflictingStatus, '{n}.parent_id'); } } @@ -683,9 +1036,8 @@ class MetaTemplatesTable extends AppTable ]); $entitiesWithMetaField = $query->all()->toList(); if (!empty($entitiesWithMetaField)) { - $className = Inflector::camelize(Inflector::pluralize($entitiesWithMetaField[0]['scope'])); - $table = TableRegistry::getTableLocator()->get($className); - $entities = $table->find() + $entitiesTable = $this->getTableForMetaTemplateScope($entitiesWithMetaField[0]['scope']); + $entities = $entitiesTable->find() ->where(['id IN' => Hash::extract($entitiesWithMetaField, '{n}.parent_id')]) ->contain([ 'MetaFields' => [ @@ -710,7 +1062,7 @@ class MetaTemplatesTable extends AppTable } if (!empty($conflictingEntities)) { - $result['updateable'] = $result['updateable'] && false; + $result['automatically-updateable'] = $result['automatically-updateable'] && false; $result['conflicts'][] = __('This field is instantiated with values not passing the validation anymore'); $result['conflictingEntities'] = $conflictingEntities; } @@ -719,7 +1071,14 @@ class MetaTemplatesTable extends AppTable return $result; } - public function checkForMetaTemplateConflicts(\App\Model\Entity\MetaTemplate $metaTemplate, $template): array + /** + * Check the conflict that would be introduced if the metaTemplate would be updated to the provided template + * + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @param \App\Model\Entity\MetaTemplate|array $template + * @return array + */ + public function getMetaTemplateConflictsForMetaTemplate(\App\Model\Entity\MetaTemplate $metaTemplate, $template): array { $templateMetaFields = []; if (!is_array($template) && get_class($template) == 'App\Model\Entity\MetaTemplate') { @@ -733,17 +1092,20 @@ class MetaTemplatesTable extends AppTable foreach ($metaTemplate->meta_template_fields as $metaField) { if ($newMetaField['field'] == $metaField->field) { unset($existingMetaTemplateFields[$metaField->field]); - $templateConflicts = $this->checkForMetaFieldConflicts($metaField, !is_array($newMetaField) && get_class($newMetaField) == 'App\Model\Entity\MetaTemplateField' ? $newMetaField->toArray() : $newMetaField); - $conflicts[$metaField->field] = $templateConflicts; - $conflicts[$metaField->field]['existing_meta_template_field'] = $metaField; - $conflicts[$metaField->field]['existing_meta_template_field']['conflicts'] = $templateConflicts['conflicts']; + $metaFieldArray = !is_array($newMetaField) && get_class($newMetaField) == 'App\Model\Entity\MetaTemplateField' ? $newMetaField->toArray() : $newMetaField; + $templateConflictsForMetaField = $this->computeExistingMetaTemplateFieldConflictForMetaTemplateField($metaField, $metaFieldArray); + if (!$templateConflictsForMetaField['automatically-updateable']) { + $conflicts[$metaField->field] = $templateConflictsForMetaField; + $conflicts[$metaField->field]['existing_meta_template_field'] = $metaField; + $conflicts[$metaField->field]['existing_meta_template_field']['conflicts'] = $templateConflictsForMetaField['conflicts']; + } } } } if (!empty($existingMetaTemplateFields)) { foreach ($existingMetaTemplateFields as $field => $tmp) { $conflicts[$field] = [ - 'updateable' => false, + 'automatically-updateable' => false, 'conflicts' => [__('This field is intended to be removed')], ]; } @@ -751,56 +1113,82 @@ class MetaTemplatesTable extends AppTable return $conflicts; } - public function checkUpdatesForTemplate($template, $metaTemplate=null): array + /** + * Get update status for the latest meta-template in the database for the provided template + * + * @param array $template + * @param \App\Model\Entity\MetaTemplate $metaTemplate $metaTemplate + * @return array + */ + public function getUpdateStatusForTemplate(array $template): array { - $result = [ + $updateStatus = [ 'new' => true, 'up-to-date' => false, - 'updateable' => false, + 'automatically-updateable' => false, 'conflicts' => [], 'template' => $template, ]; - if (is_null($metaTemplate)) { - $query = $this->find() - ->contain('MetaTemplateFields') - ->where([ - 'uuid' => $template['uuid'], - ]) - ->order(['version' => 'DESC']); - $metaTemplate = $query->first(); - } - debug($metaTemplate); + $query = $this->find() + ->contain('MetaTemplateFields') + ->where([ + 'uuid' => $template['uuid'], + ]) + ->order(['version' => 'DESC']); + $metaTemplate = $query->first(); if (!empty($metaTemplate)) { - $result['existing_template'] = $metaTemplate; - $result['current_version'] = $metaTemplate->version; - $result['next_version'] = $template['version']; - $result['new'] = false; - if ($metaTemplate->version >= $template['version']) { - $result['up-to-date'] = true; - $result['updateable'] = false; - $result['conflicts'][] = __('Could not update the template. Local version is equal or newer.'); - return $result; - } - - $conflicts = $this->checkForMetaTemplateConflicts($metaTemplate, $template); - if (!empty($conflicts)) { - $result['conflicts'] = $conflicts; - } else { - $result['updateable'] = true; - } + $updateStatus = array_merge( + $updateStatus, + $this->getStatusForMetaTemplate($template, $metaTemplate) + ); } - return $result; + return $updateStatus; } - public function checkUpdateForMetaTemplate($template, $metaTemplate): array + /** + * Get update status for the meta-template stored in the database and the provided template + * + * @param array $template + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return array + */ + public function getStatusForMetaTemplate(array $template, \App\Model\Entity\MetaTemplate $metaTemplate): array { - $result = $this->checkUpdatesForTemplate($template, $metaTemplate); - $result['meta_field_amount'] = $this->MetaTemplateFields->MetaFields->find()->where(['meta_template_id' => $metaTemplate->id])->count(); - $result['can_be_removed'] = empty($result['meta_field_amount']) && empty($result['to_existing']); - return $result; + $updateStatus = []; + $updateStatus['existing_template'] = $metaTemplate; + $updateStatus['current_version'] = $metaTemplate->version; + $updateStatus['next_version'] = $template['version']; + $updateStatus['new'] = false; + if ($metaTemplate->version >= $template['version']) { + $updateStatus['up-to-date'] = true; + $updateStatus['automatically-updateable'] = false; + $updateStatus['conflicts'][] = __('Could not update the template. Local version is equal or newer.'); + return $updateStatus; + } + + $conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template); + if (!empty($conflicts)) { + $updateStatus['conflicts'] = $conflicts; + } else { + $updateStatus['automatically-updateable'] = true; + } + $updateStatus['meta_field_amount'] = $this->MetaTemplateFields->MetaFields->find()->where(['meta_template_id' => $metaTemplate->id])->count(); + $updateStatus['can-be-removed'] = empty($updateStatus['meta_field_amount']) && empty($updateStatus['to-existing']); + return $updateStatus; } - public function massageMetaFieldsBeforeSave($entity, $input, $metaTemplate) + /** + * Massages the meta-fields of an entity based on the input + * - If the keyed ID of the input meta-field is new, a new meta-field entity is created + * - If the input meta-field's value is empty for an existing meta-field, the existing meta-field is marked as to be deleted + * - If the input meta-field already exists, patch the entity and attach the validation errors + * + * @param \App\Model\Entity\AppModel $entity + * @param array $input + * @param \App\Model\Entity\MetaTemplate $metaTemplate + * @return array An array containing the entity with its massaged meta-fields and the meta-fields that should be deleted + */ + public function massageMetaFieldsBeforeSave(\App\Model\Entity\AppModel $entity, array $input, \App\Model\Entity\MetaTemplate $metaTemplate): array { $metaFieldsTable = $this->MetaTemplateFields->MetaFields; $className = Inflector::camelize(Inflector::pluralize($metaTemplate->scope)); @@ -850,7 +1238,7 @@ class MetaTemplatesTable extends AppTable ['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 + } else { // metafield comes from a second POST where the temporary entity has already been created $metaField = $metaFieldsTable->newEmptyEntity(); $metaFieldsTable->patchEntity($metaField, [ 'value' => $new_value, @@ -880,16 +1268,16 @@ class MetaTemplatesTable extends AppTable } } -class UpdateError +class UpdateError { public $success; public $message = ''; public $errors = []; - public function __construct($success=false, $message='', $errors=[]) + public function __construct($success = false, $message = '', $errors = []) { $this->success = $success; $this->message = $message; $this->errors = $errors; } -} \ No newline at end of file +} diff --git a/templates/MetaTemplates/index.php b/templates/MetaTemplates/index.php index a6d67c7..49bea89 100644 --- a/templates/MetaTemplates/index.php +++ b/templates/MetaTemplates/index.php @@ -1,10 +1,10 @@ %s %s', __('New meta-templates available!'), __n('There is one new template on disk that can be loaded in the database', 'There are {0} new templates on disk that can be loaded in the database:', count($updateableTemplate['new']))); +if (!empty($updateableTemplates['new'])) { + $alertHtml = sprintf('%s %s', __('New meta-templates available!'), __n('There is one new template on disk that can be loaded in the database', 'There are {0} new templates on disk that can be loaded in the database:', count($updateableTemplates['new']))); $alertList = Hash::combine( - $updateableTemplate, + $updateableTemplates, null, ['%s :: %s', 'new.{s}.template.namespace', 'new.{s}.template.name'], 'new.{n}.template.namespace' @@ -171,12 +171,12 @@ echo $this->element('genericElements/IndexTable/index_table', [ ], [ 'open_modal' => '/metaTemplates/update/[onclick_params_data_path]', - 'modal_params_data_path' => 'uuid', + 'modal_params_data_path' => 'id', 'title' => __('Update Meta-Template'), 'icon' => 'download', 'complex_requirement' => [ 'function' => function ($row, $options) { - return empty($row['status']['up_to_date']) && empty($row['status']['to_existing']); + return empty($row['updateStatus']['up-to-date']) && empty($row['updateStatus']['to-existing']); } ] ], @@ -188,7 +188,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'variant' => 'warning', 'complex_requirement' => [ 'function' => function ($row, $options) { - return !empty($row['status']['to_existing']) && empty($row['status']['can_be_removed']); + return !empty($row['updateStatus']['to-existing']) && empty($row['updateStatus']['can-be-removed']); } ] ], @@ -200,7 +200,7 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'variant' => 'success', 'complex_requirement' => [ 'function' => function ($row, $options) { - return !empty($row['status']['to_existing']) && !empty($row['status']['can_be_removed']); + return !empty($row['updateStatus']['to-existing']) && !empty($row['updateStatus']['can-be-removed']); } ] ], diff --git a/templates/MetaTemplates/update.php b/templates/MetaTemplates/update.php index bd57231..3cbdc15 100644 --- a/templates/MetaTemplates/update.php +++ b/templates/MetaTemplates/update.php @@ -3,7 +3,7 @@ $bodyHtml = ''; $modalType = 'confirm'; $modalSize = 'lg'; -if ($updateableTemplate['up-to-date']) { +if ($updateStatus['up-to-date']) { $bodyHtml .= $this->Bootstrap->alert([ 'variant' => 'success', 'text' => __('This meta-template is already up-to-date!'), @@ -11,7 +11,7 @@ if ($updateableTemplate['up-to-date']) { ]); $modalType = 'ok-only'; } else { - if ($updateableTemplate['updateable']) { + if ($updateStatus['automatically-updateable']) { $bodyHtml .= $this->Bootstrap->alert([ 'variant' => 'success', 'html' => __('This meta-template can be updated to version {0} (current: {1}).', sprintf('%s', h($templateOnDisk['version'])), h($metaTemplate->version)), @@ -45,7 +45,7 @@ if ($updateableTemplate['up-to-date']) { 'dismissible' => false, ]); $conflictTable = $this->element('MetaTemplates/conflictTable', [ - 'updateableTemplate' => $updateableTemplate, + 'templateStatus' => $templateStatus, 'metaTemplate' => $metaTemplate, 'templateOnDisk' => $templateOnDisk, ]); @@ -54,7 +54,7 @@ if ($updateableTemplate['up-to-date']) { 'open' => false ], $conflictTable); $bodyHtml .= $this->element('MetaTemplates/conflictResolution', [ - 'updateableTemplate' => $updateableTemplate, + 'templateStatus' => $templateStatus, 'metaTemplate' => $metaTemplate, 'templateOnDisk' => $templateOnDisk, ]); diff --git a/templates/MetaTemplates/update_all.php b/templates/MetaTemplates/update_all.php index 0edbd5e..3e7a425 100644 --- a/templates/MetaTemplates/update_all.php +++ b/templates/MetaTemplates/update_all.php @@ -15,7 +15,7 @@ $tableHtml .= sprintf('%s', __('Will be updated')); $tableHtml .= ''; $numberOfUpdates = 0; $numberOfSkippedUpdates = 0; -foreach ($updateableTemplates as $uuid => $status) { +foreach ($templatesUpdateStatus as $uuid => $status) { $tableHtml .= ''; if (!empty($status['new'])) { $tableHtml .= sprintf('%s', h($uuid)); @@ -53,7 +53,7 @@ foreach ($updateableTemplates as $uuid => $status) { if (!empty($status['new'])) { $tableHtml .= sprintf('%s', $this->Bootstrap->icon('check', ['class' => 'text-success'])); } else { - if (!empty($status['new']) || !empty($status['updateable'])) { + if (!empty($status['new']) || !empty($status['automatically-updateable'])) { $numberOfUpdates += 1; $tableHtml .= sprintf('%s', $this->Bootstrap->icon('check', ['class' => 'text-success'])); } else { diff --git a/templates/element/MetaTemplates/conflictResolution.php b/templates/element/MetaTemplates/conflictResolution.php index ebe326f..a0d1adc 100644 --- a/templates/element/MetaTemplates/conflictResolution.php +++ b/templates/element/MetaTemplates/conflictResolution.php @@ -1,4 +1,10 @@ element('genericElements/Form/genericForm', [ 'entity' => null, 'ajax' => false, @@ -10,6 +16,7 @@ $form = $this->element('genericElements/Form/genericForm', [ 'field' => 'update_strategy', 'type' => 'radio', 'options' => [ + ['value' => 'create_new', 'text' => 'create_new', 'id' => 'radio_create_new'], ['value' => 'keep_both', 'text' => 'keep_both', 'id' => 'radio_keep_both'], ['value' => 'delete', 'text' => 'delete', 'id' => 'radio_delete'], ], @@ -25,28 +32,46 @@ $form = $this->element('genericElements/Form/genericForm', [
- - + + + + - - + + + + + + + + +
@@ -58,6 +83,7 @@ $form = $this->element('genericElements/Form/genericForm', [