2020-09-28 01:25:07 +02:00
< ? php
namespace App\Model\Table ;
use App\Model\Table\AppTable ;
use Cake\ORM\Table ;
2021-12-08 11:11:46 +01:00
use Cake\ORM\TableRegistry ;
2020-09-28 01:25:07 +02:00
use Cake\Validation\Validator ;
2021-12-01 11:01:31 +01:00
use Cake\Utility\Hash ;
2021-12-08 11:11:46 +01:00
use Cake\Utility\Inflector ;
use Cake\Utility\Text ;
2021-12-14 15:09:40 +01:00
use Cake\Filesystem\File ;
use Cake\Filesystem\Folder ;
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/'
];
2021-12-14 15:09:40 +01:00
public const UPDATE_STRATEGY_CREATE_NEW = 'create_new' ;
public const UPDATE_STRATEGY_UPDATE_EXISTING = 'update_existing' ;
public const UPDATE_STRATEGY_KEEP_BOTH = 'keep_both' ;
public const UPDATE_STRATEGY_DELETE = 'delete_all' ;
2023-02-14 14:42:35 +01:00
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 ,
];
2021-12-14 15:09:40 +01:00
private $templatesOnDisk = null ;
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' ,
[
2021-12-08 11:11:46 +01:00
'foreignKey' => 'meta_template_id' ,
'saveStrategy' => 'replace' ,
'dependent' => true ,
'cascadeCallbacks' => true ,
2020-09-28 01:25:07 +02:00
]
);
2022-12-07 14:54:28 +01:00
$this -> hasOne ( 'MetaTemplateNameDirectory' )
-> setForeignKey ( 'meta_template_directory_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' )
2022-12-07 14:54:28 +01:00
-> requirePresence ([ 'scope' , 'source' , 'version' , 'uuid' , 'name' , 'namespace' , 'meta_template_directory_id' ], 'create' );
2020-09-28 01:25:07 +02:00
return $validator ;
}
2021-12-14 15:09:40 +01:00
public function isStrategyAllowed ( string $strategy ) : bool
{
2022-03-09 08:21:27 +01:00
return in_array ( $strategy , MetaTemplatesTable :: ALLOWED_STRATEGIES );
2021-12-14 15:09:40 +01:00
}
/**
* Load the templates stored on the disk update and create them in the database without touching at the existing ones
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return array The update result containing potential errors and the successes
*/
public function updateAllTemplates () : array
2023-02-14 14:42:35 +01:00
{
// 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
2020-09-28 01:25:07 +02:00
{
2021-12-01 11:01:31 +01:00
$updatesErrors = [];
2021-12-14 15:09:40 +01:00
$files_processed = [];
$templatesOnDisk = $this -> readTemplatesFromDisk ();
$templatesUpdateStatus = $this -> getUpdateStatusForTemplates ();
foreach ( $templatesOnDisk as $template ) {
$errors = [];
$success = false ;
$updateStatus = $templatesUpdateStatus [ $template [ 'uuid' ]];
if ( $this -> isStrategyAllowed ( MetaTemplatesTable :: UPDATE_STRATEGY_CREATE_NEW ) && $updateStatus [ 'new' ]) {
$success = $this -> saveNewMetaTemplate ( $template , $errors );
}
if ( $success ) {
$files_processed [] = $template [ 'uuid' ];
} else {
$updatesErrors [] = $errors ;
2021-12-08 11:11:46 +01:00
}
2021-12-01 11:01:31 +01:00
}
2021-12-08 11:11:46 +01:00
$results = [
2021-12-01 11:01:31 +01:00
'update_errors' => $updatesErrors ,
2021-12-08 11:11:46 +01:00
'files_processed' => $files_processed ,
'success' => ! empty ( $files_processed ),
2021-12-01 11:01:31 +01:00
];
2021-12-08 11:11:46 +01:00
return $results ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Load the template stored on the disk for the provided meta - template and update it using the optional strategy .
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ param string | null $strategy The strategy to be used when updating templates with conflicts
* @ return array The update result containing potential errors and the successes
*/
2023-02-14 14:42:35 +01:00
public function update ( \App\Model\Entity\MetaTemplate $metaTemplate , $strategy = null ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$files_processed = [];
$updatesErrors = [];
$templateOnDisk = $this -> readTemplateFromDisk ( $metaTemplate -> uuid );
$templateStatus = $this -> getStatusForMetaTemplate ( $templateOnDisk , $metaTemplate );
$updateStatus = $this -> computeFullUpdateStatusForMetaTemplate ( $templateStatus , $metaTemplate );
$errors = [];
2023-02-14 14:42:35 +01:00
$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 = [];
2021-12-14 15:09:40 +01:00
$success = false ;
if ( $updateStatus [ 'up-to-date' ]) {
$errors [ 'message' ] = __ ( 'Meta-template already up-to-date' );
$success = true ;
2022-03-08 15:51:07 +01:00
} else if ( $this -> isStrategyAllowed ( MetaTemplatesTable :: UPDATE_STRATEGY_CREATE_NEW ) && $updateStatus [ 'is-new' ]) {
2021-12-14 15:09:40 +01:00
$success = $this -> saveNewMetaTemplate ( $templateOnDisk , $errors );
} else if ( $this -> isStrategyAllowed ( MetaTemplatesTable :: UPDATE_STRATEGY_UPDATE_EXISTING ) && $updateStatus [ 'automatically-updateable' ]) {
$success = $this -> updateMetaTemplate ( $metaTemplate , $templateOnDisk , $errors );
2022-03-09 08:21:27 +01:00
} else if ( ! $updateStatus [ 'up-to-date' ] && ( ! is_null ( $strategy ) && ! $this -> isStrategyAllowed ( $strategy ))) {
$errors [ 'message' ] = __ ( 'Cannot update meta-template, update strategy not allowed' );
} else if ( ! $updateStatus [ 'up-to-date' ]) {
$strategy = is_null ( $strategy ) ? MetaTemplatesTable :: DEFAULT_STRATEGY : $strategy ;
2023-02-14 14:42:35 +01:00
if ( $strategy == MetaTemplatesTable :: UPDATE_STRATEGY_UPDATE_EXISTING && ! $updateStatus [ 'automatically-updateable' ]) {
$strategy = MetaTemplatesTable :: UPDATE_STRATEGY_KEEP_BOTH ;
}
2021-12-14 15:09:40 +01:00
$success = $this -> updateMetaTemplateWithStrategyRouter ( $metaTemplate , $templateOnDisk , $strategy , $errors );
} else {
$errors [ 'message' ] = __ ( 'Could not update. Something went wrong.' );
}
2023-02-14 14:42:35 +01:00
return [
'success' => $success ,
'errors' => $errors ,
2021-12-14 15:09:40 +01:00
];
}
2021-12-20 14:24:20 +01:00
/**
* Load the templates stored on the disk update and create the one having the provided UUID in the database
* Will do nothing if the UUID is already known
*
* @ param string $uuid
* @ return array The update result containing potential errors and the successes
*/
public function createNewTemplate ( string $uuid ) : array
{
$templateOnDisk = $this -> readTemplateFromDisk ( $uuid );
$templateStatus = $this -> getUpdateStatusForTemplate ( $templateOnDisk );
$errors = [];
$updatesErrors = [];
$files_processed = [];
$savedMetaTemplate = null ;
$success = false ;
if ( empty ( $templateStatus [ 'new' ])) {
$error [ 'message' ] = __ ( 'Template UUID already exists' );
$success = true ;
} else if ( $this -> isStrategyAllowed ( MetaTemplatesTable :: UPDATE_STRATEGY_CREATE_NEW )) {
$success = $this -> saveNewMetaTemplate ( $templateOnDisk , $errors , $savedMetaTemplate );
} else {
$errors [ 'message' ] = __ ( 'Could not create template. 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 $results ;
}
2021-12-14 15:09:40 +01:00
/**
* Load the templates stored on the disk and compute their update status .
* Only compute the result if an UUID is provided
*
* @ param string | null $template_uuid
* @ return array
*/
public function getUpdateStatusForTemplates () : array
{
$errors = [];
$templateUpdatesStatus = [];
$templates = $this -> readTemplatesFromDisk ( $errors );
2021-12-01 11:01:31 +01:00
foreach ( $templates as $template ) {
2021-12-14 15:09:40 +01:00
$templateUpdatesStatus [ $template [ 'uuid' ]] = $this -> getUpdateStatusForTemplate ( $template );
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
return $templateUpdatesStatus ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Checks if the template is update - to - date from the provided update status
*
* @ param array $updateStatus
* @ return boolean
*/
public function isUpToDate ( array $updateStatus ) : bool
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
return ! empty ( $updateStatus [ 'up-to-date' ]) || ! empty ( $updateStatus [ 'new' ]);
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Checks if the template is updateable automatically from the provided update status
*
* @ param array $updateStatus
* @ return boolean
*/
public function isAutomaticallyUpdateable ( array $updateStatus ) : bool
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
return ! empty ( $updateStatus [ 'automatically-updateable' ]);
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Checks if the template is new ( and not loaded in the database yet ) from the provided update status
*
* @ param array $updateStatus
* @ return boolean
*/
public function isNew ( array $updateStatus ) : bool
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
return $updateStatus [ 'new' ];
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Checks if the template has no conflicts that would prevent an automatic update from the provided update status
*
* @ param array $updateStatus
* @ return boolean
*/
public function hasNoConflict ( array $updateStatus ) : bool
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
return $this -> hasConflict ( $updateStatus );
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Checks if the template has conflict preventing an automatic update from the provided update status
*
* @ param array $updateStatus
* @ return boolean
*/
public function hasConflict ( array $updateStatus ) : bool
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
return empty ( $updateStatus [ 'automatically-updateable' ]) && empty ( $updateStatus [ 'up-to-date' ]) && empty ( $updateStatus [ 'new' ]);
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Checks if the metaTemplate can be updated to a newer version loaded in the database
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return boolean
*/
public function isUpdateableToExistingMetaTemplate ( \App\Model\Entity\MetaTemplate $metaTemplate ) : bool
2021-12-08 11:11:46 +01:00
{
$newestTemplate = $this -> getNewestVersion ( $metaTemplate );
return ! empty ( $newestTemplate );
}
2021-12-14 15:09:40 +01:00
/**
* Checks if the template can be removed from the database for the provided update status .
* A template can be removed if a newer version is already loaded in the database and no meta - fields are using it .
*
* @ param array $updateStatus
* @ return boolean
*/
public function isRemovable ( array $updateStatus ) : bool
2021-12-08 11:11:46 +01:00
{
2021-12-14 15:09:40 +01:00
return ! empty ( $updateStatus [ 'can-be-removed' ]);
2021-12-08 11:11:46 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Compute the state from the provided update status and metaTemplate
*
* @ param array $updateStatus
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return array
*/
public function computeFullUpdateStatusForMetaTemplate ( array $updateStatus , \App\Model\Entity\MetaTemplate $metaTemplate ) : array
2021-12-01 11:01:31 +01:00
{
return [
2021-12-14 15:09:40 +01:00
'up-to-date' => $this -> isUpToDate ( $updateStatus ),
'automatically-updateable' => $this -> isAutomaticallyUpdateable ( $updateStatus ),
'is-new' => $this -> isNew ( $updateStatus ),
'has-conflict' => $this -> hasConflict ( $updateStatus ),
'to-existing' => $this -> isUpdateableToExistingMetaTemplate ( $metaTemplate ),
'can-be-removed' => $this -> isRemovable ( $updateStatus ),
2021-12-01 11:01:31 +01:00
];
}
2021-12-14 15:09:40 +01:00
/**
* Get the update status of meta - templates that are up - to - date in regards to the template stored on the disk .
*
* @ param array | null $updateStatus
* @ return array The list of update status for up - to - date templates
*/
public function getUpToDateTemplates ( $updatesStatus = null ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$updatesStatus = is_null ( $updatesStatus ) ? $this -> getUpdateStatusForTemplates () : $updatesStatus ;
foreach ( $updatesStatus as $uuid => $updateStatus ) {
if ( ! $this -> isUpToDate ( $updateStatus )) {
unset ( $updatesStatus [ $uuid ]);
2021-12-01 11:01:31 +01:00
}
}
2021-12-14 15:09:40 +01:00
return $updatesStatus ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Get the update status of meta - templates that are not up - to - date in regards to the template stored on the disk .
*
* @ param array | null $updateResult
* @ return array The list of update status for non up - to - date templates
*/
public function getNotUpToDateTemplates ( $updatesStatus = null ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$updatesStatus = is_null ( $updatesStatus ) ? $this -> getUpdateStatusForTemplates () : $updatesStatus ;
foreach ( $updatesStatus as $uuid => $updateStatus ) {
if ( $this -> isUpToDate ( $updateStatus )) {
unset ( $updatesStatus [ $uuid ]);
2021-12-01 11:01:31 +01:00
}
}
2021-12-14 15:09:40 +01:00
return $updatesStatus ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Get the update status of meta - templates that are automatically updateable in regards to the template stored on the disk .
*
* @ param array | null $updateResult
* @ return array The list of update status for non up - to - date templates
*/
public function getAutomaticallyUpdateableTemplates ( $updatesStatus = null ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$updatesStatus = is_null ( $updatesStatus ) ? $this -> getUpdateStatusForTemplates () : $updatesStatus ;
foreach ( $updatesStatus as $uuid => $updateStatus ) {
if ( ! $this -> isAutomaticallyUpdateable ( $updateStatus )) {
unset ( $updatesStatus [ $uuid ]);
2021-12-01 11:01:31 +01:00
}
}
2021-12-14 15:09:40 +01:00
return $updatesStatus ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Get the update status of meta - templates that are new in regards to the template stored on the disk .
*
* @ param array | null $updateResult
* @ return array The list of update status for new templates
*/
public function getNewTemplates ( $updatesStatus = null ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$updatesStatus = is_null ( $updatesStatus ) ? $this -> getUpdateStatusForTemplates () : $updatesStatus ;
foreach ( $updatesStatus as $uuid => $updateStatus ) {
if ( ! $this -> isNew ( $updateStatus )) {
unset ( $updatesStatus [ $uuid ]);
2021-12-01 11:01:31 +01:00
}
}
2021-12-14 15:09:40 +01:00
return $updatesStatus ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Get the update status of meta - templates that have conflict preventing an automatic update in regards to the template stored on the disk .
*
* @ param array | null $updateResult
* @ return array The list of update status for new templates
*/
public function getConflictTemplates ( $updatesStatus = null ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$updatesStatus = is_null ( $updatesStatus ) ? $this -> getUpdateStatusForTemplates () : $updatesStatus ;
foreach ( $updatesStatus as $uuid => $updateStatus ) {
if ( ! $this -> hasConflict ( $updateStatus )) {
unset ( $updatesStatus [ $uuid ]);
2021-12-01 11:01:31 +01:00
}
}
2021-12-14 15:09:40 +01:00
return $updatesStatus ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Get the latest ( having the higher version ) meta - template loaded in the database for the provided meta - template
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ param boolean $full
* @ return \App\Model\Entity\MetaTemplate | null
*/
public function getNewestVersion ( \App\Model\Entity\MetaTemplate $metaTemplate , bool $full = false )
2021-12-08 11:11:46 +01:00
{
$query = $this -> find () -> where ([
'uuid' => $metaTemplate -> uuid ,
'id !=' => $metaTemplate -> id ,
'version >=' => $metaTemplate -> version ,
])
2021-12-14 15:09:40 +01:00
-> order ([ 'version' => 'DESC' ]);
2021-12-08 11:11:46 +01:00
if ( $full ) {
$query -> contain ([ 'MetaTemplateFields' ]);
}
$newestTemplate = $query -> first ();
return $newestTemplate ;
}
2021-12-14 15:09:40 +01:00
/**
* Generate and return a query ( to be used as a subquery ) resolving to the IDs of the latest version of a saved meta - template
*
* @ return \Cake\ORM\Query
*/
public function genQueryForAllNewestVersionIDs () : \Cake\ORM\Query
{
/**
* SELECT a . id FROM meta_templates a INNER JOIN (
* SELECT uuid , MAX ( version ) maxVersion FROM meta_templates GROUP BY uuid
* ) b on a . uuid = b . uuid AND a . version = b . maxVersion ;
*/
$query = $this -> find ()
-> select ([
'id'
])
-> join ([
't' => [
'table' => '(SELECT uuid, MAX(version) AS maxVersion FROM meta_templates GROUP BY uuid)' ,
'type' => 'INNER' ,
'conditions' => [
't.uuid = MetaTemplates.uuid' ,
't.maxVersion = MetaTemplates.version'
],
],
]);
return $query ;
}
/**
* Get the update status of meta - templates that can be removed .
*
* @ param array | null $updateResult
* @ return array The list of update status for new templates
*/
public function getCanBeRemovedTemplates ( $updatesStatus = null ) : array
2021-12-08 11:11:46 +01:00
{
2021-12-14 15:09:40 +01:00
$updatesStatus = is_null ( $updatesStatus ) ? $this -> getUpdateStatusForTemplates () : $updatesStatus ;
foreach ( $updatesStatus as $i => $updateStatus ) {
if ( ! $this -> isRemovable ( $updateStatus )) {
unset ( $updatesStatus [ $i ]);
2021-12-08 11:11:46 +01:00
}
}
2021-12-14 15:09:40 +01:00
return $updatesStatus ;
2021-12-08 11:11:46 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Reads all template stored on the disk and parse them
*
* @ param array | null $errors Contains errors while parsing the meta - templates
* @ return array The parsed meta - templates stored on the disk
*/
public function readTemplatesFromDisk ( & $errors = []) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
if ( ! is_null ( $this -> templatesOnDisk )) {
return $this -> templatesOnDisk ;
}
2021-12-01 11:01:31 +01:00
$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 = '' ;
2021-12-14 15:09:40 +01:00
$template = $this -> decodeTemplateFromDisk ( $path . $file , $errorMessage );
if ( ! empty ( $template )) {
$templates [] = $template ;
2021-12-01 11:01:31 +01:00
} else {
$errors [] = $errorMessage ;
2020-11-20 11:09:24 +01:00
}
2020-09-28 01:25:07 +02:00
}
}
}
}
2021-12-14 15:09:40 +01:00
$this -> templatesOnDisk = $templates ;
2021-12-01 11:01:31 +01:00
return $templates ;
}
2021-12-14 15:09:40 +01:00
/**
* Read and parse the meta - template stored on disk having the provided UUID
*
* @ param string $uuid
* @ param string $error Contains the error while parsing the meta - template
* @ return array | null The meta - template or null if not templates matche the provided UUID
*/
public function readTemplateFromDisk ( string $uuid , & $error = '' ) : ? array
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' ) {
$errorMessage = '' ;
2021-12-14 15:09:40 +01:00
$template = $this -> decodeTemplateFromDisk ( $path . $file , $errorMessage );
if ( ! empty ( $template ) && $template [ 'uuid' ] == $uuid ) {
return $template ;
2021-12-01 11:01:31 +01:00
}
}
}
}
}
$error = __ ( 'Could not find meta-template with UUID {0}' , $uuid );
return null ;
}
2021-12-14 15:09:40 +01:00
/**
* Read and decode the meta - template located at the provided path
*
* @ param string $filePath
* @ param string $errorMessage
* @ return array | null The meta - template or null if there was an error while trying to decode
*/
public function decodeTemplateFromDisk ( string $filePath , & $errorMessage = '' ) : ? array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$file = new File ( $filePath , false );
if ( $file -> exists ()) {
$filename = $file -> name ();
$content = $file -> read ();
if ( empty ( $content )) {
$errorMessage = __ ( 'Could not read template file `{0}`.' , $filename );
return null ;
}
$metaTemplate = json_decode ( $content , true );
2021-12-01 11:01:31 +01:00
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 ;
}
2021-12-14 15:09:40 +01:00
$errorMessage = __ ( 'File does not exists' );
return null ;
2020-09-28 01:25:07 +02:00
}
2021-12-14 15:09:40 +01:00
/**
* Collect all enties having meta - fields belonging to the provided template
*
* @ param integer $template_id
2022-01-20 12:00:39 +01:00
* @ param integer | bool $limit The limit of entities to be returned . Pass null to be ignore the limit
2021-12-14 15:09:40 +01:00
* @ return array List of entities
*/
2022-01-20 12:00:39 +01:00
public function getEntitiesHavingMetaFieldsFromTemplate ( int $metaTemplateId , $limit = 10 , int & $totalAmount = 0 ) : array
2021-12-08 11:11:46 +01:00
{
2021-12-14 15:09:40 +01:00
$metaTemplate = $this -> get ( $metaTemplateId );
2021-12-08 11:11:46 +01:00
$queryParentEntities = $this -> MetaTemplateFields -> MetaFields -> find ();
$queryParentEntities
-> select ([ 'parent_id' ])
-> where ([
2021-12-14 15:09:40 +01:00
'meta_template_id' => $metaTemplateId
2021-12-08 11:11:46 +01:00
])
-> group ([ 'parent_id' ]);
2021-12-14 15:09:40 +01:00
$entitiesTable = $this -> getTableForMetaTemplateScope ( $metaTemplate );
2021-12-08 11:11:46 +01:00
$entityQuery = $entitiesTable -> find ()
-> where ([ 'id IN' => $queryParentEntities ])
-> contain ([
'MetaFields' => [
'conditions' => [
2021-12-14 15:09:40 +01:00
'meta_template_id' => $metaTemplateId
2021-12-08 11:11:46 +01:00
]
]
]);
2022-01-20 12:00:39 +01:00
if ( ! is_null ( $limit )) {
$totalAmount = $entityQuery -> all () -> count ();
$entityQuery -> limit ( $limit );
}
2021-12-08 11:11:46 +01:00
$entities = $entityQuery -> all () -> toList ();
return $entities ;
}
2021-12-14 15:09:40 +01:00
/**
* Get the table linked to the meta - template
*
* @ param \App\Model\Entity\MetaTemplate | string $metaTemplate
* @ return \App\Model\Table\AppTable
*/
private function getTableForMetaTemplateScope ( $metaTemplateOrScope ) : \App\Model\Table\AppTable
{
if ( is_string ( $metaTemplateOrScope )) {
$scope = $metaTemplateOrScope ;
} else {
$scope = $metaTemplateOrScope -> scope ;
}
$entitiesClassName = Inflector :: camelize ( Inflector :: pluralize ( $scope ));
$entitiesTable = TableRegistry :: getTableLocator () -> get ( $entitiesClassName );
return $entitiesTable ;
}
/**
* Get the meta - field keyed by their template_id and meta_template_id belonging to the provided entity
*
* @ param integer $entity_id The entity for which the meta - fields belongs to
* @ param array $conditions Additional conditions to be passed to the meta - fields query
* @ return array The associated array containing the meta - fields keyed by their meta - template and meta - template - field IDs
*/
public function getKeyedMetaFieldsForEntity ( int $entity_id , array $conditions = []) : array
2021-12-08 11:11:46 +01:00
{
$query = $this -> MetaTemplateFields -> MetaFields -> find ();
$query -> where ( array_merge (
$conditions ,
[
'MetaFields.parent_id' => $entity_id
]
));
$metaFields = $query -> all ();
2021-12-14 15:09:40 +01:00
$keyedMetaFields = [];
2021-12-08 11:11:46 +01:00
foreach ( $metaFields as $metaField ) {
2021-12-14 15:09:40 +01:00
if ( empty ( $keyedMetaFields [ $metaField -> meta_template_id ][ $metaField -> meta_template_field_id ])) {
$keyedMetaFields [ $metaField -> meta_template_id ][ $metaField -> meta_template_field_id ] = [];
2021-12-08 11:11:46 +01:00
}
2021-12-14 15:09:40 +01:00
$keyedMetaFields [ $metaField -> meta_template_id ][ $metaField -> meta_template_field_id ][ $metaField -> id ] = $metaField ;
2021-12-08 11:11:46 +01:00
}
2021-12-14 15:09:40 +01:00
return $keyedMetaFields ;
2021-12-08 11:11:46 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Insert the keyed meta - fields into the provided meta - templates
*
* @ param array $keyedMetaFields An associative array containing the meta - fields keyed by their meta - template and meta - template - field IDs
* @ param array $metaTemplates List of meta - templates
* @ return array The list of meta - template with the meta - fields inserted
*/
public function insertMetaFieldsInMetaTemplates ( array $keyedMetaFields , array $metaTemplates ) : array
2021-12-08 11:11:46 +01:00
{
$merged = [];
foreach ( $metaTemplates as $metaTemplate ) {
$metaTemplate [ 'meta_template_fields' ] = Hash :: combine ( $metaTemplate [ 'meta_template_fields' ], '{n}.id' , '{n}' );
$merged [ $metaTemplate -> id ] = $metaTemplate ;
if ( isset ( $keyedMetaFields [ $metaTemplate -> id ])) {
foreach ( $metaTemplate -> meta_template_fields as $j => $meta_template_field ) {
if ( isset ( $keyedMetaFields [ $metaTemplate -> id ][ $meta_template_field -> id ])) {
$merged [ $metaTemplate -> id ] -> meta_template_fields [ $j ][ 'metaFields' ] = $keyedMetaFields [ $metaTemplate -> id ][ $meta_template_field -> id ];
} else {
$merged [ $metaTemplate -> id ] -> meta_template_fields [ $j ][ 'metaFields' ] = [];
}
}
}
}
return $merged ;
}
2021-12-14 15:09:40 +01:00
/**
* Retreive the entity associated for the provided meta - template and id
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ param integer $entity_id
* @ return \App\Model\Entity\AppModel
*/
public function getEntity ( \App\Model\Entity\MetaTemplate $metaTemplate , int $entity_id ) : \App\Model\Entity\AppModel
2021-12-08 11:11:46 +01:00
{
2021-12-14 15:09:40 +01:00
$entitiesTable = $this -> getTableForMetaTemplateScope ( $metaTemplate );
$entity = $entitiesTable -> get ( $entity_id , [
2021-12-08 11:11:46 +01:00
'contain' => 'MetaFields'
]);
return $entity ;
}
2021-12-14 15:09:40 +01:00
/**
* Collect the unique default template for each scope
*
* @ param string | null $scope
* @ return array The list of default template
*/
public function getDefaultTemplatePerScope ( $scope = null ) : array
2020-12-10 16:50:46 +01:00
{
$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 ();
}
2021-12-14 15:09:40 +01:00
/**
* Remove the default flag for all meta - templates belonging to the provided scope
*
* @ param string $scope
* @ return int the number of updated rows
*/
public function removeDefaultFlag ( string $scope ) : int
2020-12-10 17:18:17 +01:00
{
2021-12-14 15:09:40 +01:00
return $this -> updateAll (
2020-12-10 17:18:17 +01:00
[ 'is_default' => false ],
[ 'scope' => $scope ]
);
}
2021-12-14 15:09:40 +01:00
/**
* Check if the provided template can be saved in the database without creating duplicate template in regards to the UUID and version
*
* @ param array $template
* @ return boolean
*/
public function canBeSavedWithoutDuplicates ( array $template ) : bool
{
$query = $this -> find () -> where ([
'uuid' => $template [ 'uuid' ],
'version' => $template [ 'version' ],
]);
return $query -> count () == 0 ;
}
/**
* Create and save the provided template in the database
*
* @ param array $template The template to be saved
* @ param array $errors The list of errors that occured during the save process
* @ param \App\Model\Entity\MetaTemplate $savedMetaTemplate The metaTemplate entity that has just been saved
* @ return boolean True if the save was successful , False otherwise
*/
public function saveNewMetaTemplate ( array $template , array & $errors = [], \App\Model\Entity\MetaTemplate & $savedMetaTemplate = null ) : bool
2020-09-28 01:25:07 +02:00
{
2021-12-14 15:09:40 +01:00
if ( ! $this -> canBeSavedWithoutDuplicates ( $template )) {
$errors [] = new UpdateError ( false , __ ( 'Could not save the template. A template with this UUID and version already exists' ), [ 'A template with UUID and version already exists' ]);
}
2021-12-08 11:11:46 +01:00
$template [ 'meta_template_fields' ] = $template [ 'metaFields' ];
unset ( $template [ 'metaFields' ]);
$metaTemplate = $this -> newEntity ( $template , [
'associated' => [ 'MetaTemplateFields' ]
]);
2022-12-07 14:54:28 +01:00
$metaTemplateDirectory = $this -> MetaTemplateNameDirectory -> createFromMetaTemplate ( $metaTemplate );
$metaTemplate -> meta_template_directory_id = $metaTemplateDirectory -> id ;
2021-12-08 11:11:46 +01:00
$tmp = $this -> save ( $metaTemplate , [
'associated' => [ 'MetaTemplateFields' ]
]);
2021-12-20 14:24:20 +01:00
if ( $tmp === false ) {
2021-12-14 15:09:40 +01:00
$errors [] = new UpdateError ( false , __ ( 'Could not save the template.' ), $metaTemplate -> getErrors ());
2021-12-20 14:24:20 +01:00
return false ;
2021-11-24 09:14:09 +01:00
}
2021-12-08 11:11:46 +01:00
$savedMetaTemplate = $tmp ;
2021-12-20 14:24:20 +01:00
return true ;
2021-11-24 09:14:09 +01:00
}
2020-09-28 01:25:07 +02:00
2021-12-14 15:09:40 +01:00
/**
* Update an existing meta - template and save it in the database
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate The meta - template to update
* @ param array $template The template to use to update the existing meta - template
* @ param array $errors
* @ return boolean True if the save was successful , False otherwise
*/
public function updateMetaTemplate ( \App\Model\Entity\MetaTemplate $metaTemplate , array $template , array & $errors = []) : bool
2021-11-24 09:14:09 +01:00
{
2021-12-14 15:09:40 +01:00
if ( ! $this -> canBeSavedWithoutDuplicates ( $template )) {
$errors [] = new UpdateError ( false , __ ( 'Could not save the template. A template with this UUID and version already exists' ), [ 'A template with UUID and version already exists' ]);
}
2021-12-08 11:11:46 +01:00
if ( is_string ( $metaTemplate )) {
$errors [] = new UpdateError ( false , $metaTemplate );
return false ;
}
2023-02-14 14:42:35 +01:00
$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 );
2021-12-08 11:11:46 +01:00
$metaTemplate = $this -> save ( $metaTemplate , [
'associated' => [ 'MetaTemplateFields' ]
]);
2023-02-14 14:42:35 +01:00
foreach ( $metaTemplate -> meta_template_fields as $savedMetafield ) {
if ( empty ( $savedMetafield [ '__patched' ])) {
$this -> MetaTemplateFields -> delete ( $savedMetafield );
}
}
if ( empty ( $metaTemplate )) {
2021-12-08 11:11:46 +01:00
$errors [] = new UpdateError ( false , __ ( 'Could not save the template.' ), $metaTemplate -> getErrors ());
return false ;
}
return true ;
}
2021-12-14 15:09:40 +01:00
/**
* Update an existing meta - template with the provided strategy and save it in the database
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate The meta - template to update
* @ param array $template The template to use to update the existing meta - template
* @ param string $strategy The strategy to use when handling update conflicts
* @ param array $errors
* @ return boolean True if the save was successful , False otherwise
*/
public function updateMetaTemplateWithStrategyRouter ( \App\Model\Entity\MetaTemplate $metaTemplate , array $template , string $strategy , array & $errors = []) : bool
2021-12-08 11:11:46 +01:00
{
2021-12-14 15:09:40 +01:00
if ( ! $this -> canBeSavedWithoutDuplicates ( $template )) {
$errors [] = new UpdateError ( false , __ ( 'Could not save the template. A template with this UUID and version already exists' ), [ 'A template with UUID and version already exists' ]);
}
2021-12-08 11:11:46 +01:00
if ( is_string ( $metaTemplate )) {
$errors [] = new UpdateError ( false , $metaTemplate );
return false ;
}
2021-12-14 15:09:40 +01:00
if ( $strategy == MetaTemplatesTable :: UPDATE_STRATEGY_KEEP_BOTH ) {
2021-12-08 11:11:46 +01:00
$result = $this -> executeStrategyKeep ( $template , $metaTemplate );
2023-02-14 14:42:35 +01:00
} else if ( $strategy == MetaTemplatesTable :: UPDATE_STRATEGY_UPDATE_EXISTING ) {
$result = $this -> updateMetaTemplate ( $metaTemplate , $template );
2021-12-14 15:09:40 +01:00
} else if ( $strategy == MetaTemplatesTable :: UPDATE_STRATEGY_CREATE_NEW ) {
$result = $this -> executeStrategyCreateNew ( $template , $metaTemplate );
2021-11-24 09:14:09 +01:00
} else {
2021-12-14 15:09:40 +01:00
$errors [] = new UpdateError ( false , __ ( 'Invalid strategy {0}' , $strategy ));
return false ;
2021-12-08 11:11:46 +01:00
}
if ( is_string ( $result )) {
2021-12-14 15:09:40 +01:00
$errors [] = new UpdateError ( false , $result );
return false ;
2021-12-08 11:11:46 +01:00
}
return true ;
}
2021-12-14 15:09:40 +01:00
/**
* Execute the `keep_both` update strategy by creating a new meta - template and moving non - conflicting entities to this one .
* Strategy :
* - Old template remains untouched
* - Create new template
* - Migrate all non - conflicting meta - fields for one entity to the new template
* - Keep all the conflicting meta - fields for one entity on the old template
*
* @ param array $template
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return bool | string If the new template could be saved or the error message
*/
2021-12-08 11:11:46 +01:00
public function executeStrategyKeep ( array $template , \App\Model\Entity\MetaTemplate $metaTemplate )
{
2021-12-14 15:09:40 +01:00
if ( ! $this -> canBeSavedWithoutDuplicates ( $template )) {
$errors [] = new UpdateError ( false , __ ( 'Could not save the template. A template with this UUID and version already exists' ), [ 'A template with UUID and version already exists' ]);
}
$conflicts = $this -> getMetaTemplateConflictsForMetaTemplate ( $metaTemplate , $template );
2023-02-14 14:42:35 +01:00
$conflictingEntities = Hash :: combine ( $conflicts , '{s}.conflictingEntities.{n}.parent_id' , '{s}.conflictingEntities.{n}.parent_id' );
2021-12-08 11:11:46 +01:00
$blockingConflict = Hash :: extract ( $conflicts , '{s}.conflicts' );
$errors = [];
if ( empty ( $blockingConflict )) { // No conflict, everything can be updated without special care
2021-12-14 15:09:40 +01:00
$this -> updateMetaTemplate ( $metaTemplate , $template , $errors );
2021-12-08 11:11:46 +01:00
return ! empty ( $errors ) ? $errors [ 0 ] : true ;
}
2022-01-20 12:00:39 +01:00
$entities = $this -> getEntitiesHavingMetaFieldsFromTemplate ( $metaTemplate -> id , null );
2021-12-08 11:11:46 +01:00
foreach ( $entities as $entity ) {
2021-12-14 15:09:40 +01:00
$conflicts = $this -> getMetaFieldsConflictsUnderTemplate ( $entity [ 'meta_fields' ], $template );
2021-12-08 11:11:46 +01:00
if ( ! empty ( $conflicts )) {
$conflictingEntities [ $entity -> id ] = $entity -> id ;
2021-11-24 09:14:09 +01:00
}
2021-12-08 11:11:46 +01:00
}
if ( empty ( $conflictingEntities )) {
2021-12-14 15:09:40 +01:00
$this -> updateMetaTemplate ( $metaTemplate , $template , $errors );
2021-12-08 11:11:46 +01:00
return ! empty ( $errors ) ? $errors [ 0 ] : true ;
}
2023-02-14 14:42:35 +01:00
$template [ 'is_default' ] = $metaTemplate -> is_default ;
$template [ 'enabled' ] = $metaTemplate -> enabled ;
$metaTemplate -> set ( 'enabled' , false );
2021-12-08 11:11:46 +01:00
if ( $metaTemplate -> is_default ) {
$metaTemplate -> set ( 'is_default' , false );
$this -> save ( $metaTemplate );
}
2021-12-14 15:09:40 +01:00
$savedMetaTemplate = null ;
$this -> saveNewMetaTemplate ( $template , $errors , $savedMetaTemplate );
if ( ! empty ( $savedMetaTemplate )) {
2021-12-08 11:11:46 +01:00
foreach ( $entities as $entity ) {
2021-12-14 15:09:40 +01:00
if ( empty ( $conflictingEntities [ $entity -> id ])) { // conflicting entities remain untouched
2023-02-14 14:42:35 +01:00
$this -> supersedeMetaFieldsWithMetaTemplateField ( $entity [ 'meta_fields' ], $savedMetaTemplate );
2021-12-08 11:11:46 +01:00
}
2021-11-24 09:14:09 +01:00
}
2021-12-08 11:11:46 +01:00
} else {
return $errors [ 0 ] -> message ;
}
return true ;
}
2023-02-14 14:42:35 +01:00
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 ;
}
2021-12-14 15:09:40 +01:00
/**
* Execute the `delete_all` update strategy by updating the meta - template and deleting all conflicting meta - fields .
* Strategy :
* - Delete conflicting meta - fields
* - Update template to the new version
*
* @ param array $template
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return bool | string If the new template could be saved or the error message
*/
public function executeStrategyDeleteAll ( array $template , \App\Model\Entity\MetaTemplate $metaTemplate )
2021-12-08 11:11:46 +01:00
{
2021-12-14 15:09:40 +01:00
if ( ! $this -> canBeSavedWithoutDuplicates ( $template )) {
$errors [] = new UpdateError ( false , __ ( 'Could not save the template. A template with this UUID and version already exists' ), [ 'A template with UUID and version already exists' ]);
}
2021-12-08 11:11:46 +01:00
$errors = [];
2021-12-14 15:09:40 +01:00
$conflicts = $this -> getMetaTemplateConflictsForMetaTemplate ( $metaTemplate , $template );
2021-12-08 11:11:46 +01:00
$blockingConflict = Hash :: extract ( $conflicts , '{s}.conflicts' );
if ( empty ( $blockingConflict )) { // No conflict, everything can be updated without special care
2021-12-14 15:09:40 +01:00
$this -> updateMetaTemplate ( $metaTemplate , $template , $errors );
2021-12-08 11:11:46 +01:00
return ! empty ( $errors ) ? $errors [ 0 ] : true ;
2021-11-24 09:14:09 +01:00
}
2022-01-20 12:00:39 +01:00
$entities = $this -> getEntitiesHavingMetaFieldsFromTemplate ( $metaTemplate -> id , null );
2021-12-08 11:11:46 +01:00
foreach ( $entities as $entity ) {
2021-12-14 15:09:40 +01:00
$conflicts = $this -> getMetaFieldsConflictsUnderTemplate ( $entity [ 'meta_fields' ], $template );
$deletedCount = $this -> MetaTemplateFields -> MetaFields -> deleteAll ([
2021-12-08 11:11:46 +01:00
'id IN' => $conflicts
]);
}
2021-12-14 15:09:40 +01:00
$this -> updateMetaTemplate ( $metaTemplate , $template , $errors );
2021-12-08 11:11:46 +01:00
return ! empty ( $errors ) ? $errors [ 0 ] : true ;
}
2021-12-14 15:09:40 +01:00
/**
* Execute the `create_new` update strategy by creating a new meta - template
* Strategy :
* - Create a new meta - template
* - Make the new meta - template `default` and `enabled` if previous template had these states
* - Turn of these states on the old meta - template
*
* @ param array $template
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return bool | string If the new template could be saved or the error message
*/
public function executeStrategyCreateNew ( array $template , \App\Model\Entity\MetaTemplate $metaTemplate )
{
if ( ! $this -> canBeSavedWithoutDuplicates ( $template )) {
$errors [] = new UpdateError ( false , __ ( 'Could not save the template. A template with this UUID and version already exists' ), [ 'A template with UUID and version already exists' ]);
}
$errors = [];
$template [ 'is_default' ] = $metaTemplate -> is_default ;
$template [ 'enabled' ] = $metaTemplate -> enabled ;
$savedMetaTemplate = null ;
$success = $this -> saveNewMetaTemplate ( $template , $errors , $savedMetaTemplate );
if ( $success ) {
if ( $metaTemplate -> is_default ) {
$metaTemplate -> set ( 'is_default' , false );
$metaTemplate -> set ( 'enabled' , false );
$this -> save ( $metaTemplate );
}
}
return ! empty ( $errors ) ? $errors [ 0 ] : true ;
}
/**
* Supersede a meta - fields ' s meta - template - field with the provided one .
*
2023-02-14 14:42:35 +01:00
* @ param array $metaFields
2021-12-14 15:09:40 +01:00
* @ param \App\Model\Entity\MetaTemplateField $savedMetaTemplateField
* @ return bool True if the replacement was a success , False otherwise
*/
2023-02-14 14:42:35 +01:00
public function supersedeMetaFieldsWithMetaTemplateField ( array $metaFields , \App\Model\Entity\MetaTemplate $savedMetaTemplate ) : bool
2021-12-08 11:11:46 +01:00
{
2023-02-14 14:42:35 +01:00
$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 );
}
$entities = $this -> MetaTemplateFields -> MetaFields -> saveMany ( $metaFields );
return ! empty ( $entities );
2021-12-08 11:11:46 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Compute the validity of the provided meta - fields under the provided meta - template
*
* @ param \App\Model\Entity\MetaField [] $metaFields
* @ param array | \App\Model\Entity\MetaTemplate $template
* @ return \App\Model\Entity\MetaField [] The list of conflicting meta - fields under the provided template
*/
public function getMetaFieldsConflictsUnderTemplate ( array $metaFields , $template ) : array
2021-12-08 11:11:46 +01:00
{
2022-01-20 12:00:39 +01:00
if ( ! is_array ( $template ) && get_class ( $template ) == 'App\Model\Entity\MetaTemplate' ) {
$metaTemplateFields = $template -> meta_template_fields ;
$existingMetaTemplate = true ;
} else {
$metaTemplateFields = $template [ 'metaFields' ];
}
2021-12-08 11:11:46 +01:00
$conflicting = [];
$metaTemplateFieldByName = [];
2021-12-14 15:09:40 +01:00
foreach ( $metaTemplateFields as $metaTemplateField ) {
2022-01-20 12:00:39 +01:00
if ( ! is_array ( $template )) {
$metaTemplateField = $metaTemplateField -> toArray ();
}
2021-12-14 15:09:40 +01:00
$metaTemplateFieldByName [ $metaTemplateField [ 'field' ]] = $this -> MetaTemplateFields -> newEntity ( $metaTemplateField );
2021-12-08 11:11:46 +01:00
}
foreach ( $metaFields as $metaField ) {
2023-02-14 14:42:35 +01:00
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 ]
);
2021-12-08 11:11:46 +01:00
}
if ( $isValid !== true ) {
$conflicting [] = $metaField ;
}
}
return $conflicting ;
2021-12-01 11:01:31 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Compute the potential conflict that would be introduced by updating an existing meta - template - field with the provided one .
2022-01-20 12:00:39 +01:00
* This will go through all instanciation of the existing meta - template - field and checking their validity against the provided one .
2021-12-14 15:09:40 +01:00
*
* @ param \App\Model\Entity\MetaTemplateField $metaTemplateField
* @ param array $templateField
* @ return array
*/
2022-03-01 11:32:30 +01:00
public function computeExistingMetaTemplateFieldConflictForMetaTemplateField ( \App\Model\Entity\MetaTemplateField $metaTemplateField , array $templateField , string $scope ) : array
2021-12-01 11:01:31 +01:00
{
$result = [
2021-12-14 15:09:40 +01:00
'automatically-updateable' => true ,
2021-12-01 11:01:31 +01:00
'conflicts' => [],
2021-12-08 11:11:46 +01:00
'conflictingEntities' => [],
2021-12-01 11:01:31 +01:00
];
2021-12-08 11:11:46 +01:00
if ( $metaTemplateField -> multiple && $templateField [ 'multiple' ] == false ) { // Field is no longer multiple
$query = $this -> MetaTemplateFields -> MetaFields -> find ();
$query
-> enableHydration ( false )
-> select ([
'parent_id' ,
'meta_template_field_id' ,
'count' => $query -> func () -> count ( 'meta_template_field_id' ),
])
-> where ([
'meta_template_field_id' => $metaTemplateField -> id ,
])
-> group ([ 'parent_id' ])
-> having ([ 'count >' => 1 ]);
$conflictingStatus = $query -> all () -> toList ();
if ( ! empty ( $conflictingStatus )) {
2021-12-14 15:09:40 +01:00
$result [ 'automatically-updateable' ] = false ;
$result [ 'conflicts' ][] = __ ( 'This field is no longer multiple and is being that way' );
2021-12-08 11:11:46 +01:00
$result [ 'conflictingEntities' ] = Hash :: extract ( $conflictingStatus , '{n}.parent_id' );
}
2021-12-01 11:01:31 +01:00
}
2023-02-14 14:42:35 +01:00
if (
( ! empty ( $templateField [ 'regex' ]) && $templateField [ 'regex' ] != $metaTemplateField -> regex ) ||
! empty ( $templateField [ 'values_list' ])
) {
$entities = $this -> getEntitiesForMetaTemplateField ( $scope , $metaTemplateField -> id , true );
2022-03-01 11:32:30 +01:00
$conflictingEntities = [];
foreach ( $entities as $entity ) {
foreach ( $entity [ 'meta_fields' ] as $metaField ) {
$isValid = $this -> MetaTemplateFields -> MetaFields -> isValidMetaFieldForMetaTemplateField (
$metaField -> value ,
$templateField
);
if ( $isValid !== true ) {
2023-02-14 14:42:35 +01:00
$conflictingEntities [] = [
'parent_id' => $entity -> id ,
'meta_template_field_id' => $metaTemplateField -> id ,
];
2022-03-01 11:32:30 +01:00
break ;
2021-12-08 11:11:46 +01:00
}
}
2022-03-01 11:32:30 +01:00
}
2021-12-08 11:11:46 +01:00
2022-03-01 11:32:30 +01:00
if ( ! empty ( $conflictingEntities )) {
$result [ 'automatically-updateable' ] = $result [ 'automatically-updateable' ] && false ;
2023-02-14 14:42:35 +01:00
$result [ 'conflicts' ][] = __ ( 'This field is instantiated with values not passing the validation anymore.' );
2022-03-01 11:32:30 +01:00
$result [ 'conflictingEntities' ] = array_merge ( $result [ 'conflictingEntities' ], $conflictingEntities );
2021-12-08 11:11:46 +01:00
}
2021-12-01 11:01:31 +01:00
}
return $result ;
}
2023-02-14 14:42:35 +01:00
/**
* 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 ();
}
2021-12-14 15:09:40 +01:00
/**
* Check the conflict that would be introduced if the metaTemplate would be updated to the provided template
*
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ param \App\Model\Entity\MetaTemplate | array $template
* @ return array
*/
public function getMetaTemplateConflictsForMetaTemplate ( \App\Model\Entity\MetaTemplate $metaTemplate , $template ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-08 11:11:46 +01:00
$templateMetaFields = [];
if ( ! is_array ( $template ) && get_class ( $template ) == 'App\Model\Entity\MetaTemplate' ) {
$templateMetaFields = $template -> meta_template_fields ;
} else {
$templateMetaFields = $template [ 'metaFields' ];
}
2021-12-01 11:01:31 +01:00
$conflicts = [];
2023-02-14 14:42:35 +01:00
$existingMetaTemplateFields = Hash :: combine ( $metaTemplate -> toArray (), 'meta_template_fields.{n}.field' , 'meta_template_fields.{n}' );
2021-12-08 11:11:46 +01:00
foreach ( $templateMetaFields as $newMetaField ) {
2021-12-01 11:01:31 +01:00
foreach ( $metaTemplate -> meta_template_fields as $metaField ) {
if ( $newMetaField [ 'field' ] == $metaField -> field ) {
unset ( $existingMetaTemplateFields [ $metaField -> field ]);
2021-12-14 15:09:40 +01:00
$metaFieldArray = ! is_array ( $newMetaField ) && get_class ( $newMetaField ) == 'App\Model\Entity\MetaTemplateField' ? $newMetaField -> toArray () : $newMetaField ;
2022-03-01 11:32:30 +01:00
$templateConflictsForMetaField = $this -> computeExistingMetaTemplateFieldConflictForMetaTemplateField ( $metaField , $metaFieldArray , $metaTemplate -> scope );
2021-12-14 15:09:40 +01:00
if ( ! $templateConflictsForMetaField [ 'automatically-updateable' ]) {
$conflicts [ $metaField -> field ] = $templateConflictsForMetaField ;
$conflicts [ $metaField -> field ][ 'existing_meta_template_field' ] = $metaField ;
$conflicts [ $metaField -> field ][ 'existing_meta_template_field' ][ 'conflicts' ] = $templateConflictsForMetaField [ 'conflicts' ];
}
2021-12-01 11:01:31 +01:00
}
}
2021-11-24 09:14:09 +01:00
}
2021-12-01 11:01:31 +01:00
if ( ! empty ( $existingMetaTemplateFields )) {
2023-02-14 14:42:35 +01:00
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' ]] = [
2021-12-14 15:09:40 +01:00
'automatically-updateable' => false ,
2021-12-01 11:01:31 +01:00
'conflicts' => [ __ ( 'This field is intended to be removed' )],
2023-02-14 14:42:35 +01:00
'conflictingEntities' => $entityWithMetafieldToBeRemoved ,
2021-12-01 11:01:31 +01:00
];
}
2021-11-24 09:14:09 +01:00
}
2021-12-01 11:01:31 +01:00
return $conflicts ;
}
2021-12-14 15:09:40 +01:00
/**
* Get update status for the latest meta - template in the database for the provided template
*
* @ param array $template
* @ param \App\Model\Entity\MetaTemplate $metaTemplate $metaTemplate
* @ return array
*/
public function getUpdateStatusForTemplate ( array $template ) : array
2021-12-01 11:01:31 +01:00
{
2021-12-14 15:09:40 +01:00
$updateStatus = [
2021-12-01 11:01:31 +01:00
'new' => true ,
'up-to-date' => false ,
2021-12-14 15:09:40 +01:00
'automatically-updateable' => false ,
2021-12-01 11:01:31 +01:00
'conflicts' => [],
'template' => $template ,
];
2021-12-14 15:09:40 +01:00
$query = $this -> find ()
-> contain ( 'MetaTemplateFields' )
-> where ([
'uuid' => $template [ 'uuid' ],
])
-> order ([ 'version' => 'DESC' ]);
$metaTemplate = $query -> first ();
2021-12-01 11:01:31 +01:00
if ( ! empty ( $metaTemplate )) {
2021-12-14 15:09:40 +01:00
$updateStatus = array_merge (
$updateStatus ,
$this -> getStatusForMetaTemplate ( $template , $metaTemplate )
);
2021-11-24 09:14:09 +01:00
}
2021-12-14 15:09:40 +01:00
return $updateStatus ;
2020-09-28 01:25:07 +02:00
}
2021-12-08 11:11:46 +01:00
2021-12-14 15:09:40 +01:00
/**
* Get update status for the meta - template stored in the database and the provided template
*
* @ param array $template
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return array
*/
public function getStatusForMetaTemplate ( array $template , \App\Model\Entity\MetaTemplate $metaTemplate ) : array
2021-12-08 11:11:46 +01:00
{
2021-12-14 15:09:40 +01:00
$updateStatus = [];
$updateStatus [ 'existing_template' ] = $metaTemplate ;
$updateStatus [ 'current_version' ] = $metaTemplate -> version ;
$updateStatus [ 'next_version' ] = $template [ 'version' ];
$updateStatus [ 'new' ] = false ;
2023-02-14 14:42:35 +01:00
$updateStatus [ 'automatically-updateable' ] = false ;
2021-12-14 15:09:40 +01:00
if ( $metaTemplate -> version >= $template [ 'version' ]) {
$updateStatus [ 'up-to-date' ] = true ;
$updateStatus [ 'conflicts' ][] = __ ( 'Could not update the template. Local version is equal or newer.' );
return $updateStatus ;
}
$conflicts = $this -> getMetaTemplateConflictsForMetaTemplate ( $metaTemplate , $template );
if ( ! empty ( $conflicts )) {
$updateStatus [ 'conflicts' ] = $conflicts ;
2023-02-14 14:42:35 +01:00
$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 ;
}
2021-12-14 15:09:40 +01:00
} else {
$updateStatus [ 'automatically-updateable' ] = true ;
}
$updateStatus [ 'meta_field_amount' ] = $this -> MetaTemplateFields -> MetaFields -> find () -> where ([ 'meta_template_id' => $metaTemplate -> id ]) -> count ();
$updateStatus [ 'can-be-removed' ] = empty ( $updateStatus [ 'meta_field_amount' ]) && empty ( $updateStatus [ 'to-existing' ]);
return $updateStatus ;
2021-12-08 11:11:46 +01:00
}
2021-12-14 15:09:40 +01:00
/**
* Massages the meta - fields of an entity based on the input
* - If the keyed ID of the input meta - field is new , a new meta - field entity is created
* - If the input meta - field ' s value is empty for an existing meta - field , the existing meta - field is marked as to be deleted
* - If the input meta - field already exists , patch the entity and attach the validation errors
*
* @ param \App\Model\Entity\AppModel $entity
* @ param array $input
* @ param \App\Model\Entity\MetaTemplate $metaTemplate
* @ return array An array containing the entity with its massaged meta - fields and the meta - fields that should be deleted
*/
public function massageMetaFieldsBeforeSave ( \App\Model\Entity\AppModel $entity , array $input , \App\Model\Entity\MetaTemplate $metaTemplate ) : array
2021-12-08 11:11:46 +01:00
{
$metaFieldsTable = $this -> MetaTemplateFields -> MetaFields ;
$className = Inflector :: camelize ( Inflector :: pluralize ( $metaTemplate -> scope ));
$entityTable = TableRegistry :: getTableLocator () -> get ( $className );
$metaFieldsIndex = [];
if ( ! empty ( $entity -> meta_fields )) {
foreach ( $entity -> meta_fields as $i => $metaField ) {
$metaFieldsIndex [ $metaField -> id ] = $i ;
}
} else {
$entity -> meta_fields = [];
}
$metaFieldsToDelete = [];
foreach ( $input [ 'MetaTemplates' ] as $template_id => $template ) {
foreach ( $template [ 'meta_template_fields' ] as $meta_template_field_id => $meta_template_field ) {
$rawMetaTemplateField = $metaTemplate -> meta_template_fields [ $meta_template_field_id ];
foreach ( $meta_template_field [ 'metaFields' ] as $meta_field_id => $meta_field ) {
if ( $meta_field_id == 'new' ) { // create new meta_field
$new_meta_fields = $meta_field ;
foreach ( $new_meta_fields as $new_value ) {
if ( ! empty ( $new_value )) {
$metaField = $metaFieldsTable -> newEmptyEntity ();
$metaFieldsTable -> patchEntity ( $metaField , [
'value' => $new_value ,
'scope' => $entityTable -> getBehavior ( 'MetaFields' ) -> getScope (),
'field' => $rawMetaTemplateField -> field ,
'meta_template_id' => $rawMetaTemplateField -> meta_template_id ,
'meta_template_field_id' => $rawMetaTemplateField -> id ,
'parent_id' => $entity -> id ,
'uuid' => Text :: uuid (),
]);
$entity -> meta_fields [] = $metaField ;
$entity -> MetaTemplates [ $template_id ] -> meta_template_fields [ $meta_template_field_id ] -> metaFields [] = $metaField ;
}
}
} else {
$new_value = $meta_field [ 'value' ];
if ( ! empty ( $new_value )) { // update meta_field and attach validation errors
if ( ! empty ( $metaFieldsIndex [ $meta_field_id ])) {
$index = $metaFieldsIndex [ $meta_field_id ];
$metaFieldsTable -> patchEntity ( $entity -> meta_fields [ $index ], [
'value' => $new_value , 'meta_template_field_id' => $rawMetaTemplateField -> id
], [ 'value' ]);
$metaFieldsTable -> patchEntity (
$entity -> MetaTemplates [ $template_id ] -> meta_template_fields [ $meta_template_field_id ] -> metaFields [ $meta_field_id ],
[ 'value' => $new_value , 'meta_template_field_id' => $rawMetaTemplateField -> id ],
[ 'value' ]
);
2021-12-14 15:09:40 +01:00
} else { // metafield comes from a second POST where the temporary entity has already been created
2021-12-08 11:11:46 +01:00
$metaField = $metaFieldsTable -> newEmptyEntity ();
$metaFieldsTable -> patchEntity ( $metaField , [
'value' => $new_value ,
'scope' => $entityTable -> getBehavior ( 'MetaFields' ) -> getScope (), // get scope from behavior
'field' => $rawMetaTemplateField -> field ,
'meta_template_id' => $rawMetaTemplateField -> meta_template_id ,
'meta_template_field_id' => $rawMetaTemplateField -> id ,
'parent_id' => $entity -> id ,
'uuid' => Text :: uuid (),
]);
$entity -> meta_fields [] = $metaField ;
$entity -> MetaTemplates [ $template_id ] -> meta_template_fields [ $meta_template_field_id ] -> metaFields [] = $metaField ;
}
} else { // Metafield value is empty, indicating the field should be removed
$index = $metaFieldsIndex [ $meta_field_id ];
$metaFieldsToDelete [] = $entity -> meta_fields [ $index ];
unset ( $entity -> meta_fields [ $index ]);
unset ( $entity -> MetaTemplates [ $template_id ] -> meta_template_fields [ $meta_template_field_id ] -> metaFields [ $meta_field_id ]);
}
}
}
}
}
$entity -> setDirty ( 'meta_fields' , true );
return [ 'entity' => $entity , 'metafields_to_delete' => $metaFieldsToDelete ];
}
2020-09-28 01:25:07 +02:00
}
2021-12-08 11:11:46 +01:00
2021-12-14 15:09:40 +01:00
class UpdateError
2021-12-08 11:11:46 +01:00
{
public $success ;
public $message = '' ;
public $errors = [];
2021-12-14 15:09:40 +01:00
public function __construct ( $success = false , $message = '' , $errors = [])
2021-12-08 11:11:46 +01:00
{
$this -> success = $success ;
$this -> message = $message ;
$this -> errors = $errors ;
}
2021-12-14 15:09:40 +01:00
}