2020-09-28 01:25:07 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Model\Table;
|
|
|
|
|
|
|
|
use App\Model\Table\AppTable;
|
|
|
|
use Cake\ORM\Table;
|
|
|
|
use Cake\Validation\Validator;
|
2021-12-01 11:01:31 +01:00
|
|
|
use Cake\Utility\Hash;
|
2020-09-28 01:25:07 +02:00
|
|
|
|
|
|
|
class MetaTemplatesTable extends AppTable
|
|
|
|
{
|
2021-12-01 11:01:31 +01:00
|
|
|
public const TEMPLATE_PATH = [
|
|
|
|
ROOT . '/libraries/default/meta_fields/',
|
|
|
|
ROOT . '/libraries/custom/meta_fields/'
|
|
|
|
];
|
|
|
|
|
2020-09-28 01:25:07 +02:00
|
|
|
public function initialize(array $config): void
|
|
|
|
{
|
|
|
|
parent::initialize($config);
|
2021-09-28 13:32:51 +02:00
|
|
|
$this->addBehavior('Timestamp');
|
2020-09-28 01:25:07 +02:00
|
|
|
$this->hasMany(
|
|
|
|
'MetaTemplateFields',
|
|
|
|
[
|
|
|
|
'foreignKey' => 'meta_template_id'
|
|
|
|
]
|
|
|
|
);
|
2021-09-09 13:12:52 +02:00
|
|
|
$this->setDisplayField('name');
|
2020-09-28 01:25:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public function validationDefault(Validator $validator): Validator
|
|
|
|
{
|
|
|
|
$validator
|
|
|
|
->notEmptyString('scope')
|
|
|
|
->notEmptyString('name')
|
|
|
|
->notEmptyString('namespace')
|
|
|
|
->notEmptyString('uuid')
|
|
|
|
->notEmptyString('version')
|
|
|
|
->notEmptyString('source')
|
|
|
|
->requirePresence(['scope', 'source', 'version', 'uuid', 'name', 'namespace'], 'create');
|
|
|
|
return $validator;
|
|
|
|
}
|
|
|
|
|
2021-12-01 11:01:31 +01:00
|
|
|
public function update(&$errors=[])
|
2020-09-28 01:25:07 +02:00
|
|
|
{
|
|
|
|
$files_processed = [];
|
2021-12-01 11:01:31 +01:00
|
|
|
// 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) {
|
2020-09-28 01:25:07 +02:00
|
|
|
if (is_dir($path)) {
|
|
|
|
$files = scandir($path);
|
|
|
|
foreach ($files as $k => $file) {
|
|
|
|
if (substr($file, -5) === '.json') {
|
2021-12-01 11:01:31 +01:00
|
|
|
$errorMessage = '';
|
|
|
|
$metaTemplate = $this->decodeTemplateFromDisk($path . $file, $errorMessage);
|
|
|
|
if (!empty($metaTemplate)) {
|
|
|
|
$templates[] = $metaTemplate;
|
|
|
|
} else {
|
|
|
|
$errors[] = $errorMessage;
|
2020-11-20 11:09:24 +01:00
|
|
|
}
|
2020-09-28 01:25:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-01 11:01:31 +01:00
|
|
|
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;
|
|
|
|
}
|
2020-09-28 01:25:07 +02:00
|
|
|
}
|
|
|
|
|
2020-11-20 11:09:24 +01:00
|
|
|
public function getTemplate($id)
|
|
|
|
{
|
|
|
|
$query = $this->find();
|
|
|
|
$query->where(['id' => $id]);
|
|
|
|
$template = $query->first();
|
|
|
|
if (empty($template)) {
|
|
|
|
throw new NotFoundException(__('Invalid template ID specified.'));
|
|
|
|
}
|
|
|
|
return $template;
|
|
|
|
}
|
|
|
|
|
2020-12-10 16:50:46 +01:00
|
|
|
public function getDefaultTemplatePerScope(String $scope = '')
|
|
|
|
{
|
|
|
|
$query = $this->find('list', [
|
|
|
|
'keyField' => 'scope',
|
|
|
|
'valueField' => function ($template) {
|
|
|
|
return $template;
|
|
|
|
}
|
|
|
|
])->where(['is_default' => true]);
|
|
|
|
if (!empty($scope)) {
|
|
|
|
$query->where(['scope' => $scope]);
|
|
|
|
}
|
|
|
|
return $query->all()->toArray();
|
|
|
|
}
|
|
|
|
|
2020-12-10 17:18:17 +01:00
|
|
|
public function removeDefaultFlag(String $scope)
|
|
|
|
{
|
|
|
|
$this->updateAll(
|
|
|
|
['is_default' => false],
|
|
|
|
['scope' => $scope]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-24 09:14:09 +01:00
|
|
|
public function loadAndSaveMetaFile(String $filePath)
|
2020-09-28 01:25:07 +02:00
|
|
|
{
|
|
|
|
if (file_exists($filePath)) {
|
|
|
|
$contents = file_get_contents($filePath);
|
|
|
|
$metaTemplate = json_decode($contents, true);
|
2021-11-24 09:14:09 +01:00
|
|
|
if (empty($metaTemplate)) {
|
|
|
|
return __('Could not load template file. Error while decoding the template\'s JSON');
|
|
|
|
}
|
|
|
|
if (empty($metaTemplate['uuid']) || empty($metaTemplate['version'])) {
|
|
|
|
return __('Could not load template file. Invalid template file. Missing template UUID or version');
|
|
|
|
}
|
|
|
|
return $this->saveMetaFile($metaTemplate);
|
|
|
|
}
|
|
|
|
return __('Could not load template file. File does not exists');
|
|
|
|
}
|
2020-09-28 01:25:07 +02:00
|
|
|
|
2021-11-24 09:14:09 +01:00
|
|
|
public function saveMetaFile(array $newMetaTemplate)
|
|
|
|
{
|
|
|
|
$query = $this->find();
|
2021-12-01 11:01:31 +01:00
|
|
|
$query->contain('MetaTemplateFields')->where(['uuid' => $newMetaTemplate['uuid']]);
|
2021-11-24 09:14:09 +01:00
|
|
|
$metaTemplate = $query->first();
|
|
|
|
if (empty($metaTemplate)) {
|
|
|
|
$metaTemplate = $this->newEntity($newMetaTemplate);
|
|
|
|
$result = $this->save($metaTemplate);
|
|
|
|
if (!$result) {
|
|
|
|
return __('Something went wrong, could not create the template.');
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ($metaTemplate->version >= $newMetaTemplate['version']) {
|
|
|
|
return __('Could not update the template. Local version is newer.');
|
|
|
|
}
|
|
|
|
// Take care of meta template fields
|
|
|
|
$metaTemplate = $this->patchEntity($metaTemplate, $newMetaTemplate);
|
|
|
|
$metaTemplate = $this->save($metaTemplate);
|
|
|
|
if (!$metaTemplate) {
|
|
|
|
return __('Something went wrong, could not update the template.');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($result) {
|
|
|
|
$this->MetaTemplateFields->deleteAll(['meta_template_id' => $template->id]);
|
|
|
|
foreach ($newMetaTemplate['metaFields'] as $metaField) {
|
|
|
|
$metaField['meta_template_id'] = $template->id;
|
|
|
|
$metaField = $this->MetaTemplateFields->newEntity($metaField);
|
|
|
|
$this->MetaTemplateFields->save($metaField);
|
2020-09-28 01:25:07 +02:00
|
|
|
}
|
|
|
|
}
|
2021-11-24 09:14:09 +01:00
|
|
|
}
|
2020-09-28 01:25:07 +02:00
|
|
|
|
2021-11-24 09:14:09 +01:00
|
|
|
public function handleMetaTemplateFieldUpdateEdgeCase($metaTemplateField, $newMetaTemplateField)
|
|
|
|
{
|
2021-12-01 11:01:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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 (!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');
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-11-24 09:14:09 +01:00
|
|
|
}
|
2021-12-01 11:01:31 +01:00
|
|
|
if (!empty($existingMetaTemplateFields)) {
|
|
|
|
foreach ($existingMetaTemplateFields as $field => $tmp) {
|
|
|
|
$conflicts[$field] = [
|
|
|
|
'updateable' => false,
|
|
|
|
'conflicts' => [__('This field is intended to be removed')],
|
|
|
|
];
|
|
|
|
}
|
2021-11-24 09:14:09 +01:00
|
|
|
}
|
2021-12-01 11:01:31 +01:00
|
|
|
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;
|
|
|
|
}
|
2021-11-24 09:14:09 +01:00
|
|
|
}
|
2021-12-01 11:01:31 +01:00
|
|
|
return $result;
|
2020-09-28 01:25:07 +02:00
|
|
|
}
|
|
|
|
}
|