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',
|
||||
]);
|
||||
|
||||
if (empty($this->viewVars['updateableTemplates']['up-to-date'])) {
|
||||
if (empty($this->viewVars['templateStatus']['up-to-date'])) {
|
||||
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'update', [
|
||||
'label' => __('Update template'),
|
||||
'url' => '/metaTemplates/update/{{id}}',
|
||||
|
|
|
@ -244,6 +244,55 @@ class MetaTemplatesController extends AppController
|
|||
$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()
|
||||
{
|
||||
$templatesUpdateStatus = $this->MetaTemplates->getUpdateStatusForTemplates();
|
||||
|
|
|
@ -39,8 +39,6 @@ class MetaFieldsTable extends AppTable
|
|||
->notEmptyString('value')
|
||||
->notEmptyString('meta_template_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')
|
||||
->requirePresence(['scope', 'field', 'value', 'uuid', 'meta_template_directory_id', ], 'create');
|
||||
|
||||
|
@ -53,10 +51,9 @@ class MetaFieldsTable extends AppTable
|
|||
return $validator;
|
||||
}
|
||||
|
||||
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
||||
{
|
||||
if (!isset($data['meta_template_directory_id'])) {
|
||||
$data['meta_template_directory_id'] = $this->getTemplateDirectoryIdFromMetaTemplate($data['meta_template_id']);
|
||||
public function afterMarshal(EventInterface $event, \App\Model\Entity\MetaField $entity, ArrayObject $data, ArrayObject $options) {
|
||||
if (!isset($entity->meta_template_directory_id)) {
|
||||
$entity->set('meta_template_directory_id', $this->getTemplateDirectoryIdFromMetaTemplate($entity->meta_template_id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +86,9 @@ class MetaFieldsTable extends AppTable
|
|||
if (!empty($metaTemplateField['regex'])) {
|
||||
return $this->isValidRegex($value, $metaTemplateField);
|
||||
}
|
||||
if (!empty($metaTemplateField['values_list'])) {
|
||||
return $this->isValidValuesList($value, $metaTemplateField);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -118,4 +118,11 @@ class MetaFieldsTable extends AppTable
|
|||
}
|
||||
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_DELETE = 'delete_all';
|
||||
|
||||
public const DEFAULT_STRATEGY = MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW;
|
||||
public const ALLOWED_STRATEGIES = [MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW];
|
||||
public const DEFAULT_STRATEGY = MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING;
|
||||
public const ALLOWED_STRATEGIES = [
|
||||
MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW,
|
||||
MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING,
|
||||
MetaTemplatesTable::UPDATE_STRATEGY_KEEP_BOTH,
|
||||
];
|
||||
|
||||
private $templatesOnDisk = null;
|
||||
|
||||
|
@ -73,6 +77,41 @@ class MetaTemplatesTable extends AppTable
|
|||
* @return array The update result containing potential errors and the successes
|
||||
*/
|
||||
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 = [];
|
||||
$files_processed = [];
|
||||
|
@ -106,7 +145,7 @@ class MetaTemplatesTable extends AppTable
|
|||
* @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
|
||||
public function update(\App\Model\Entity\MetaTemplate $metaTemplate, $strategy = null): array
|
||||
{
|
||||
$files_processed = [];
|
||||
$updatesErrors = [];
|
||||
|
@ -114,6 +153,25 @@ class MetaTemplatesTable extends AppTable
|
|||
$templateStatus = $this->getStatusForMetaTemplate($templateOnDisk, $metaTemplate);
|
||||
$updateStatus = $this->computeFullUpdateStatusForMetaTemplate($templateStatus, $metaTemplate);
|
||||
$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;
|
||||
if ($updateStatus['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');
|
||||
} else if (!$updateStatus['up-to-date']) {
|
||||
$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);
|
||||
} else {
|
||||
$errors['message'] = __('Could not update. Something went wrong.');
|
||||
}
|
||||
if ($success) {
|
||||
$files_processed[] = $templateOnDisk['uuid'];
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
$updatesErrors[] = $errors;
|
||||
}
|
||||
$results = [
|
||||
'update_errors' => $updatesErrors,
|
||||
'files_processed' => $files_processed,
|
||||
'success' => !empty($files_processed),
|
||||
return [
|
||||
'success' => $success,
|
||||
'errors' => $errors,
|
||||
];
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -764,13 +817,27 @@ class MetaTemplatesTable extends AppTable
|
|||
$errors[] = new UpdateError(false, $metaTemplate);
|
||||
return false;
|
||||
}
|
||||
$metaTemplate = $this->patchEntity($metaTemplate, $template, [
|
||||
'associated' => ['MetaTemplateFields']
|
||||
]);
|
||||
$metaTemplate = $this->patchEntity($metaTemplate, $template);
|
||||
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, [
|
||||
'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());
|
||||
return false;
|
||||
}
|
||||
|
@ -797,8 +864,8 @@ class MetaTemplatesTable extends AppTable
|
|||
}
|
||||
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_UPDATE_EXISTING) {
|
||||
$result = $this->updateMetaTemplate($metaTemplate, $template);
|
||||
} else if ($strategy == MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW) {
|
||||
$result = $this->executeStrategyCreateNew($template, $metaTemplate);
|
||||
} 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']);
|
||||
}
|
||||
$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');
|
||||
$errors = [];
|
||||
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);
|
||||
|
||||
$conflictingEntities = [];
|
||||
foreach ($entities as $entity) {
|
||||
$conflicts = $this->getMetaFieldsConflictsUnderTemplate($entity['meta_fields'], $template);
|
||||
if (!empty($conflicts)) {
|
||||
|
@ -849,8 +916,9 @@ class MetaTemplatesTable extends AppTable
|
|||
$this->updateMetaTemplate($metaTemplate, $template, $errors);
|
||||
return !empty($errors) ? $errors[0] : true;
|
||||
}
|
||||
$template['is_default'] = $metaTemplate['is_default'];
|
||||
$template['enabled'] = $metaTemplate['enabled'];
|
||||
$template['is_default'] = $metaTemplate->is_default;
|
||||
$template['enabled'] = $metaTemplate->enabled;
|
||||
$metaTemplate->set('enabled', false);
|
||||
if ($metaTemplate->is_default) {
|
||||
$metaTemplate->set('is_default', false);
|
||||
$this->save($metaTemplate);
|
||||
|
@ -858,13 +926,9 @@ class MetaTemplatesTable extends AppTable
|
|||
$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])) { // conflicting entities remain untouched
|
||||
foreach ($entity['meta_fields'] as $metaField) {
|
||||
$savedMetaTemplateField = $savedMetaTemplateFieldByName[$metaField->field];
|
||||
$this->supersedeMetaFieldWithMetaTemplateField($metaField, $savedMetaTemplateField);
|
||||
}
|
||||
$this->supersedeMetaFieldsWithMetaTemplateField($entity['meta_fields'], $savedMetaTemplate);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -873,6 +937,44 @@ class MetaTemplatesTable extends AppTable
|
|||
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.
|
||||
* Strategy:
|
||||
|
@ -941,16 +1043,20 @@ class MetaTemplatesTable extends AppTable
|
|||
/**
|
||||
* 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
|
||||
* @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
|
||||
{
|
||||
$savedMetaTemplateFieldByName = Hash::combine($savedMetaTemplate['meta_template_fields'], '{n}.field', '{n}');
|
||||
foreach ($metaFields as $i => $metaField) {
|
||||
$savedMetaTemplateField = $savedMetaTemplateFieldByName[$metaField->field];
|
||||
$metaField->set('meta_template_id', $savedMetaTemplateField->meta_template_id);
|
||||
$metaField->set('meta_template_field_id', $savedMetaTemplateField->id);
|
||||
$metaField = $this->MetaTemplateFields->MetaFields->save($metaField);
|
||||
return !empty($metaField);
|
||||
}
|
||||
$entities = $this->MetaTemplateFields->MetaFields->saveMany($metaFields);
|
||||
return !empty($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -977,13 +1083,14 @@ class MetaTemplatesTable extends AppTable
|
|||
$metaTemplateFieldByName[$metaTemplateField['field']] = $this->MetaTemplateFields->newEntity($metaTemplateField);
|
||||
}
|
||||
foreach ($metaFields as $metaField) {
|
||||
if ($existingMetaTemplate && $metaField->meta_template_id != $template->id) {
|
||||
continue;
|
||||
}
|
||||
if (empty($metaTemplateFieldByName[$metaField->field])) { // Meta-field was removed from the template
|
||||
$isValid = false;
|
||||
} else {
|
||||
$isValid = $this->MetaTemplateFields->MetaFields->isValidMetaFieldForMetaTemplateField(
|
||||
$metaField->value,
|
||||
$metaTemplateFieldByName[$metaField->field]
|
||||
);
|
||||
}
|
||||
if ($isValid !== true) {
|
||||
$conflicting[] = $metaField;
|
||||
}
|
||||
|
@ -1027,27 +1134,12 @@ class MetaTemplatesTable extends AppTable
|
|||
$result['conflictingEntities'] = Hash::extract($conflictingStatus, '{n}.parent_id');
|
||||
}
|
||||
}
|
||||
if (!empty($templateField['regex']) && $templateField['regex'] != $metaTemplateField->regex) {
|
||||
$entitiesWithMetaFieldQuery = $this->MetaTemplateFields->MetaFields->find();
|
||||
$entitiesWithMetaFieldQuery
|
||||
->enableHydration(false)
|
||||
->select([
|
||||
'parent_id',
|
||||
])
|
||||
->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();
|
||||
|
||||
if (
|
||||
(!empty($templateField['regex']) && $templateField['regex'] != $metaTemplateField->regex) ||
|
||||
!empty($templateField['values_list'])
|
||||
) {
|
||||
$entities = $this->getEntitiesForMetaTemplateField($scope, $metaTemplateField->id, true);
|
||||
$conflictingEntities = [];
|
||||
foreach ($entities as $entity) {
|
||||
foreach ($entity['meta_fields'] as $metaField) {
|
||||
|
@ -1056,7 +1148,10 @@ class MetaTemplatesTable extends AppTable
|
|||
$templateField
|
||||
);
|
||||
if ($isValid !== true) {
|
||||
$conflictingEntities[] = $entity->id;
|
||||
$conflictingEntities[] = [
|
||||
'parent_id' => $entity->id,
|
||||
'meta_template_field_id' => $metaTemplateField->id,
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1064,13 +1159,50 @@ class MetaTemplatesTable extends AppTable
|
|||
|
||||
if (!empty($conflictingEntities)) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
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
|
||||
*
|
||||
|
@ -1087,7 +1219,7 @@ class MetaTemplatesTable extends AppTable
|
|||
$templateMetaFields = $template['metaFields'];
|
||||
}
|
||||
$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 ($metaTemplate->meta_template_fields as $metaField) {
|
||||
if ($newMetaField['field'] == $metaField->field) {
|
||||
|
@ -1103,10 +1235,23 @@ class MetaTemplatesTable extends AppTable
|
|||
}
|
||||
}
|
||||
if (!empty($existingMetaTemplateFields)) {
|
||||
foreach ($existingMetaTemplateFields as $field => $tmp) {
|
||||
$conflicts[$field] = [
|
||||
foreach ($existingMetaTemplateFields as $metaTemplateField) {
|
||||
$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,
|
||||
'conflicts' => [__('This field is intended to be removed')],
|
||||
'conflictingEntities' => $entityWithMetafieldToBeRemoved,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1159,9 +1304,9 @@ class MetaTemplatesTable extends AppTable
|
|||
$updateStatus['current_version'] = $metaTemplate->version;
|
||||
$updateStatus['next_version'] = $template['version'];
|
||||
$updateStatus['new'] = false;
|
||||
$updateStatus['automatically-updateable'] = 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;
|
||||
}
|
||||
|
@ -1169,6 +1314,17 @@ class MetaTemplatesTable extends AppTable
|
|||
$conflicts = $this->getMetaTemplateConflictsForMetaTemplate($metaTemplate, $template);
|
||||
if (!empty($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 {
|
||||
$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]',
|
||||
'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',
|
||||
'variant' => 'success',
|
||||
'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)),
|
||||
'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', [
|
||||
'entity' => null,
|
||||
'ajax' => false,
|
||||
|
@ -28,9 +33,9 @@ if ($updateStatus['up-to-date']) {
|
|||
[
|
||||
'field' => 'update_strategy',
|
||||
'type' => 'checkbox',
|
||||
'value' => MetaTemplatesTable::UPDATE_STRATEGY_CREATE_NEW,
|
||||
'value' => MetaTemplatesTable::UPDATE_STRATEGY_UPDATE_EXISTING,
|
||||
'checked' => true,
|
||||
]
|
||||
],
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
|
@ -42,7 +47,7 @@ if ($updateStatus['up-to-date']) {
|
|||
$modalSize = 'xl';
|
||||
$bodyHtml .= $this->Bootstrap->alert([
|
||||
'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,
|
||||
]);
|
||||
$conflictTable = $this->element('MetaTemplates/conflictTable', [
|
||||
|
@ -50,8 +55,11 @@ if ($updateStatus['up-to-date']) {
|
|||
'metaTemplate' => $metaTemplate,
|
||||
'templateOnDisk' => $templateOnDisk,
|
||||
]);
|
||||
$conflictCount = array_reduce($templateStatus['conflicts'], function ($carry, $conflict) {
|
||||
return $carry + count($conflict['conflictingEntities']);
|
||||
}, 0);
|
||||
$bodyHtml .= $this->Bootstrap->collapse([
|
||||
'text' => __('View conflicts'),
|
||||
'text' => __('View conflicts ({0})', $conflictCount),
|
||||
'open' => false
|
||||
], $conflictTable);
|
||||
$bodyHtml .= $this->element('MetaTemplates/conflictResolution', [
|
||||
|
|
|
@ -39,6 +39,12 @@ foreach ($templatesUpdateStatus as $uuid => $status) {
|
|||
}
|
||||
if (!empty($status['new'])) {
|
||||
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||
} else {
|
||||
if ($status['current_version'] == $status['next_version']) {
|
||||
$tableHtml .= sprintf(
|
||||
'<td>%s</td>',
|
||||
h($status['current_version'])
|
||||
);
|
||||
} else {
|
||||
$tableHtml .= sprintf(
|
||||
'<td>%s %s %s</td>',
|
||||
|
@ -47,6 +53,7 @@ foreach ($templatesUpdateStatus as $uuid => $status) {
|
|||
h($status['next_version'])
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!empty($status['new'])) {
|
||||
$numberOfUpdates += 1;
|
||||
$tableHtml .= sprintf('<td>%s</td>', $this->Bootstrap->icon('check'));
|
||||
|
@ -60,6 +67,8 @@ foreach ($templatesUpdateStatus as $uuid => $status) {
|
|||
}
|
||||
if (!empty($status['new'])) {
|
||||
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||
} elseif (!empty($status['up-to-date'])) {
|
||||
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||
} else {
|
||||
$tableHtml .= sprintf('<td>%s</td>', !empty($status['conflicts']) ? $this->Bootstrap->icon('check') : $this->Bootstrap->icon('times'));
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<?php
|
||||
$create_new_allowed = true;
|
||||
$keep_all_allowed = false;
|
||||
$update_allowed = true;
|
||||
$delete_all_allowed = false;
|
||||
$totalAllowed = $create_new_allowed + $keep_all_allowed + $delete_all_allowed;
|
||||
$maxWidth = 99 - ($create_new_allowed ? 33 : 0) - ($keep_all_allowed ? 33 : 0) - ($delete_all_allowed ? 33 : 0);
|
||||
$totalAllowed = $create_new_allowed + $update_allowed + $delete_all_allowed;
|
||||
$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', [
|
||||
'entity' => null,
|
||||
|
@ -17,7 +18,7 @@ $form = $this->element('genericElements/Form/genericForm', [
|
|||
'type' => 'radio',
|
||||
'options' => [
|
||||
['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'],
|
||||
],
|
||||
]
|
||||
|
@ -33,10 +34,13 @@ $form = $this->element('genericElements/Form/genericForm', [
|
|||
<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">
|
||||
<?php if ($create_new_allowed) : ?>
|
||||
<input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" value="create_new" checked>
|
||||
<label class="btn btn-outline-primary mw-<?= $maxWidth ?>" for="btnradio1">
|
||||
<input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" value="create_new" <?= $defaultStrategy == 'create_new' ? 'checked' : '' ?>>
|
||||
<label class="btn btn-outline-warning mw-<?= $maxWidth ?>" for="btnradio1">
|
||||
<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">
|
||||
<li><?= __('A new meta-template will be created and made default.') ?></li>
|
||||
<li><?= __('The old meta-template will remain untouched.') ?></li>
|
||||
|
@ -46,14 +50,17 @@ $form = $this->element('genericElements/Form/genericForm', [
|
|||
</label>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($keep_all_allowed) : ?>
|
||||
<input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off" value="keep_both">
|
||||
<label class="btn btn-outline-warning mw-<?= $maxWidth ?>" for="btnradio2">
|
||||
<?php if ($update_allowed) : ?>
|
||||
<input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off" value="update_existing" <?= $defaultStrategy == 'update_existing' ? 'checked' : '' ?>>
|
||||
<label class="btn btn-outline-primary mw-<?= $maxWidth ?>" for="btnradio2">
|
||||
<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">
|
||||
<li><?= __('Meta-fields not having conflicts will be migrated to the new meta-template.') ?></li>
|
||||
<li><?= __('Meta-fields having a conflicts will stay on their current meta-template.') ?></li>
|
||||
<li><?= __('Entities not having conflicts will have their meta-fields migrated to the new 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>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -61,10 +68,13 @@ $form = $this->element('genericElements/Form/genericForm', [
|
|||
<?php endif; ?>
|
||||
|
||||
<?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">
|
||||
<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">
|
||||
<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>
|
||||
|
@ -84,18 +94,18 @@ $form = $this->element('genericElements/Form/genericForm', [
|
|||
(function() {
|
||||
const $form = $('.conflict-resolution-form-container form')
|
||||
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')
|
||||
|
||||
$(document).ready(function() {
|
||||
$('.conflict-resolution-picker').find('input[type="radio"]').change(function() {
|
||||
updateSelected($(this).val())
|
||||
})
|
||||
updateSelected('create_new')
|
||||
updateSelected('<?= $defaultStrategy ?>')
|
||||
})
|
||||
|
||||
function updateSelected(choice) {
|
||||
if (choice == 'keep_both') {
|
||||
if (choice == 'update_existing') {
|
||||
$keep.prop('checked', true)
|
||||
} else if (choice == 'delete') {
|
||||
$delete.prop('checked', true)
|
||||
|
|
|
@ -21,7 +21,7 @@ use Cake\Routing\Router;
|
|||
</td>
|
||||
<td>
|
||||
<?php
|
||||
foreach ($fieldConflict['conflictingEntities'] as $i => $id) {
|
||||
foreach ($fieldConflict['conflictingEntities'] as $i => $metaEntity) {
|
||||
if ($i > 0) {
|
||||
echo ', ';
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ use Cake\Routing\Router;
|
|||
$url = Router::url([
|
||||
'controller' => Inflector::pluralize($templateStatus['existing_template']->scope),
|
||||
'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>
|
||||
|
|
Loading…
Reference in New Issue