new: [meta-template] Improvement of the update system
- Changed default update strategy from `create_new` to `update_existing` - Added mechanism to automatically migrate meta-fields to newest template - Improved validation and conflict detection strategies - Fixed various UI bugs and improved QoLdevelop-unstable
parent
c0636b89ab
commit
20eebd097d
|
@ -91,7 +91,7 @@ class MetaTemplatesNavigation extends BaseNavigation
|
||||||
'url' => '/metaTemplates/prune_outdated_template',
|
'url' => '/metaTemplates/prune_outdated_template',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (empty($this->viewVars['updateableTemplates']['up-to-date'])) {
|
if (empty($this->viewVars['templateStatus']['up-to-date'])) {
|
||||||
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'update', [
|
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'update', [
|
||||||
'label' => __('Update template'),
|
'label' => __('Update template'),
|
||||||
'url' => '/metaTemplates/update/{{id}}',
|
'url' => '/metaTemplates/update/{{id}}',
|
||||||
|
|
|
@ -244,6 +244,55 @@ class MetaTemplatesController extends AppController
|
||||||
$this->set('movedMetaTemplateFields', $movedMetaTemplateFields);
|
$this->set('movedMetaTemplateFields', $movedMetaTemplateFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function migrateMetafieldsToNewestTemplate(int $template_id)
|
||||||
|
{
|
||||||
|
$oldMetaTemplate = $this->MetaTemplates->find()->where([
|
||||||
|
'id' => $template_id
|
||||||
|
])->contain(['MetaTemplateFields'])->first();
|
||||||
|
|
||||||
|
if (empty($oldMetaTemplate)) {
|
||||||
|
throw new NotFoundException(__('Invalid {0} {1}.', $this->MetaTemplates->getAlias(), $template_id));
|
||||||
|
}
|
||||||
|
$newestMetaTemplate = $this->MetaTemplates->getNewestVersion($oldMetaTemplate, true);
|
||||||
|
if ($oldMetaTemplate->id == $newestMetaTemplate->id) {
|
||||||
|
throw new NotFoundException(__('Invalid {0} {1}. Template already the newest version', $this->MetaTemplates->getAlias(), $template_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->is('post')) {
|
||||||
|
$result = $this->MetaTemplates->migrateMetafieldsToNewestTemplate($oldMetaTemplate, $newestMetaTemplate);
|
||||||
|
if ($this->ParamHandler->isRest()) {
|
||||||
|
return $this->RestResponse->viewData($result, 'json');
|
||||||
|
} else {
|
||||||
|
if ($result['success']) {
|
||||||
|
$message = __('{0} entities updated. {1} entities could not be automatically migrated.', $result['migrated_count'], $result['conflicting_entities']);
|
||||||
|
} else {
|
||||||
|
$message = __('{0} entities updated. {1} entities could not be automatically migrated. {2} entities could not be updated due to errors', $result['migrated_count'], $result['conflicting_entities'], $result['migration_errors']);
|
||||||
|
}
|
||||||
|
$this->CRUD->setResponseForController('update', $result['success'], $message, $result, $result['migration_errors'], ['redirect' => $this->referer()]);
|
||||||
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
|
if (!empty($responsePayload)) {
|
||||||
|
return $responsePayload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$entities = $this->MetaTemplates->getEntitiesHavingMetaFieldsFromTemplate($oldMetaTemplate->id, null);
|
||||||
|
$conflictingEntities = [];
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$conflicts = $this->MetaTemplates->getMetaFieldsConflictsUnderTemplate($entity->meta_fields, $newestMetaTemplate);
|
||||||
|
if (!empty($conflicts)) {
|
||||||
|
$conflictingEntities[] = $entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->ParamHandler->isRest()) {
|
||||||
|
$this->set('oldMetaTemplate', $oldMetaTemplate);
|
||||||
|
$this->set('newestMetaTemplate', $newestMetaTemplate);
|
||||||
|
$this->set('conflictingEntities', $conflictingEntities);
|
||||||
|
$this->set('entityCount', count($entities));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$templatesUpdateStatus = $this->MetaTemplates->getUpdateStatusForTemplates();
|
$templatesUpdateStatus = $this->MetaTemplates->getUpdateStatusForTemplates();
|
||||||
|
|
|
@ -39,8 +39,6 @@ class MetaFieldsTable extends AppTable
|
||||||
->notEmptyString('value')
|
->notEmptyString('value')
|
||||||
->notEmptyString('meta_template_id')
|
->notEmptyString('meta_template_id')
|
||||||
->notEmptyString('meta_template_field_id')
|
->notEmptyString('meta_template_field_id')
|
||||||
// ->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_id', 'meta_template_field_id'], 'create');
|
|
||||||
// ->requirePresence(['scope', 'field', 'value', 'uuid',], 'create');
|
|
||||||
->notEmptyString('meta_template_directory_id')
|
->notEmptyString('meta_template_directory_id')
|
||||||
->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_directory_id', ], 'create');
|
->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_directory_id', ], 'create');
|
||||||
|
|
||||||
|
@ -53,10 +51,9 @@ class MetaFieldsTable extends AppTable
|
||||||
return $validator;
|
return $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
public function afterMarshal(EventInterface $event, \App\Model\Entity\MetaField $entity, ArrayObject $data, ArrayObject $options) {
|
||||||
{
|
if (!isset($entity->meta_template_directory_id)) {
|
||||||
if (!isset($data['meta_template_directory_id'])) {
|
$entity->set('meta_template_directory_id', $this->getTemplateDirectoryIdFromMetaTemplate($entity->meta_template_id));
|
||||||
$data['meta_template_directory_id'] = $this->getTemplateDirectoryIdFromMetaTemplate($data['meta_template_id']);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +86,9 @@ class MetaFieldsTable extends AppTable
|
||||||
if (!empty($metaTemplateField['regex'])) {
|
if (!empty($metaTemplateField['regex'])) {
|
||||||
return $this->isValidRegex($value, $metaTemplateField);
|
return $this->isValidRegex($value, $metaTemplateField);
|
||||||
}
|
}
|
||||||
|
if (!empty($metaTemplateField['values_list'])) {
|
||||||
|
return $this->isValidValuesList($value, $metaTemplateField);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,4 +118,11 @@ class MetaFieldsTable extends AppTable
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isValidValuesList($value, $metaTemplateField)
|
||||||
|
{
|
||||||
|
|
||||||
|
$valuesList = $metaTemplateField['values_list'];
|
||||||
|
return in_array($value, $valuesList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,12 @@ class MetaTemplatesTable extends AppTable
|
||||||
public const UPDATE_STRATEGY_KEEP_BOTH = 'keep_both';
|
public const UPDATE_STRATEGY_KEEP_BOTH = 'keep_both';
|
||||||
public const UPDATE_STRATEGY_DELETE = 'delete_all';
|
public const UPDATE_STRATEGY_DELETE = 'delete_all';
|
||||||
|
|
||||||
public const DEFAULT_STRATEGY = MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW;
|
public const DEFAULT_STRATEGY = MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING;
|
||||||
public const ALLOWED_STRATEGIES = [MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW];
|
public const ALLOWED_STRATEGIES = [
|
||||||
|
MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW,
|
||||||
|
MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING,
|
||||||
|
MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH,
|
||||||
|
];
|
||||||
|
|
||||||
private $templatesOnDisk = null;
|
private $templatesOnDisk = null;
|
||||||
|
|
||||||
|
@ -73,6 +77,41 @@ class MetaTemplatesTable extends AppTable
|
||||||
* @return array The update result containing potential errors and the successes
|
* @return array The update result containing potential errors and the successes
|
||||||
*/
|
*/
|
||||||
public function updateAllTemplates(): array
|
public function updateAllTemplates(): array
|
||||||
|
{
|
||||||
|
// Create new template found on the disk
|
||||||
|
$newTemplateResult = $this->createAllNewTemplates();
|
||||||
|
$files_processed = $newTemplateResult['files_processed'];
|
||||||
|
$updatesErrors = $newTemplateResult['update_errors'];
|
||||||
|
|
||||||
|
$templatesUpdateStatus = $this->getUpdateStatusForTemplates();
|
||||||
|
|
||||||
|
// Update all existing templates
|
||||||
|
foreach ($templatesUpdateStatus as $uuid => $templateUpdateStatus) {
|
||||||
|
if (!empty($templateUpdateStatus['existing_template'])) {
|
||||||
|
$metaTemplate = $templateUpdateStatus['existing_template'];
|
||||||
|
$result = $this->update($metaTemplate, null);
|
||||||
|
if ($result['success']) {
|
||||||
|
$files_processed[] = $metaTemplate->uuid;
|
||||||
|
}
|
||||||
|
if (!empty($result['errors'])) {
|
||||||
|
$updatesErrors[] = $result['errors'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$results = [
|
||||||
|
'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
|
||||||
|
*
|
||||||
|
* @return array The update result containing potential errors and the successes
|
||||||
|
*/
|
||||||
|
public function createAllNewTemplates(): array
|
||||||
{
|
{
|
||||||
$updatesErrors = [];
|
$updatesErrors = [];
|
||||||
$files_processed = [];
|
$files_processed = [];
|
||||||
|
@ -106,7 +145,7 @@ class MetaTemplatesTable extends AppTable
|
||||||
* @param string|null $strategy The strategy to be used when updating templates with conflicts
|
* @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
|
* @return array The update result containing potential errors and the successes
|
||||||
*/
|
*/
|
||||||
public function update($metaTemplate, $strategy = null): array
|
public function update(\App\Model\Entity\MetaTemplate $metaTemplate, $strategy = null): array
|
||||||
{
|
{
|
||||||
$files_processed = [];
|
$files_processed = [];
|
||||||
$updatesErrors = [];
|
$updatesErrors = [];
|
||||||
|
@ -114,6 +153,25 @@ class MetaTemplatesTable extends AppTable
|
||||||
$templateStatus = $this->getStatusForMetaTemplate($templateOnDisk, $metaTemplate);
|
$templateStatus = $this->getStatusForMetaTemplate($templateOnDisk, $metaTemplate);
|
||||||
$updateStatus = $this->computeFullUpdateStatusForMetaTemplate($templateStatus, $metaTemplate);
|
$updateStatus = $this->computeFullUpdateStatusForMetaTemplate($templateStatus, $metaTemplate);
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
|
$result = $this->doUpdate($updateStatus, $templateOnDisk, $metaTemplate, $strategy);
|
||||||
|
if ($result['success']) {
|
||||||
|
$files_processed[] = $templateOnDisk['uuid'];
|
||||||
|
}
|
||||||
|
if (!empty($result['errors'])) {
|
||||||
|
$updatesErrors[] = $errors;
|
||||||
|
}
|
||||||
|
$results = [
|
||||||
|
'update_errors' => $updatesErrors,
|
||||||
|
'files_processed' => $files_processed,
|
||||||
|
'success' => !empty($files_processed),
|
||||||
|
];
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function doUpdate(array $updateStatus, array $templateOnDisk, \App\Model\Entity\MetaTemplate $metaTemplate, string $strategy = null): array
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
$success = false;
|
$success = false;
|
||||||
if ($updateStatus['up-to-date']) {
|
if ($updateStatus['up-to-date']) {
|
||||||
$errors['message'] = __('Meta-template already up-to-date');
|
$errors['message'] = __('Meta-template already up-to-date');
|
||||||
|
@ -126,22 +184,17 @@ class MetaTemplatesTable extends AppTable
|
||||||
$errors['message'] = __('Cannot update meta-template, update strategy not allowed');
|
$errors['message'] = __('Cannot update meta-template, update strategy not allowed');
|
||||||
} else if (!$updateStatus['up-to-date']) {
|
} else if (!$updateStatus['up-to-date']) {
|
||||||
$strategy = is_null($strategy) ? MetaTemplatesTable::DEFAULT_STRATEGY : $strategy;
|
$strategy = is_null($strategy) ? MetaTemplatesTable::DEFAULT_STRATEGY : $strategy;
|
||||||
|
if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING && !$updateStatus['automatically-updateable']) {
|
||||||
|
$strategy = MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH;
|
||||||
|
}
|
||||||
$success = $this->updateMetaTemplateWithStrategyRouter($metaTemplate, $templateOnDisk, $strategy, $errors);
|
$success = $this->updateMetaTemplateWithStrategyRouter($metaTemplate, $templateOnDisk, $strategy, $errors);
|
||||||
} else {
|
} else {
|
||||||
$errors['message'] = __('Could not update. Something went wrong.');
|
$errors['message'] = __('Could not update. Something went wrong.');
|
||||||
}
|
}
|
||||||
if ($success) {
|
return [
|
||||||
$files_processed[] = $templateOnDisk['uuid'];
|
'success' => $success,
|
||||||
}
|
'errors' => $errors,
|
||||||
if (!empty($errors)) {
|
|
||||||
$updatesErrors[] = $errors;
|
|
||||||
}
|
|
||||||
$results = [
|
|
||||||
'update_errors' => $updatesErrors,
|
|
||||||
'files_processed' => $files_processed,
|
|
||||||
'success' => !empty($files_processed),
|
|
||||||
];
|
];
|
||||||
return $results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -764,13 +817,27 @@ class MetaTemplatesTable extends AppTable
|
||||||
$errors[] = new UpdateError(false, $metaTemplate);
|
$errors[] = new UpdateError(false, $metaTemplate);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$metaTemplate = $this->patchEntity($metaTemplate, $template, [
|
$metaTemplate = $this->patchEntity($metaTemplate, $template);
|
||||||
'associated' => ['MetaTemplateFields']
|
foreach ($template['metaFields'] as $newMetaField) {
|
||||||
]);
|
$newMetaField['__patched'] = true;
|
||||||
|
foreach($metaTemplate->meta_template_fields as $i => $oldMetaField) {
|
||||||
|
if ($oldMetaField->field == $newMetaField['field']) {
|
||||||
|
$metaTemplate->meta_template_fields[$i] = $this->MetaTemplateFields->patchEntity($oldMetaField, $newMetaField);
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$metaTemplate->meta_template_fields[] = $this->MetaTemplateFields->newEntity($newMetaField);
|
||||||
|
}
|
||||||
|
$metaTemplate->setDirty('meta_template_fields', true);
|
||||||
$metaTemplate = $this->save($metaTemplate, [
|
$metaTemplate = $this->save($metaTemplate, [
|
||||||
'associated' => ['MetaTemplateFields']
|
'associated' => ['MetaTemplateFields']
|
||||||
]);
|
]);
|
||||||
if (!empty($metaTemplate)) {
|
foreach ($metaTemplate->meta_template_fields as $savedMetafield) {
|
||||||
|
if (empty($savedMetafield['__patched'])) {
|
||||||
|
$this->MetaTemplateFields->delete($savedMetafield);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($metaTemplate)) {
|
||||||
$errors[] = new UpdateError(false, __('Could not save the template.'), $metaTemplate->getErrors());
|
$errors[] = new UpdateError(false, __('Could not save the template.'), $metaTemplate->getErrors());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -797,8 +864,8 @@ class MetaTemplatesTable extends AppTable
|
||||||
}
|
}
|
||||||
if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH) {
|
if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH) {
|
||||||
$result = $this->executeStrategyKeep($template, $metaTemplate);
|
$result = $this->executeStrategyKeep($template, $metaTemplate);
|
||||||
} else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_DELETE) {
|
} else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING) {
|
||||||
$result = $this->executeStrategyDeleteAll($template, $metaTemplate);
|
$result = $this->updateMetaTemplate($metaTemplate, $template);
|
||||||
} else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) {
|
} else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) {
|
||||||
$result = $this->executeStrategyCreateNew($template, $metaTemplate);
|
$result = $this->executeStrategyCreateNew($template, $metaTemplate);
|
||||||
} else {
|
} else {
|
||||||
|
@ -830,6 +897,7 @@ class MetaTemplatesTable extends AppTable
|
||||||
$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[] = 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);
|
$conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template);
|
||||||
|
$conflictingEntities = Hash::combine($conflicts, '{s}.conflictingEntities.{n}.parent_id', '{s}.conflictingEntities.{n}.parent_id');
|
||||||
$blockingConflict = Hash::extract($conflicts, '{s}.conflicts');
|
$blockingConflict = Hash::extract($conflicts, '{s}.conflicts');
|
||||||
$errors = [];
|
$errors = [];
|
||||||
if (empty($blockingConflict)) { // No conflict, everything can be updated without special care
|
if (empty($blockingConflict)) { // No conflict, everything can be updated without special care
|
||||||
|
@ -838,7 +906,6 @@ class MetaTemplatesTable extends AppTable
|
||||||
}
|
}
|
||||||
$entities = $this->getEntitiesHavingMetaFieldsFromTemplate($metaTemplate->id, null);
|
$entities = $this->getEntitiesHavingMetaFieldsFromTemplate($metaTemplate->id, null);
|
||||||
|
|
||||||
$conflictingEntities = [];
|
|
||||||
foreach ($entities as $entity) {
|
foreach ($entities as $entity) {
|
||||||
$conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity['meta_fields'], $template);
|
$conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity['meta_fields'], $template);
|
||||||
if (!empty($conflicts)) {
|
if (!empty($conflicts)) {
|
||||||
|
@ -849,8 +916,9 @@ class MetaTemplatesTable extends AppTable
|
||||||
$this->updateMetaTemplate($metaTemplate, $template, $errors);
|
$this->updateMetaTemplate($metaTemplate, $template, $errors);
|
||||||
return !empty($errors) ? $errors[0] : true;
|
return !empty($errors) ? $errors[0] : true;
|
||||||
}
|
}
|
||||||
$template['is_default'] = $metaTemplate['is_default'];
|
$template['is_default'] = $metaTemplate->is_default;
|
||||||
$template['enabled'] = $metaTemplate['enabled'];
|
$template['enabled'] = $metaTemplate->enabled;
|
||||||
|
$metaTemplate->set('enabled', false);
|
||||||
if ($metaTemplate->is_default) {
|
if ($metaTemplate->is_default) {
|
||||||
$metaTemplate->set('is_default', false);
|
$metaTemplate->set('is_default', false);
|
||||||
$this->save($metaTemplate);
|
$this->save($metaTemplate);
|
||||||
|
@ -858,13 +926,9 @@ class MetaTemplatesTable extends AppTable
|
||||||
$savedMetaTemplate = null;
|
$savedMetaTemplate = null;
|
||||||
$this->saveNewMetaTemplate($template, $errors, $savedMetaTemplate);
|
$this->saveNewMetaTemplate($template, $errors, $savedMetaTemplate);
|
||||||
if (!empty($savedMetaTemplate)) {
|
if (!empty($savedMetaTemplate)) {
|
||||||
$savedMetaTemplateFieldByName = Hash::combine($savedMetaTemplate['meta_template_fields'], '{n}.field', '{n}');
|
|
||||||
foreach ($entities as $entity) {
|
foreach ($entities as $entity) {
|
||||||
if (empty($conflictingEntities[$entity->id])) { // conflicting entities remain untouched
|
if (empty($conflictingEntities[$entity->id])) { // conflicting entities remain untouched
|
||||||
foreach ($entity['meta_fields'] as $metaField) {
|
$this->supersedeMetaFieldsWithMetaTemplateField($entity['meta_fields'], $savedMetaTemplate);
|
||||||
$savedMetaTemplateField = $savedMetaTemplateFieldByName[$metaField->field];
|
|
||||||
$this->supersedeMetaFieldWithMetaTemplateField($metaField, $savedMetaTemplateField);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -873,6 +937,44 @@ class MetaTemplatesTable extends AppTable
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function migrateMetafieldsToNewestTemplate(\App\Model\Entity\MetaTemplate $oldMetaTemplate, \App\Model\Entity\MetaTemplate $newestMetaTemplate): array
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'success' => true,
|
||||||
|
'migrated_count' => 0,
|
||||||
|
'conflicting_entities' => 0,
|
||||||
|
'migration_errors' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
$entities = $this->getEntitiesHavingMetaFieldsFromTemplate($oldMetaTemplate->id, null);
|
||||||
|
if (empty($entities)) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$successfullyMigratedEntities = 0;
|
||||||
|
$migrationErrors = 0;
|
||||||
|
$conflictingEntities = [];
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity->meta_fields, $newestMetaTemplate);
|
||||||
|
if (!empty($conflicts)) {
|
||||||
|
$conflictingEntities[] = $entity->id;
|
||||||
|
} else {
|
||||||
|
$success = $this->supersedeMetaFieldsWithMetaTemplateField($entity->meta_fields, $newestMetaTemplate);
|
||||||
|
if ($success) {
|
||||||
|
$successfullyMigratedEntities += 1;
|
||||||
|
} else {
|
||||||
|
$migrationErrors += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['success'] = $migrationErrors == 0;
|
||||||
|
$result['migrated_count'] = $successfullyMigratedEntities;
|
||||||
|
$result['conflicting_entities'] = count($conflictingEntities);
|
||||||
|
$result['migration_errors'] = $migrationErrors;
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the `delete_all` update strategy by updating the meta-template and deleting all conflicting meta-fields.
|
* Execute the `delete_all` update strategy by updating the meta-template and deleting all conflicting meta-fields.
|
||||||
* Strategy:
|
* Strategy:
|
||||||
|
@ -941,16 +1043,20 @@ class MetaTemplatesTable extends AppTable
|
||||||
/**
|
/**
|
||||||
* Supersede a meta-fields's meta-template-field with the provided one.
|
* Supersede a meta-fields's meta-template-field with the provided one.
|
||||||
*
|
*
|
||||||
* @param \App\Model\Entity\MetaField $metaField
|
* @param array $metaFields
|
||||||
* @param \App\Model\Entity\MetaTemplateField $savedMetaTemplateField
|
* @param \App\Model\Entity\MetaTemplateField $savedMetaTemplateField
|
||||||
* @return bool True if the replacement was a success, False otherwise
|
* @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
|
public function supersedeMetaFieldsWithMetaTemplateField(array $metaFields, \App\Model\Entity\MetaTemplate $savedMetaTemplate): bool
|
||||||
{
|
{
|
||||||
$metaField->set('meta_template_id', $savedMetaTemplateField->meta_template_id);
|
$savedMetaTemplateFieldByName = Hash::combine($savedMetaTemplate['meta_template_fields'], '{n}.field', '{n}');
|
||||||
$metaField->set('meta_template_field_id', $savedMetaTemplateField->id);
|
foreach ($metaFields as $i => $metaField) {
|
||||||
$metaField = $this->MetaTemplateFields->MetaFields->save($metaField);
|
$savedMetaTemplateField = $savedMetaTemplateFieldByName[$metaField->field];
|
||||||
return !empty($metaField);
|
$metaField->set('meta_template_id', $savedMetaTemplateField->meta_template_id);
|
||||||
|
$metaField->set('meta_template_field_id', $savedMetaTemplateField->id);
|
||||||
|
}
|
||||||
|
$entities = $this->MetaTemplateFields->MetaFields->saveMany($metaFields);
|
||||||
|
return !empty($entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -977,13 +1083,14 @@ class MetaTemplatesTable extends AppTable
|
||||||
$metaTemplateFieldByName[$metaTemplateField['field']] = $this->MetaTemplateFields->newEntity($metaTemplateField);
|
$metaTemplateFieldByName[$metaTemplateField['field']] = $this->MetaTemplateFields->newEntity($metaTemplateField);
|
||||||
}
|
}
|
||||||
foreach ($metaFields as $metaField) {
|
foreach ($metaFields as $metaField) {
|
||||||
if ($existingMetaTemplate && $metaField->meta_template_id != $template->id) {
|
if (empty($metaTemplateFieldByName[$metaField->field])) { // Meta-field was removed from the template
|
||||||
continue;
|
$isValid = false;
|
||||||
|
} else {
|
||||||
|
$isValid = $this->MetaTemplateFields->MetaFields->isValidMetaFieldForMetaTemplateField(
|
||||||
|
$metaField->value,
|
||||||
|
$metaTemplateFieldByName[$metaField->field]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$isValid = $this->MetaTemplateFields->MetaFields->isValidMetaFieldForMetaTemplateField(
|
|
||||||
$metaField->value,
|
|
||||||
$metaTemplateFieldByName[$metaField->field]
|
|
||||||
);
|
|
||||||
if ($isValid !== true) {
|
if ($isValid !== true) {
|
||||||
$conflicting[] = $metaField;
|
$conflicting[] = $metaField;
|
||||||
}
|
}
|
||||||
|
@ -1027,27 +1134,12 @@ class MetaTemplatesTable extends AppTable
|
||||||
$result['conflictingEntities'] = Hash::extract($conflictingStatus, '{n}.parent_id');
|
$result['conflictingEntities'] = Hash::extract($conflictingStatus, '{n}.parent_id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!empty($templateField['regex']) && $templateField['regex'] != $metaTemplateField->regex) {
|
|
||||||
$entitiesWithMetaFieldQuery = $this->MetaTemplateFields->MetaFields->find();
|
if (
|
||||||
$entitiesWithMetaFieldQuery
|
(!empty($templateField['regex']) && $templateField['regex'] != $metaTemplateField->regex) ||
|
||||||
->enableHydration(false)
|
!empty($templateField['values_list'])
|
||||||
->select([
|
) {
|
||||||
'parent_id',
|
$entities = $this->getEntitiesForMetaTemplateField($scope, $metaTemplateField->id, true);
|
||||||
])
|
|
||||||
->where([
|
|
||||||
'meta_template_field_id' => $metaTemplateField->id,
|
|
||||||
]);
|
|
||||||
$entitiesTable = $this->getTableForMetaTemplateScope($scope);
|
|
||||||
$entities = $entitiesTable->find()
|
|
||||||
->where(['id IN' => $entitiesWithMetaFieldQuery])
|
|
||||||
->contain([
|
|
||||||
'MetaFields' => [
|
|
||||||
'conditions' => [
|
|
||||||
'MetaFields.meta_template_field_id' => $metaTemplateField->id
|
|
||||||
]
|
|
||||||
]
|
|
||||||
])
|
|
||||||
->all()->toList();
|
|
||||||
$conflictingEntities = [];
|
$conflictingEntities = [];
|
||||||
foreach ($entities as $entity) {
|
foreach ($entities as $entity) {
|
||||||
foreach ($entity['meta_fields'] as $metaField) {
|
foreach ($entity['meta_fields'] as $metaField) {
|
||||||
|
@ -1056,7 +1148,10 @@ class MetaTemplatesTable extends AppTable
|
||||||
$templateField
|
$templateField
|
||||||
);
|
);
|
||||||
if ($isValid !== true) {
|
if ($isValid !== true) {
|
||||||
$conflictingEntities[] = $entity->id;
|
$conflictingEntities[] = [
|
||||||
|
'parent_id' => $entity->id,
|
||||||
|
'meta_template_field_id' => $metaTemplateField->id,
|
||||||
|
];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1064,13 +1159,50 @@ class MetaTemplatesTable extends AppTable
|
||||||
|
|
||||||
if (!empty($conflictingEntities)) {
|
if (!empty($conflictingEntities)) {
|
||||||
$result['automatically-updateable'] = $result['automatically-updateable'] && false;
|
$result['automatically-updateable'] = $result['automatically-updateable'] && false;
|
||||||
$result['conflicts'][] = __('This field is instantiated with values not passing the validation anymore');
|
$result['conflicts'][] = __('This field is instantiated with values not passing the validation anymore.');
|
||||||
$result['conflictingEntities'] = array_merge($result['conflictingEntities'], $conflictingEntities);
|
$result['conflictingEntities'] = array_merge($result['conflictingEntities'], $conflictingEntities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all entities having the meta-fields using the provided meta-template-field.
|
||||||
|
*
|
||||||
|
* @param string $scope
|
||||||
|
* @param integer $metaTemplateFieldID The ID of the matching meta-template-field
|
||||||
|
* @param boolean $includeMatchingMetafields Should the entities also include the matching meta-fields
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function getEntitiesForMetaTemplateField(string $scope, int $metaTemplateFieldID, bool $includeMatchingMetafields=true): array
|
||||||
|
{
|
||||||
|
$entitiesTable = $this->getTableForMetaTemplateScope($scope);
|
||||||
|
$entitiesWithMetaFieldQuery = $this->MetaTemplateFields->MetaFields->find();
|
||||||
|
$entitiesWithMetaFieldQuery
|
||||||
|
->enableHydration(false)
|
||||||
|
->select([
|
||||||
|
'parent_id',
|
||||||
|
])
|
||||||
|
->where([
|
||||||
|
'meta_template_field_id' => $metaTemplateFieldID,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$entitiesQuery = $entitiesTable->find()
|
||||||
|
->where(['id IN' => $entitiesWithMetaFieldQuery]);
|
||||||
|
|
||||||
|
if ($includeMatchingMetafields) {
|
||||||
|
$entitiesQuery->contain([
|
||||||
|
'MetaFields' => [
|
||||||
|
'conditions' => [
|
||||||
|
'MetaFields.meta_template_field_id' => $metaTemplateFieldID,
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entitiesQuery->all()->toList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check the conflict that would be introduced if the metaTemplate would be updated to the provided template
|
* Check the conflict that would be introduced if the metaTemplate would be updated to the provided template
|
||||||
*
|
*
|
||||||
|
@ -1087,7 +1219,7 @@ class MetaTemplatesTable extends AppTable
|
||||||
$templateMetaFields = $template['metaFields'];
|
$templateMetaFields = $template['metaFields'];
|
||||||
}
|
}
|
||||||
$conflicts = [];
|
$conflicts = [];
|
||||||
$existingMetaTemplateFields = Hash::combine($metaTemplate->toArray(), 'meta_template_fields.{n}.field');
|
$existingMetaTemplateFields = Hash::combine($metaTemplate->toArray(), 'meta_template_fields.{n}.field', 'meta_template_fields.{n}');
|
||||||
foreach ($templateMetaFields as $newMetaField) {
|
foreach ($templateMetaFields as $newMetaField) {
|
||||||
foreach ($metaTemplate->meta_template_fields as $metaField) {
|
foreach ($metaTemplate->meta_template_fields as $metaField) {
|
||||||
if ($newMetaField['field'] == $metaField->field) {
|
if ($newMetaField['field'] == $metaField->field) {
|
||||||
|
@ -1103,10 +1235,23 @@ class MetaTemplatesTable extends AppTable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!empty($existingMetaTemplateFields)) {
|
if (!empty($existingMetaTemplateFields)) {
|
||||||
foreach ($existingMetaTemplateFields as $field => $tmp) {
|
foreach ($existingMetaTemplateFields as $metaTemplateField) {
|
||||||
$conflicts[$field] = [
|
$query = $this->MetaTemplateFields->MetaFields->find();
|
||||||
|
$query
|
||||||
|
->enableHydration(false)
|
||||||
|
->select([
|
||||||
|
'parent_id',
|
||||||
|
'meta_template_field_id',
|
||||||
|
])
|
||||||
|
->where([
|
||||||
|
'meta_template_field_id' => $metaTemplateField['id'],
|
||||||
|
])
|
||||||
|
->group(['parent_id']);
|
||||||
|
$entityWithMetafieldToBeRemoved = $query->all()->toList();
|
||||||
|
$conflicts[$metaTemplateField['field']] = [
|
||||||
'automatically-updateable' => false,
|
'automatically-updateable' => false,
|
||||||
'conflicts' => [__('This field is intended to be removed')],
|
'conflicts' => [__('This field is intended to be removed')],
|
||||||
|
'conflictingEntities' => $entityWithMetafieldToBeRemoved,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1159,9 +1304,9 @@ class MetaTemplatesTable extends AppTable
|
||||||
$updateStatus['current_version'] = $metaTemplate->version;
|
$updateStatus['current_version'] = $metaTemplate->version;
|
||||||
$updateStatus['next_version'] = $template['version'];
|
$updateStatus['next_version'] = $template['version'];
|
||||||
$updateStatus['new'] = false;
|
$updateStatus['new'] = false;
|
||||||
|
$updateStatus['automatically-updateable'] = false;
|
||||||
if ($metaTemplate->version >= $template['version']) {
|
if ($metaTemplate->version >= $template['version']) {
|
||||||
$updateStatus['up-to-date'] = true;
|
$updateStatus['up-to-date'] = true;
|
||||||
$updateStatus['automatically-updateable'] = false;
|
|
||||||
$updateStatus['conflicts'][] = __('Could not update the template. Local version is equal or newer.');
|
$updateStatus['conflicts'][] = __('Could not update the template. Local version is equal or newer.');
|
||||||
return $updateStatus;
|
return $updateStatus;
|
||||||
}
|
}
|
||||||
|
@ -1169,6 +1314,17 @@ class MetaTemplatesTable extends AppTable
|
||||||
$conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template);
|
$conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template);
|
||||||
if (!empty($conflicts)) {
|
if (!empty($conflicts)) {
|
||||||
$updateStatus['conflicts'] = $conflicts;
|
$updateStatus['conflicts'] = $conflicts;
|
||||||
|
$updateStatus['automatically-updateable'] = false;
|
||||||
|
$emptySum = 0;
|
||||||
|
foreach ($conflicts as $fieldname => $fieldStatus) {
|
||||||
|
if (!empty($fieldStatus['conflictingEntities'])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$emptySum += 1;
|
||||||
|
}
|
||||||
|
if ($emptySum == count($conflicts)) {
|
||||||
|
$updateStatus['automatically-updateable'] = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$updateStatus['automatically-updateable'] = true;
|
$updateStatus['automatically-updateable'] = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,10 +198,22 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'open_modal' => '/metaTemplates/migrateMetafieldsToNewestTemplate/[onclick_params_data_path]',
|
||||||
|
'modal_params_data_path' => 'id',
|
||||||
|
'title' => __('Update meta-fields to the newest version of this meta-template'),
|
||||||
|
'icon' => 'arrow-circle-up',
|
||||||
|
'variant' => 'success',
|
||||||
|
'complex_requirement' => [
|
||||||
|
'function' => function ($row, $options) {
|
||||||
|
return !empty($row['updateStatus']['to-existing']) && empty($row['updateStatus']['can-be-removed']);
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
[
|
[
|
||||||
'open_modal' => '/metaTemplates/delete/[onclick_params_data_path]',
|
'open_modal' => '/metaTemplates/delete/[onclick_params_data_path]',
|
||||||
'modal_params_data_path' => 'id',
|
'modal_params_data_path' => 'id',
|
||||||
'title' => __('Get meta-fields that should be moved to the newest version of this meta-template'),
|
'title' => __('This meta-template doesn\'t have any meta-fields and can be safely removed.'),
|
||||||
'icon' => 'trash',
|
'icon' => 'trash',
|
||||||
'variant' => 'success',
|
'variant' => 'success',
|
||||||
'complex_requirement' => [
|
'complex_requirement' => [
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Cake\Utility\Inflector;
|
||||||
|
use Cake\Routing\Router;
|
||||||
|
|
||||||
|
$urlNewestMetaTemplate = Router::url([
|
||||||
|
'controller' => 'metaTemplates',
|
||||||
|
'action' => 'view',
|
||||||
|
$newestMetaTemplate->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
$bodyHtml = '';
|
||||||
|
$bodyHtml .= sprintf('<div><span>%s: </span><span class="font-monospace">%s</span></div>', __('Current version'), h($oldMetaTemplate->version));
|
||||||
|
$bodyHtml .= sprintf('<div><span>%s: </span><a href="%s" target="_blank" class="font-monospac">%s</a></div>', __('Newest version'), $urlNewestMetaTemplate, h($newestMetaTemplate->version));
|
||||||
|
$bodyHtml .= sprintf('<h4 class="my-2">%s</h4>', __('{0} Entities with meta-fields for the meta-template version <span class="font-monospace">{1}</span>', h($entityCount), h($oldMetaTemplate->version)));
|
||||||
|
|
||||||
|
// debug($conflictingEntities);
|
||||||
|
|
||||||
|
if (empty($conflictingEntities)) {
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'text' => __('All entities can updated automatically', count($conflictingEntities)),
|
||||||
|
'variant' => 'success',
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'html' => sprintf(
|
||||||
|
'<ul>%s%s</ul>',
|
||||||
|
$this->Bootstrap->node('li', [], __('{0} entities can be updated automatically', $entityCount - count($conflictingEntities))),
|
||||||
|
$this->Bootstrap->node('li', [], __('{0} entities cannot be updated automatically and require manual migration', count($conflictingEntities)))
|
||||||
|
),
|
||||||
|
'variant' => 'warning',
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
$bodyHtml .= '<ul>';
|
||||||
|
foreach ($conflictingEntities as $entity) {
|
||||||
|
$url = Router::url([
|
||||||
|
'controller' => 'metaTemplates',
|
||||||
|
'action' => 'migrateOldMetaTemplateToNewestVersionForEntity',
|
||||||
|
$oldMetaTemplate->id,
|
||||||
|
$entity->id,
|
||||||
|
]);
|
||||||
|
$bodyHtml .= sprintf(
|
||||||
|
'<li><a href="%s" target="_blank">%s</a> <span class="fw-light">%s<span></li>',
|
||||||
|
$url,
|
||||||
|
__('{0}::{1}', h(Inflector::humanize($oldMetaTemplate->scope)), $entity->id),
|
||||||
|
__('has {0} meta-fields to update', count($entity->meta_fields))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (count($conflictingEntities) > 10) {
|
||||||
|
$bodyHtml .= sprintf('<li class="list-inline-item fw-light fs-7">%s</li>', __('{0} more entities', h(10 - count($conflictingEntities))));
|
||||||
|
}
|
||||||
|
$bodyHtml .= '</ul>';
|
||||||
|
}
|
||||||
|
$form = sprintf(
|
||||||
|
'<div class="d-none hidden-form-container">%s%s</div>',
|
||||||
|
$this->Form->create(null, [
|
||||||
|
'url' => [
|
||||||
|
'controller' => 'MetaTemplates',
|
||||||
|
'action' => 'migrateMetafieldsToNewestTemplate',
|
||||||
|
$oldMetaTemplate->id,
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
$this->Form->end()
|
||||||
|
);
|
||||||
|
$bodyHtml .= $form;
|
||||||
|
|
||||||
|
$title = __('{0} has a new meta-template and meta-fields to be updated', sprintf('<i class="me-1">%s</i>', h($oldMetaTemplate->name)));
|
||||||
|
if (!empty($ajax)) {
|
||||||
|
echo $this->Bootstrap->modal([
|
||||||
|
'titleHtml' => $title,
|
||||||
|
'bodyHtml' => $bodyHtml,
|
||||||
|
'size' => 'lg',
|
||||||
|
'type' => 'confirm',
|
||||||
|
'confirmButton' => [
|
||||||
|
'text' => __('Migrate meta-fields'),
|
||||||
|
'variant' => 'success',
|
||||||
|
],
|
||||||
|
'confirmFunction' => 'migrateMetafieldsToNewestTemplate',
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo $this->Bootstrap->node('h1', [], $title);
|
||||||
|
echo $bodyHtml;
|
||||||
|
echo $this->Bootstrap->button([
|
||||||
|
'text' => __('Migrate meta-fields'),
|
||||||
|
'variant' => 'success',
|
||||||
|
'onclick' => '$(".hidden-form-container form").submit()',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function migrateMetafieldsToNewestTemplate(modalObject, tmpApi) {
|
||||||
|
const $form = modalObject.$modal.find('form')
|
||||||
|
return tmpApi.postForm($form[0]).catch((errors) => {
|
||||||
|
const formHelper = new FormValidationHelper($form[0])
|
||||||
|
const errorHTMLNode = formHelper.buildValidationMessageNode(errors, true)
|
||||||
|
modalObject.$modal.find('div.form-error-container').append(errorHTMLNode)
|
||||||
|
return errors
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -18,6 +18,11 @@ if ($updateStatus['up-to-date']) {
|
||||||
'html' => __('This meta-template can be updated to version {0} (current: {1}).', sprintf('<strong>%s</strong>', h($templateOnDisk['version'])), h($metaTemplate->version)),
|
'html' => __('This meta-template can be updated to version {0} (current: {1}).', sprintf('<strong>%s</strong>', h($templateOnDisk['version'])), h($metaTemplate->version)),
|
||||||
'dismissible' => false,
|
'dismissible' => false,
|
||||||
]);
|
]);
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'variant' => 'success',
|
||||||
|
'text' => __('All meta-fields will be migrated to the newest version.'),
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
$form = $this->element('genericElements/Form/genericForm', [
|
$form = $this->element('genericElements/Form/genericForm', [
|
||||||
'entity' => null,
|
'entity' => null,
|
||||||
'ajax' => false,
|
'ajax' => false,
|
||||||
|
@ -28,9 +33,9 @@ if ($updateStatus['up-to-date']) {
|
||||||
[
|
[
|
||||||
'field' => 'update_strategy',
|
'field' => 'update_strategy',
|
||||||
'type' => 'checkbox',
|
'type' => 'checkbox',
|
||||||
'value' => MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW,
|
'value' => MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING,
|
||||||
'checked' => true,
|
'checked' => true,
|
||||||
]
|
],
|
||||||
],
|
],
|
||||||
'submit' => [
|
'submit' => [
|
||||||
'action' => $this->request->getParam('action')
|
'action' => $this->request->getParam('action')
|
||||||
|
@ -42,7 +47,7 @@ if ($updateStatus['up-to-date']) {
|
||||||
$modalSize = 'xl';
|
$modalSize = 'xl';
|
||||||
$bodyHtml .= $this->Bootstrap->alert([
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
'variant' => 'warning',
|
'variant' => 'warning',
|
||||||
'text' => __('Updating to version {0} cannot be done automatically as it introduces some conflicts.', h($templateOnDisk['version'])),
|
'html' => __('Updating to version {0} cannot be done automatically as it introduces some conflicts.', sprintf('<strong>%s</strong>', h($templateOnDisk['version']))),
|
||||||
'dismissible' => false,
|
'dismissible' => false,
|
||||||
]);
|
]);
|
||||||
$conflictTable = $this->element('MetaTemplates/conflictTable', [
|
$conflictTable = $this->element('MetaTemplates/conflictTable', [
|
||||||
|
@ -50,8 +55,11 @@ if ($updateStatus['up-to-date']) {
|
||||||
'metaTemplate' => $metaTemplate,
|
'metaTemplate' => $metaTemplate,
|
||||||
'templateOnDisk' => $templateOnDisk,
|
'templateOnDisk' => $templateOnDisk,
|
||||||
]);
|
]);
|
||||||
|
$conflictCount = array_reduce($templateStatus['conflicts'], function ($carry, $conflict) {
|
||||||
|
return $carry + count($conflict['conflictingEntities']);
|
||||||
|
}, 0);
|
||||||
$bodyHtml .= $this->Bootstrap->collapse([
|
$bodyHtml .= $this->Bootstrap->collapse([
|
||||||
'text' => __('View conflicts'),
|
'text' => __('View conflicts ({0})', $conflictCount),
|
||||||
'open' => false
|
'open' => false
|
||||||
], $conflictTable);
|
], $conflictTable);
|
||||||
$bodyHtml .= $this->element('MetaTemplates/conflictResolution', [
|
$bodyHtml .= $this->element('MetaTemplates/conflictResolution', [
|
||||||
|
|
|
@ -40,12 +40,19 @@ foreach ($templatesUpdateStatus as $uuid => $status) {
|
||||||
if (!empty($status['new'])) {
|
if (!empty($status['new'])) {
|
||||||
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||||
} else {
|
} else {
|
||||||
$tableHtml .= sprintf(
|
if ($status['current_version'] == $status['next_version']) {
|
||||||
'<td>%s %s %s</td>',
|
$tableHtml .= sprintf(
|
||||||
h($status['current_version']),
|
'<td>%s</td>',
|
||||||
$this->Bootstrap->icon('arrow-right', ['class' => 'fs-8']),
|
h($status['current_version'])
|
||||||
h($status['next_version'])
|
);
|
||||||
);
|
} else {
|
||||||
|
$tableHtml .= sprintf(
|
||||||
|
'<td>%s %s %s</td>',
|
||||||
|
h($status['current_version']),
|
||||||
|
$this->Bootstrap->icon('arrow-right', ['class' => 'fs-8']),
|
||||||
|
h($status['next_version'])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!empty($status['new'])) {
|
if (!empty($status['new'])) {
|
||||||
$numberOfUpdates += 1;
|
$numberOfUpdates += 1;
|
||||||
|
@ -60,6 +67,8 @@ foreach ($templatesUpdateStatus as $uuid => $status) {
|
||||||
}
|
}
|
||||||
if (!empty($status['new'])) {
|
if (!empty($status['new'])) {
|
||||||
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||||
|
} elseif (!empty($status['up-to-date'])) {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||||
} else {
|
} else {
|
||||||
$tableHtml .= sprintf('<td>%s</td>', !empty($status['conflicts']) ? $this->Bootstrap->icon('check') : $this->Bootstrap->icon('times'));
|
$tableHtml .= sprintf('<td>%s</td>', !empty($status['conflicts']) ? $this->Bootstrap->icon('check') : $this->Bootstrap->icon('times'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
$create_new_allowed = true;
|
$create_new_allowed = true;
|
||||||
$keep_all_allowed = false;
|
$update_allowed = true;
|
||||||
$delete_all_allowed = false;
|
$delete_all_allowed = false;
|
||||||
$totalAllowed = $create_new_allowed + $keep_all_allowed + $delete_all_allowed;
|
$totalAllowed = $create_new_allowed + $update_allowed + $delete_all_allowed;
|
||||||
$maxWidth = 99 - ($create_new_allowed ? 33 : 0) - ($keep_all_allowed ? 33 : 0) - ($delete_all_allowed ? 33 : 0);
|
$maxWidth = 99 - ($create_new_allowed ? 33 : 0) - ($update_allowed ? 33 : 0) - ($delete_all_allowed ? 33 : 0);
|
||||||
|
$defaultStrategy = 'update_existing';
|
||||||
|
|
||||||
$form = $this->element('genericElements/Form/genericForm', [
|
$form = $this->element('genericElements/Form/genericForm', [
|
||||||
'entity' => null,
|
'entity' => null,
|
||||||
|
@ -17,7 +18,7 @@ $form = $this->element('genericElements/Form/genericForm', [
|
||||||
'type' => 'radio',
|
'type' => 'radio',
|
||||||
'options' => [
|
'options' => [
|
||||||
['value' => 'create_new', 'text' => 'create_new', 'id' => 'radio_create_new'],
|
['value' => 'create_new', 'text' => 'create_new', 'id' => 'radio_create_new'],
|
||||||
['value' => 'keep_both', 'text' => 'keep_both', 'id' => 'radio_keep_both'],
|
['value' => 'update_existing', 'text' => 'update', 'id' => 'radio_update'],
|
||||||
['value' => 'delete', 'text' => 'delete', 'id' => 'radio_delete'],
|
['value' => 'delete', 'text' => 'delete', 'id' => 'radio_delete'],
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
@ -33,10 +34,13 @@ $form = $this->element('genericElements/Form/genericForm', [
|
||||||
<div class="mt-3 d-flex justify-content-center">
|
<div class="mt-3 d-flex justify-content-center">
|
||||||
<div class="btn-group justify-content-center" role="group" aria-label="Basic radio toggle button group">
|
<div class="btn-group justify-content-center" role="group" aria-label="Basic radio toggle button group">
|
||||||
<?php if ($create_new_allowed) : ?>
|
<?php if ($create_new_allowed) : ?>
|
||||||
<input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" value="create_new" checked>
|
<input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" value="create_new" <?= $defaultStrategy == 'create_new' ? 'checked' : '' ?>>
|
||||||
<label class="btn btn-outline-primary mw-<?= $maxWidth ?>" for="btnradio1">
|
<label class="btn btn-outline-warning mw-<?= $maxWidth ?>" for="btnradio1">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-3"><?= __('Create new template') ?></h5>
|
<h5 class="mb-3">
|
||||||
|
<?= $defaultStrategy == 'create_new' ? $this->Bootstrap->badge(['text' => 'recommended', 'variant' => 'success', 'class' => ['mb-3', 'fs-8']]) : '' ?>
|
||||||
|
<?= __('Create new template') ?>
|
||||||
|
</h5>
|
||||||
<ul class="text-start fs-7">
|
<ul class="text-start fs-7">
|
||||||
<li><?= __('A new meta-template will be created and made default.') ?></li>
|
<li><?= __('A new meta-template will be created and made default.') ?></li>
|
||||||
<li><?= __('The old meta-template will remain untouched.') ?></li>
|
<li><?= __('The old meta-template will remain untouched.') ?></li>
|
||||||
|
@ -46,14 +50,17 @@ $form = $this->element('genericElements/Form/genericForm', [
|
||||||
</label>
|
</label>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($keep_all_allowed) : ?>
|
<?php if ($update_allowed) : ?>
|
||||||
<input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off" value="keep_both">
|
<input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off" value="update_existing" <?= $defaultStrategy == 'update_existing' ? 'checked' : '' ?>>
|
||||||
<label class="btn btn-outline-warning mw-<?= $maxWidth ?>" for="btnradio2">
|
<label class="btn btn-outline-primary mw-<?= $maxWidth ?>" for="btnradio2">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-3"><?= __('Update non-conflicting') ?></h5>
|
<div><?= $defaultStrategy == 'update_existing' ? $this->Bootstrap->badge(['text' => 'recommended', 'variant' => 'success', 'class' => ['mb-3']]) : '' ?></div>
|
||||||
|
<h5 class="mb-3">
|
||||||
|
<?= __('Update non-conflicting') ?>
|
||||||
|
</h5>
|
||||||
<ul class="text-start fs-7">
|
<ul class="text-start fs-7">
|
||||||
<li><?= __('Meta-fields not having conflicts will be migrated to the new meta-template.') ?></li>
|
<li><?= __('Entities not having conflicts will have their meta-fields migrated to the new meta-template.') ?></li>
|
||||||
<li><?= __('Meta-fields having a conflicts will stay on their current meta-template.') ?></li>
|
<li><?= __('Entities having a conflicts will stay on their current meta-template.') ?></li>
|
||||||
<li><?= __('Conflicts can be taken care of manually via the UI.') ?></li>
|
<li><?= __('Conflicts can be taken care of manually via the UI.') ?></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,10 +68,13 @@ $form = $this->element('genericElements/Form/genericForm', [
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($delete_all_allowed) : ?>
|
<?php if ($delete_all_allowed) : ?>
|
||||||
<input type="radio" class="btn-check" name="btnradio" id="btnradio3" autocomplete="off" value="delete">
|
<input type="radio" class="btn-check" name="btnradio" id="btnradio3" autocomplete="off" value="delete" <?= $defaultStrategy == 'delete' ? 'checked' : '' ?>>
|
||||||
<label class="btn btn-outline-danger mw-<?= $maxWidth ?>" for="btnradio3">
|
<label class="btn btn-outline-danger mw-<?= $maxWidth ?>" for="btnradio3">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-3"><?= __('Delete conflicting fields') ?></h5>
|
<h5 class="mb-3">
|
||||||
|
<?= $defaultStrategy == 'delete' ? $this->Bootstrap->badge(['text' => 'recommended', 'variant' => 'success', 'class' => ['mb-3', 'fs-8']]) : '' ?>
|
||||||
|
<?= __('Delete conflicting fields') ?>
|
||||||
|
</h5>
|
||||||
<ul class="text-start fs-7">
|
<ul class="text-start fs-7">
|
||||||
<li><?= __('Meta-fields not satisfying the new meta-template definition will be deleted.') ?></li>
|
<li><?= __('Meta-fields not satisfying the new meta-template definition will be deleted.') ?></li>
|
||||||
<li><?= __('All other meta-fields will be upgraded to the new meta-template.') ?></li>
|
<li><?= __('All other meta-fields will be upgraded to the new meta-template.') ?></li>
|
||||||
|
@ -84,18 +94,18 @@ $form = $this->element('genericElements/Form/genericForm', [
|
||||||
(function() {
|
(function() {
|
||||||
const $form = $('.conflict-resolution-form-container form')
|
const $form = $('.conflict-resolution-form-container form')
|
||||||
const $create = $form.find('input#radio_create_new')
|
const $create = $form.find('input#radio_create_new')
|
||||||
const $keep = $form.find('input#radio_keep_both')
|
const $keep = $form.find('input#radio_update')
|
||||||
const $delete = $form.find('input#radio_delete')
|
const $delete = $form.find('input#radio_delete')
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$('.conflict-resolution-picker').find('input[type="radio"]').change(function() {
|
$('.conflict-resolution-picker').find('input[type="radio"]').change(function() {
|
||||||
updateSelected($(this).val())
|
updateSelected($(this).val())
|
||||||
})
|
})
|
||||||
updateSelected('create_new')
|
updateSelected('<?= $defaultStrategy ?>')
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateSelected(choice) {
|
function updateSelected(choice) {
|
||||||
if (choice == 'keep_both') {
|
if (choice == 'update_existing') {
|
||||||
$keep.prop('checked', true)
|
$keep.prop('checked', true)
|
||||||
} else if (choice == 'delete') {
|
} else if (choice == 'delete') {
|
||||||
$delete.prop('checked', true)
|
$delete.prop('checked', true)
|
||||||
|
|
|
@ -21,7 +21,7 @@ use Cake\Routing\Router;
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?php
|
<?php
|
||||||
foreach ($fieldConflict['conflictingEntities'] as $i => $id) {
|
foreach ($fieldConflict['conflictingEntities'] as $i => $metaEntity) {
|
||||||
if ($i > 0) {
|
if ($i > 0) {
|
||||||
echo ', ';
|
echo ', ';
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,9 @@ use Cake\Routing\Router;
|
||||||
$url = Router::url([
|
$url = Router::url([
|
||||||
'controller' => Inflector::pluralize($templateStatus['existing_template']->scope),
|
'controller' => Inflector::pluralize($templateStatus['existing_template']->scope),
|
||||||
'action' => 'view',
|
'action' => 'view',
|
||||||
$id
|
$metaEntity['parent_id']
|
||||||
]);
|
]);
|
||||||
echo sprintf('<a href="%s" target="_blank">%s</a>', $url, __('{0} #{1}', h(Inflector::humanize($templateStatus['existing_template']->scope)), h($id)));
|
echo sprintf('<a href="%s" target="_blank">%s</a>', $url, __('{0} #{1}', h(Inflector::humanize($templateStatus['existing_template']->scope)), h($metaEntity['parent_id'])));
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</td>
|
</td>
|
||||||
|
|
Loading…
Reference in New Issue