new: [metaTemplate] Interface and functions to update meta-templates - WiP
Actual update not implemented yet.pull/93/head
parent
a6ecab5b47
commit
819d96e805
|
@ -12,26 +12,42 @@ class MetaTemplatesNavigation extends BaseNavigation
|
||||||
$this->bcf->addRoute('MetaTemplates', 'view', $this->bcf->defaultCRUD('MetaTemplates', 'view'));
|
$this->bcf->addRoute('MetaTemplates', 'view', $this->bcf->defaultCRUD('MetaTemplates', 'view'));
|
||||||
$this->bcf->addRoute('MetaTemplates', 'enable', [
|
$this->bcf->addRoute('MetaTemplates', 'enable', [
|
||||||
'label' => __('Enable'),
|
'label' => __('Enable'),
|
||||||
'icon' => 'check',
|
'icon' => 'check-square',
|
||||||
'url' => '/metaTemplates/enable/{{id}}/enabled',
|
'url' => '/metaTemplates/enable/{{id}}/enabled',
|
||||||
'url_vars' => ['id' => 'id'],
|
'url_vars' => ['id' => 'id'],
|
||||||
]);
|
]);
|
||||||
$this->bcf->addRoute('MetaTemplates', 'set_default', [
|
$this->bcf->addRoute('MetaTemplates', 'set_default', [
|
||||||
'label' => __('Set as default'),
|
'label' => __('Set as default'),
|
||||||
'icon' => 'check',
|
'icon' => 'check-square',
|
||||||
'url' => '/metaTemplates/toggle/{{id}}/default',
|
'url' => '/metaTemplates/toggle/{{id}}/default',
|
||||||
'url_vars' => ['id' => 'id'],
|
'url_vars' => ['id' => 'id'],
|
||||||
]);
|
]);
|
||||||
$this->bcf->addRoute('MetaTemplates', 'update', [
|
|
||||||
|
$totalUpdateCount = 0;
|
||||||
|
if (!empty($this->viewVars['updateableTemplate']['updateable']) && !empty($this->viewVars['updateableTemplate']['new'])) {
|
||||||
|
$udpateCount = count($this->viewVars['updateableTemplate']['updateable']) ?? 0;
|
||||||
|
$newCount = count($this->viewVars['updateableTemplate']['new']) ?? 0;
|
||||||
|
$totalUpdateCount = $udpateCount + $newCount;
|
||||||
|
}
|
||||||
|
$updateRouteConfig = [
|
||||||
'label' => __('Update all templates'),
|
'label' => __('Update all templates'),
|
||||||
'icon' => 'download',
|
'icon' => 'download',
|
||||||
'url' => '/metaTemplates/update',
|
'url' => '/metaTemplates/update',
|
||||||
]);
|
];
|
||||||
|
if ($totalUpdateCount > 0) {
|
||||||
|
$updateRouteConfig['badge'] = [
|
||||||
|
'text' => h($totalUpdateCount),
|
||||||
|
'variant' => 'warning',
|
||||||
|
'title' => __('There are {0} new meta-template(s) and {1} update(s) available', h($newCount), h($udpateCount)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$this->bcf->addRoute('MetaTemplates', 'update', $updateRouteConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addParents()
|
public function addParents()
|
||||||
{
|
{
|
||||||
$this->bcf->addParent('MetaTemplates', 'view', 'MetaTemplates', 'index');
|
$this->bcf->addParent('MetaTemplates', 'view', 'MetaTemplates', 'index');
|
||||||
|
$this->bcf->addParent('MetaTemplates', 'update', 'MetaTemplates', 'index');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addLinks()
|
public function addLinks()
|
||||||
|
@ -41,12 +57,38 @@ class MetaTemplatesNavigation extends BaseNavigation
|
||||||
|
|
||||||
public function addActions()
|
public function addActions()
|
||||||
{
|
{
|
||||||
$this->bcf->addAction('MetaTemplates', 'index', 'MetaTemplates', 'update');
|
$totalUpdateCount = 0;
|
||||||
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'update', [
|
if (!empty($this->viewVars['updateableTemplate']['not_up_to_date']) && !empty($this->viewVars['updateableTemplate']['new'])) {
|
||||||
|
$udpateCount = count($this->viewVars['updateableTemplate']['not_up_to_date']) ?? 0;
|
||||||
|
$newCount = count($this->viewVars['updateableTemplate']['new']) ?? 0;
|
||||||
|
$totalUpdateCount = $udpateCount + $newCount;
|
||||||
|
}
|
||||||
|
$updateActionConfig = [
|
||||||
'label' => __('Update template'),
|
'label' => __('Update template'),
|
||||||
'url' => '/metaTemplates/update/{{id}}',
|
'url' => '/metaTemplates/update/{{id}}',
|
||||||
'url_vars' => ['id' => 'id'],
|
'url_vars' => ['id' => 'id'],
|
||||||
]);
|
];
|
||||||
|
if ($totalUpdateCount > 0) {
|
||||||
|
$updateActionConfig['badge'] = [
|
||||||
|
'text' => h($totalUpdateCount),
|
||||||
|
'variant' => 'warning',
|
||||||
|
'title' => __('There are {0} new meta-template(s) and {1} update(s) available', h($newCount), h($udpateCount)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$this->bcf->addAction('MetaTemplates', 'index', 'MetaTemplates', 'update', $updateActionConfig);
|
||||||
|
|
||||||
|
if (empty($this->viewVars['updateableTemplate']['up-to-date'])) {
|
||||||
|
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'update', [
|
||||||
|
'label' => __('Update template'),
|
||||||
|
'url' => '/metaTemplates/update/{{id}}',
|
||||||
|
'url_vars' => ['id' => 'id'],
|
||||||
|
'variant' => 'warning',
|
||||||
|
'badge' => [
|
||||||
|
'variant' => 'warning',
|
||||||
|
'title' => __('Update available')
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'enable');
|
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'enable');
|
||||||
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'set_default');
|
$this->bcf->addAction('MetaTemplates', 'view', 'MetaTemplates', 'set_default');
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,21 +37,22 @@ class MetaTemplatesController extends AppController
|
||||||
} else {
|
} else {
|
||||||
if (!$this->ParamHandler->isRest()) {
|
if (!$this->ParamHandler->isRest()) {
|
||||||
if (!empty($template_id)) {
|
if (!empty($template_id)) {
|
||||||
$this->set('title', __('Update Meta Templates #{0}', h($template_id)));
|
$this->set('metaTemplate', $metaTemplate);
|
||||||
$this->set('question', __('Are you sure you wish to update the Meta Template definitions of the template `{0}`?', h($metaTemplate->name)));
|
$this->setUpdateStatus($metaTemplate->id);
|
||||||
} else {
|
} else {
|
||||||
$this->set('title', __('Update Meta Templates'));
|
$this->set('title', __('Update Meta Templates'));
|
||||||
$this->set('question', __('Are you sure you wish to update the Meta Template definitions'));
|
$this->set('question', __('Are you sure you wish to update the Meta Template definitions'));
|
||||||
|
$updateableTemplates = $this->MetaTemplates->checkForUpdates();
|
||||||
|
$this->set('updateableTemplates', $updateableTemplates);
|
||||||
|
$this->render('updateAll');
|
||||||
}
|
}
|
||||||
$this->set('actionName', __('Update'));
|
|
||||||
$this->set('path', ['controller' => 'metaTemplates', 'action' => 'update']);
|
|
||||||
$this->render('/genericTemplates/confirm');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
$updateableTemplate = $this->MetaTemplates->checkForUpdates();
|
||||||
$this->CRUD->index([
|
$this->CRUD->index([
|
||||||
'filters' => $this->filterFields,
|
'filters' => $this->filterFields,
|
||||||
'quickFilters' => $this->quickFilterFields,
|
'quickFilters' => $this->quickFilterFields,
|
||||||
|
@ -64,15 +65,27 @@ class MetaTemplatesController extends AppController
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
'contain' => $this->containFields
|
'contain' => $this->containFields,
|
||||||
|
'afterFind' => function($data) use ($updateableTemplate) {
|
||||||
|
foreach ($data as $i => $metaTemplate) {
|
||||||
|
if (!empty($updateableTemplate[$metaTemplate->uuid])) {
|
||||||
|
$metaTemplate->set('status', $this->MetaTemplates->getTemplateStatus($updateableTemplate[$metaTemplate->uuid]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
$responsePayload = $this->CRUD->getResponsePayload();
|
$responsePayload = $this->CRUD->getResponsePayload();
|
||||||
if (!empty($responsePayload)) {
|
if (!empty($responsePayload)) {
|
||||||
return $responsePayload;
|
return $responsePayload;
|
||||||
}
|
}
|
||||||
|
$updateableTemplate = [
|
||||||
|
'not_up_to_date' => $this->MetaTemplates->getNotUpToDateTemplates(),
|
||||||
|
'new' => $this->MetaTemplates->getNewTemplates(),
|
||||||
|
];
|
||||||
$this->set('defaultTemplatePerScope', $this->MetaTemplates->getDefaultTemplatePerScope());
|
$this->set('defaultTemplatePerScope', $this->MetaTemplates->getDefaultTemplatePerScope());
|
||||||
$this->set('alignmentScope', 'individuals');
|
$this->set('alignmentScope', 'individuals');
|
||||||
$this->set('metaGroup', 'Administration');
|
$this->set('updateableTemplate', $updateableTemplate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function view($id)
|
public function view($id)
|
||||||
|
@ -84,7 +97,7 @@ class MetaTemplatesController extends AppController
|
||||||
if (!empty($responsePayload)) {
|
if (!empty($responsePayload)) {
|
||||||
return $responsePayload;
|
return $responsePayload;
|
||||||
}
|
}
|
||||||
$this->set('metaGroup', 'Administration');
|
$this->setUpdateStatus($id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toggle($id, $fieldName = 'enabled')
|
public function toggle($id, $fieldName = 'enabled')
|
||||||
|
@ -101,4 +114,13 @@ class MetaTemplatesController extends AppController
|
||||||
return $responsePayload;
|
return $responsePayload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setUpdateStatus($id)
|
||||||
|
{
|
||||||
|
$metaTemplate = $this->MetaTemplates->get($id);
|
||||||
|
$templateOnDisk = $this->MetaTemplates->readTemplateFromDisk($metaTemplate->uuid);
|
||||||
|
$updateableTemplate = $this->MetaTemplates->checkUpdatesForTemplate($templateOnDisk);
|
||||||
|
$this->set('updateableTemplate', $updateableTemplate);
|
||||||
|
$this->set('templateOnDisk', $templateOnDisk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Model\Entity;
|
||||||
|
|
||||||
|
use App\Model\Entity\AppModel;
|
||||||
|
use Cake\ORM\Entity;
|
||||||
|
|
||||||
|
class MetaTemplateField extends AppModel
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
|
@ -5,9 +5,15 @@ namespace App\Model\Table;
|
||||||
use App\Model\Table\AppTable;
|
use App\Model\Table\AppTable;
|
||||||
use Cake\ORM\Table;
|
use Cake\ORM\Table;
|
||||||
use Cake\Validation\Validator;
|
use Cake\Validation\Validator;
|
||||||
|
use Cake\Utility\Hash;
|
||||||
|
|
||||||
class MetaTemplatesTable extends AppTable
|
class MetaTemplatesTable extends AppTable
|
||||||
{
|
{
|
||||||
|
public const TEMPLATE_PATH = [
|
||||||
|
ROOT . '/libraries/default/meta_fields/',
|
||||||
|
ROOT . '/libraries/custom/meta_fields/'
|
||||||
|
];
|
||||||
|
|
||||||
public function initialize(array $config): void
|
public function initialize(array $config): void
|
||||||
{
|
{
|
||||||
parent::initialize($config);
|
parent::initialize($config);
|
||||||
|
@ -34,26 +40,196 @@ class MetaTemplatesTable extends AppTable
|
||||||
return $validator;
|
return $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update()
|
public function update(&$errors=[])
|
||||||
{
|
{
|
||||||
$paths = [
|
|
||||||
ROOT . '/libraries/default/meta_fields/',
|
|
||||||
ROOT . '/libraries/custom/meta_fields/'
|
|
||||||
];
|
|
||||||
$files_processed = [];
|
$files_processed = [];
|
||||||
foreach ($paths as $path) {
|
// foreach (self::TEMPLATE_PATH as $path) {
|
||||||
|
// if (is_dir($path)) {
|
||||||
|
// $files = scandir($path);
|
||||||
|
// foreach ($files as $k => $file) {
|
||||||
|
// if (substr($file, -5) === '.json') {
|
||||||
|
// if ($this->loadAndSaveMetaFile($path . $file) === true) {
|
||||||
|
// $files_processed[] = $file;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
$readErrors = [];
|
||||||
|
$preUpdateChecks = [];
|
||||||
|
$updatesErrors = [];
|
||||||
|
$templates = $this->readTemplatesFromDisk($readErrors);
|
||||||
|
foreach ($templates as $template) {
|
||||||
|
$preUpdateChecks[$template['uuid']] = $this->checkForUpdates($template);
|
||||||
|
}
|
||||||
|
$errors = [
|
||||||
|
'read_errors' => $readErrors,
|
||||||
|
'pre_update_errors' => $preUpdateChecks,
|
||||||
|
'update_errors' => $updatesErrors,
|
||||||
|
];
|
||||||
|
return $files_processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkForUpdates(): array
|
||||||
|
{
|
||||||
|
$templates = $this->readTemplatesFromDisk($readErrors);
|
||||||
|
$result = [];
|
||||||
|
foreach ($templates as $template) {
|
||||||
|
$result[$template['uuid']] = $this->checkUpdatesForTemplate($template);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUpToDate(array $updateResult): bool
|
||||||
|
{
|
||||||
|
return $updateResult['up-to-date'] || $updateResult['new'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isUpdateable(array $updateResult): bool
|
||||||
|
{
|
||||||
|
return $updateResult['updateable'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isNew(array $updateResult): bool
|
||||||
|
{
|
||||||
|
return $updateResult['new'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasNoConflict(array $updateResult): bool
|
||||||
|
{
|
||||||
|
return $this->hasConflict($updateResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasConflict(array $updateResult): bool
|
||||||
|
{
|
||||||
|
return !$updateResult['updateable'] && !$updateResult['up-to-date'] && !$updateResult['new'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTemplateStatus(array $updateResult): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'up_to_date' => $this->isUpToDate($updateResult),
|
||||||
|
'updateable' => $this->isUpdateable($updateResult),
|
||||||
|
'is_new' => $this->isNew($updateResult),
|
||||||
|
'has_conflict' => $this->hasConflict($updateResult),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpToDateTemplates($result=null): array
|
||||||
|
{
|
||||||
|
$result = is_null($result) ? $this->checkForUpdates() : $result;
|
||||||
|
foreach ($result as $i => $updateResult) {
|
||||||
|
if (!$this->isUpToDate($updateResult)) {
|
||||||
|
unset($result[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNotUpToDateTemplates($result=null): array
|
||||||
|
{
|
||||||
|
$result = is_null($result) ? $this->checkForUpdates() : $result;
|
||||||
|
foreach ($result as $i => $updateResult) {
|
||||||
|
if ($this->isUpToDate($updateResult)) {
|
||||||
|
unset($result[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUpdateableTemplates($result=null): array
|
||||||
|
{
|
||||||
|
$result = is_null($result) ? $this->checkForUpdates() : $result;
|
||||||
|
foreach ($result as $i => $updateResult) {
|
||||||
|
if (!$this->isUpdateable($updateResult)) {
|
||||||
|
unset($result[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNewTemplates($result=null): array
|
||||||
|
{
|
||||||
|
$result = is_null($result) ? $this->checkForUpdates() : $result;
|
||||||
|
foreach ($result as $i => $updateResult) {
|
||||||
|
if (!$this->isNew($updateResult)) {
|
||||||
|
unset($result[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConflictTemplates($result=null): array
|
||||||
|
{
|
||||||
|
$result = is_null($result) ? $this->checkForUpdates() : $result;
|
||||||
|
foreach ($result as $i => $updateResult) {
|
||||||
|
if (!$this->hasConflict($updateResult)) {
|
||||||
|
unset($result[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readTemplatesFromDisk(&$errors=[]): array
|
||||||
|
{
|
||||||
|
$templates = [];
|
||||||
|
$errors = [];
|
||||||
|
foreach (self::TEMPLATE_PATH as $path) {
|
||||||
if (is_dir($path)) {
|
if (is_dir($path)) {
|
||||||
$files = scandir($path);
|
$files = scandir($path);
|
||||||
foreach ($files as $k => $file) {
|
foreach ($files as $k => $file) {
|
||||||
if (substr($file, -5) === '.json') {
|
if (substr($file, -5) === '.json') {
|
||||||
if ($this->loadAndSaveMetaFile($path . $file) === true) {
|
$errorMessage = '';
|
||||||
$files_processed[] = $file;
|
$metaTemplate = $this->decodeTemplateFromDisk($path . $file, $errorMessage);
|
||||||
|
if (!empty($metaTemplate)) {
|
||||||
|
$templates[] = $metaTemplate;
|
||||||
|
} else {
|
||||||
|
$errors[] = $errorMessage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $files_processed;
|
return $templates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readTemplateFromDisk(string $uuid, &$error=''): ?array
|
||||||
|
{
|
||||||
|
foreach (self::TEMPLATE_PATH as $path) {
|
||||||
|
if (is_dir($path)) {
|
||||||
|
$files = scandir($path);
|
||||||
|
foreach ($files as $k => $file) {
|
||||||
|
if (substr($file, -5) === '.json') {
|
||||||
|
$errorMessage = '';
|
||||||
|
$metaTemplate = $this->decodeTemplateFromDisk($path . $file, $errorMessage);
|
||||||
|
if (!empty($metaTemplate) && $metaTemplate['uuid'] == $uuid) {
|
||||||
|
return $metaTemplate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$error = __('Could not find meta-template with UUID {0}', $uuid);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function decodeTemplateFromDisk(string $filePath, &$errorMessage=''): ?array
|
||||||
|
{
|
||||||
|
if (file_exists($filePath)) {
|
||||||
|
$explodedPath = explode('/', $filePath);
|
||||||
|
$filename = $explodedPath[count($explodedPath)-1];
|
||||||
|
$contents = file_get_contents($filePath);
|
||||||
|
$metaTemplate = json_decode($contents, true);
|
||||||
|
if (empty($metaTemplate)) {
|
||||||
|
$errorMessage = __('Could not load template file `{0}`. Error while decoding the template\'s JSON', $filename);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (empty($metaTemplate['uuid']) || empty($metaTemplate['version'])) {
|
||||||
|
$errorMessage = __('Could not load template file. Invalid template file. Missing template UUID or version');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return $metaTemplate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTemplate($id)
|
public function getTemplate($id)
|
||||||
|
@ -108,7 +284,7 @@ class MetaTemplatesTable extends AppTable
|
||||||
public function saveMetaFile(array $newMetaTemplate)
|
public function saveMetaFile(array $newMetaTemplate)
|
||||||
{
|
{
|
||||||
$query = $this->find();
|
$query = $this->find();
|
||||||
$query->contain('MetaTemaplteFields')->where(['uuid' => $newMetaTemplate['uuid']]);
|
$query->contain('MetaTemplateFields')->where(['uuid' => $newMetaTemplate['uuid']]);
|
||||||
$metaTemplate = $query->first();
|
$metaTemplate = $query->first();
|
||||||
if (empty($metaTemplate)) {
|
if (empty($metaTemplate)) {
|
||||||
$metaTemplate = $this->newEntity($newMetaTemplate);
|
$metaTemplate = $this->newEntity($newMetaTemplate);
|
||||||
|
@ -139,12 +315,84 @@ class MetaTemplatesTable extends AppTable
|
||||||
|
|
||||||
public function handleMetaTemplateFieldUpdateEdgeCase($metaTemplateField, $newMetaTemplateField)
|
public function handleMetaTemplateFieldUpdateEdgeCase($metaTemplateField, $newMetaTemplateField)
|
||||||
{
|
{
|
||||||
if (false) { // Field has been removed
|
}
|
||||||
|
|
||||||
|
public function checkForMetaFieldConflicts(\App\Model\Entity\MetaTemplateField $metaField, array $templateField): array
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'updateable' => true,
|
||||||
|
'conflicts' => [],
|
||||||
|
];
|
||||||
|
if ($metaField->multiple && $templateField['multiple'] == false) { // Field is no longer multiple
|
||||||
|
$result['updateable'] = false;
|
||||||
|
$result['conflicts'][] = __('This field is no longer multiple');
|
||||||
}
|
}
|
||||||
if (false) { // Field no longer multiple
|
if (!empty($templateField['regex']) && $templateField['regex'] != $metaField->regex) {
|
||||||
|
// FIXME: Check if all meta-fields pass the new validation
|
||||||
|
$result['updateable'] = false;
|
||||||
|
$result['conflicts'][] = __('This field is instantiated with values not passing the validation anymore');
|
||||||
}
|
}
|
||||||
if (false) { // Field no longer pass validation
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkForMetaTemplateConflicts(\App\Model\Entity\MetaTemplate $metaTemplate, array $template): array
|
||||||
|
{
|
||||||
|
$conflicts = [];
|
||||||
|
$existingMetaTemplateFields = Hash::combine($metaTemplate->toArray(), 'meta_template_fields.{n}.field');
|
||||||
|
foreach ($template['metaFields'] as $newMetaField) {
|
||||||
|
foreach ($metaTemplate->meta_template_fields as $metaField) {
|
||||||
|
if ($newMetaField['field'] == $metaField->field) {
|
||||||
|
unset($existingMetaTemplateFields[$metaField->field]);
|
||||||
|
$templateConflicts = $this->checkForMetaFieldConflicts($metaField, $newMetaField);
|
||||||
|
if (!$templateConflicts['updateable']) {
|
||||||
|
$conflicts[$metaField->field] = $templateConflicts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
if (!empty($existingMetaTemplateFields)) {
|
||||||
|
foreach ($existingMetaTemplateFields as $field => $tmp) {
|
||||||
|
$conflicts[$field] = [
|
||||||
|
'updateable' => false,
|
||||||
|
'conflicts' => [__('This field is intended to be removed')],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $conflicts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function checkUpdatesForTemplate($template): array
|
||||||
|
{
|
||||||
|
$result = [
|
||||||
|
'new' => true,
|
||||||
|
'up-to-date' => false,
|
||||||
|
'updateable' => false,
|
||||||
|
'conflicts' => [],
|
||||||
|
'template' => $template,
|
||||||
|
];
|
||||||
|
$query = $this->find()
|
||||||
|
->contain('MetaTemplateFields')->where([
|
||||||
|
'uuid' => $template['uuid']
|
||||||
|
]);
|
||||||
|
$metaTemplate = $query->first();
|
||||||
|
if (!empty($metaTemplate)) {
|
||||||
|
$result['existing_template'] = $metaTemplate;
|
||||||
|
$result['current_version'] = $metaTemplate->version;
|
||||||
|
$result['next_version'] = $template['version'];
|
||||||
|
$result['new'] = false;
|
||||||
|
if ($metaTemplate->version >= $template['version']) {
|
||||||
|
$result['up-to-date'] = true;
|
||||||
|
$result['updateable'] = false;
|
||||||
|
$result['conflicts'][] = __('Could not update the template. Local version is equal or newer.');
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
$conflicts = $this->checkForMetaTemplateConflicts($metaTemplate, $template);
|
||||||
|
if (!empty($conflicts)) {
|
||||||
|
$result['conflicts'] = $conflicts;
|
||||||
|
} else {
|
||||||
|
$result['updateable'] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,24 @@
|
||||||
<?php
|
<?php
|
||||||
|
use Cake\Utility\Hash;
|
||||||
|
|
||||||
|
if (!empty($updateableTemplate)) {
|
||||||
|
$alertHtml = sprintf('<strong>%s</strong> %s', __('New meta-templates available!'), __n('There is one new template on disk that can be loaded in the database', 'There are {0} new templates on disk that can be loaded in the database:', count($updateableTemplate['new'])));
|
||||||
|
$alertList = Hash::combine(
|
||||||
|
$updateableTemplate,
|
||||||
|
null,
|
||||||
|
['%s :: %s', 'new.{s}.template.namespace', 'new.{s}.template.name'],
|
||||||
|
'new.{n}.template.namespace'
|
||||||
|
);
|
||||||
|
$alertList = array_map(function($entry) {
|
||||||
|
return h($entry);
|
||||||
|
}, $alertList);
|
||||||
|
$alertHtml .= $this->Html->nestedList($alertList);
|
||||||
|
echo $this->Bootstrap->alert([
|
||||||
|
'html' => $alertHtml,
|
||||||
|
'variant' => 'warning',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
echo $this->element('genericElements/IndexTable/index_table', [
|
echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'data' => [
|
'data' => [
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
|
@ -138,7 +158,12 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'name' => __('UUID'),
|
'name' => __('UUID'),
|
||||||
'sort' => 'uuid',
|
'sort' => 'uuid',
|
||||||
'data_path' => 'uuid'
|
'data_path' => 'uuid'
|
||||||
]
|
],
|
||||||
|
[
|
||||||
|
'name' => __('Updateable'),
|
||||||
|
'data_path' => 'status',
|
||||||
|
'element' => 'update_status',
|
||||||
|
],
|
||||||
],
|
],
|
||||||
'title' => __('Meta Field Templates'),
|
'title' => __('Meta Field Templates'),
|
||||||
'description' => __('The various templates used to enrich certain objects by a set of standardised fields.'),
|
'description' => __('The various templates used to enrich certain objects by a set of standardised fields.'),
|
||||||
|
@ -150,11 +175,15 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
||||||
'icon' => 'eye'
|
'icon' => 'eye'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'url' => '/metaTemplates/update',
|
'open_modal' => '/metaTemplates/update/[onclick_params_data_path]',
|
||||||
'url_params_data_paths' => ['id'],
|
'modal_params_data_path' => 'id',
|
||||||
'title' => __('Update'),
|
'title' => __('Update Meta-Template'),
|
||||||
'icon' => 'download',
|
'icon' => 'download',
|
||||||
'requirement' => true // FIXME: Check if template can be updated
|
'complex_requirement' => [
|
||||||
|
'function' => function ($row, $options) {
|
||||||
|
return empty($row['status']['up_to_date']);
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$bodyHtml = '';
|
||||||
|
$modalType = 'confirm';
|
||||||
|
$modalSize = 'lg';
|
||||||
|
if ($updateableTemplate['up-to-date']) {
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'variant' => 'success',
|
||||||
|
'text' => __('This meta-template is already up-to-date!'),
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
$modalType = 'ok-only';
|
||||||
|
} else {
|
||||||
|
if ($updateableTemplate['updateable']) {
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'variant' => 'success',
|
||||||
|
'html' => __('This meta-template can be updated to version {0} (current: {1}).', sprintf('<strong>%s</strong>', h($templateOnDisk['version'])), h($metaTemplate->version)),
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$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'])),
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
$conflictTable = $this->element('MetaTemplates/conflictTable', [
|
||||||
|
'updateableTemplate' => $updateableTemplate,
|
||||||
|
'metaTemplate' => $metaTemplate,
|
||||||
|
'templateOnDisk' => $templateOnDisk,
|
||||||
|
]);
|
||||||
|
$bodyHtml .= $this->Bootstrap->collapse([
|
||||||
|
'title' => __('View conflicts'),
|
||||||
|
'open' => false
|
||||||
|
], $conflictTable);
|
||||||
|
$bodyHtml .= $this->element('MetaTemplates/conflictResolution', [
|
||||||
|
'updateableTemplate' => $updateableTemplate,
|
||||||
|
'metaTemplate' => $metaTemplate,
|
||||||
|
'templateOnDisk' => $templateOnDisk,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $this->Bootstrap->modal([
|
||||||
|
'title' => __('Update Meta Templates #{0} ?', h($metaTemplate->id)),
|
||||||
|
'bodyHtml' => $bodyHtml,
|
||||||
|
'size' => $modalSize,
|
||||||
|
'type' => $modalType,
|
||||||
|
'confirmText' => __('Update meta-templates'),
|
||||||
|
'confirmFunction' => 'updateMetaTemplate',
|
||||||
|
]);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// function updateMetaTemplate(idList, selectedData, $table) {
|
||||||
|
// const successCallback = function([data, modalObject]) {
|
||||||
|
// location.reload()
|
||||||
|
// }
|
||||||
|
// const failCallback = ([data, modalObject]) => {
|
||||||
|
// const tableData = selectedData.map(row => {
|
||||||
|
// entryInError = data.filter(error => error.data.id == row.id)[0]
|
||||||
|
// $faIcon = $('<i class="fa"></i>').addClass(entryInError.success ? 'fa-check text-success' : 'fa-times text-danger')
|
||||||
|
// return [row.id, row.first_name, row.last_name, row.email, entryInError.message, JSON.stringify(entryInError.errors), $faIcon]
|
||||||
|
// });
|
||||||
|
// handleMessageTable(
|
||||||
|
// modalObject.$modal,
|
||||||
|
// ['<?= __('ID') ?>', '<?= __('First name') ?>', '<?= __('Last name') ?>', '<?= __('email') ?>', '<?= __('Message') ?>', '<?= __('Error') ?>', '<?= __('State') ?>'],
|
||||||
|
// tableData
|
||||||
|
// )
|
||||||
|
// const $footer = $(modalObject.ajaxApi.statusNode).parent()
|
||||||
|
// modalObject.ajaxApi.statusNode.remove()
|
||||||
|
// const $cancelButton = $footer.find('button[data-bs-dismiss="modal"]')
|
||||||
|
// $cancelButton.text('<?= __('OK') ?>').removeClass('btn-secondary').addClass('btn-primary')
|
||||||
|
// }
|
||||||
|
// UI.submissionModal('[URL_HERE]', successCallback, failCallback).then(([modalObject, ajaxApi]) => {
|
||||||
|
// const $idsInput = modalObject.$modal.find('form').find('input#ids-field')
|
||||||
|
// $idsInput.val(JSON.stringify(idList))
|
||||||
|
// const tableData = selectedData.map(row => {
|
||||||
|
// return [row.id, row.first_name, row.last_name, row.email]
|
||||||
|
// });
|
||||||
|
// handleMessageTable(
|
||||||
|
// modalObject.$modal,
|
||||||
|
// ['<?= __('ID') ?>', '<?= __('First name') ?>', '<?= __('Last name') ?>', '<?= __('email') ?>'],
|
||||||
|
// tableData
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
|
||||||
|
// function constructMessageTable(header, data) {
|
||||||
|
// return HtmlHelper.table(
|
||||||
|
// header,
|
||||||
|
// data, {
|
||||||
|
// small: true,
|
||||||
|
// borderless: true,
|
||||||
|
// tableClass: ['message-table', 'mt-4 mb-0'],
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function handleMessageTable($modal, header, data) {
|
||||||
|
// const $modalBody = $modal.find('.modal-body')
|
||||||
|
// const $messageTable = $modalBody.find('table.message-table')
|
||||||
|
// const messageTableHTML = constructMessageTable(header, data)[0].outerHTML
|
||||||
|
// if ($messageTable.length) {
|
||||||
|
// $messageTable.html(messageTableHTML)
|
||||||
|
// } else {
|
||||||
|
// $modalBody.append(messageTableHTML)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
</script>
|
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
use Cake\Routing\Router;
|
||||||
|
|
||||||
|
$bodyHtml = '';
|
||||||
|
$modalType = 'confirm';
|
||||||
|
$modalSize = 'lg';
|
||||||
|
|
||||||
|
$tableHtml = '<table class="table"><thead><tr>';
|
||||||
|
$tableHtml .= sprintf('<th class="text-nowrap">%s</th>', __('Template'));
|
||||||
|
$tableHtml .= sprintf('<th class="text-nowrap">%s</th>', __('Version'));
|
||||||
|
$tableHtml .= sprintf('<th class="text-nowrap">%s</th>', __('New Template'));
|
||||||
|
$tableHtml .= sprintf('<th class="text-nowrap">%s</th>', __('Update available'));
|
||||||
|
$tableHtml .= sprintf('<th class="text-nowrap">%s</th>', __('Has Conflicts'));
|
||||||
|
$tableHtml .= sprintf('<th class="text-nowrap">%s</th>', __('Will be updated'));
|
||||||
|
$tableHtml .= '</tr></thead><tbody>';
|
||||||
|
$numberOfUpdates = 0;
|
||||||
|
$numberOfSkippedUpdates = 0;
|
||||||
|
foreach ($updateableTemplates as $uuid => $status) {
|
||||||
|
$tableHtml .= '<tr>';
|
||||||
|
if (!empty($status['new'])) {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', h($uuid));
|
||||||
|
} else {
|
||||||
|
$tableHtml .= sprintf('<td><a href="%s">%s</a></td>',
|
||||||
|
Router::url(['controller' => 'MetaTemplates', 'action' => 'view', 'plugin' => null, h($status['existing_template']->id)]),
|
||||||
|
h($status['existing_template']->name)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!empty($status['new'])) {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||||
|
} 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'])) {
|
||||||
|
$numberOfUpdates += 1;
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', $this->Bootstrap->icon('check'));
|
||||||
|
} else {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', $this->Bootstrap->icon('times'));
|
||||||
|
}
|
||||||
|
if (!empty($status['new'])) {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||||
|
} else {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', empty($status['up-to-date']) ? $this->Bootstrap->icon('check') : $this->Bootstrap->icon('times'));
|
||||||
|
}
|
||||||
|
if (!empty($status['new'])) {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', __('N/A'));
|
||||||
|
} else {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', !empty($status['conflicts']) ? $this->Bootstrap->icon('check') : $this->Bootstrap->icon('times'));
|
||||||
|
}
|
||||||
|
if (!empty($status['new'])) {
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', $this->Bootstrap->icon('check', ['class' => 'text-success']));
|
||||||
|
} else {
|
||||||
|
if (!empty($status['new']) || !empty($status['updateable'])) {
|
||||||
|
$numberOfUpdates += 1;
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', $this->Bootstrap->icon('check', ['class' => 'text-success']));
|
||||||
|
} else {
|
||||||
|
$numberOfSkippedUpdates += 1;
|
||||||
|
$tableHtml .= sprintf('<td>%s</td>', $this->Bootstrap->icon('times', ['class' => 'text-danger']));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tableHtml .= '</tr>';
|
||||||
|
}
|
||||||
|
$tableHtml .= '</tbody></table>';
|
||||||
|
|
||||||
|
if (empty($numberOfSkippedUpdates) && empty($numberOfUpdates)) {
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'variant' => 'success',
|
||||||
|
'text' => __('All meta-templates are already up-to-date!'),
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
$modalType = 'ok-only';
|
||||||
|
} elseif ($numberOfSkippedUpdates == 0) {
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'variant' => 'success',
|
||||||
|
'text' => __('All {0} meta-templates can be updated', $numberOfUpdates),
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$modalSize = 'xl';
|
||||||
|
$alertHtml = '';
|
||||||
|
if (!empty($numberOfUpdates)) {
|
||||||
|
$alertHtml .= sprintf('<div>%s</div>', __('{0} meta-templates can be updated.', sprintf('<strong>%s</strong>', $numberOfUpdates)));
|
||||||
|
}
|
||||||
|
if (!empty($numberOfSkippedUpdates)) {
|
||||||
|
$alertHtml .= sprintf('<div>%s</div>', __('{0} meta-templates will be skipped.', sprintf('<strong>%s</strong>', $numberOfSkippedUpdates)));
|
||||||
|
$alertHtml .= sprintf('<div>%s</div>', __('You can still choose the update strategy when updating each conflicting template manually.'));
|
||||||
|
}
|
||||||
|
$bodyHtml .= $this->Bootstrap->alert([
|
||||||
|
'variant' => 'warning',
|
||||||
|
'html' => $alertHtml,
|
||||||
|
'dismissible' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bodyHtml .= $tableHtml;
|
||||||
|
|
||||||
|
echo $this->Bootstrap->modal([
|
||||||
|
'title' => h($title),
|
||||||
|
'bodyHtml' => $bodyHtml,
|
||||||
|
'size' => $modalSize,
|
||||||
|
'type' => $modalType,
|
||||||
|
'confirmText' => __('Update meta-templates'),
|
||||||
|
'confirmFunction' => 'updateMetaTemplate',
|
||||||
|
]);
|
||||||
|
?>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<form>
|
||||||
|
<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">
|
||||||
|
<input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" checked>
|
||||||
|
<label class="btn btn-outline-primary mw-33" for="btnradio1">
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-3"><?= __('Keep both template') ?></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><?= __('Conflicts can be taken care of manually via the UI.') ?></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input type="radio" class="btn-check" name="btnradio" id="btnradio3" autocomplete="off">
|
||||||
|
<label class="btn btn-outline-danger mw-33" for="btnradio3">
|
||||||
|
<div>
|
||||||
|
<h5 class="mb-3"><?= __('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>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
|
@ -0,0 +1,29 @@
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col"><?= __('Field name') ?></th>
|
||||||
|
<th scope="col"><?= __('Conflict') ?></th>
|
||||||
|
<th scope="col"><?= __('Automatic Resolution') ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($updateableTemplate['conflicts'] as $fieldName => $fieldConflict) : ?>
|
||||||
|
<?php foreach ($fieldConflict['conflicts'] as $conflict) : ?>
|
||||||
|
<tr>
|
||||||
|
<th scope="row"><?= h($fieldName) ?></th>
|
||||||
|
<td>
|
||||||
|
<?= h($conflict) ?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
echo $this->Bootstrap->badge([
|
||||||
|
'text' => __('Affected meta-fields will be removed'),
|
||||||
|
'variant' => 'danger',
|
||||||
|
])
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?
|
||||||
|
if (empty($field['data_path']) || empty($row[$field['data_path']])) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$status = $row[$field['data_path']];
|
||||||
|
|
||||||
|
$icon = !empty($row['status']['updateable']) ? 'check' : 'times';
|
||||||
|
echo $this->Bootstrap->icon($icon);
|
||||||
|
?>
|
Loading…
Reference in New Issue