Seditio Source
Root |
./othercms/croogo-4.0.7/vendor/croogo/croogo/Core/src/Model/Behavior/CopyableBehavior.php
<?php

namespace Croogo\Core\Model\Behavior;

use
Cake\Datasource\EntityInterface;
use
Cake\Log\Log;
use
Cake\ORM\Behavior;
use
Cake\ORM\Entity;
use
Cake\ORM\Table;
use
Cake\Utility\Hash;
use
Croogo\Core\Croogo;
use
PDOException;

/**
 * Copyable Behavior class file.
 *
 * Adds ability to copy a model record, including all hasMany and
 * hasAndBelongsToMany associations. Relies on Containable behavior, which
 * this behavior will attach on the fly as needed.
 *
 * HABTM relationships are just duplicated in the join table, while hasMany
 * and hasOne records are recursively copied as well.
 *
 * Usage is straightforward:
 * From model: $this->copy($id); // id = the id of the record to be copied
 * From container: $this->MyModel->copy($id);
 *
 * @category Behavior
 * @package Croogo.Croogo.Model.Behavior
 * @author Jamie Nay
 * @copyright Jamie Nay
 * @license     http://www.opensource.org/licenses/mit-license.php The MIT License
 * @link http://github.com/jamienay/copyable_behavior
 * @link http://www.croogo.org
 */
class CopyableBehavior extends Behavior
{

   
/**
     * Behavior settings
     */
   
public $settings = [];

   
/**
     * Array of contained models.
     */
   
public $contain = [];

   
/**
     * The full results of Model::find() that are modified and saved
     * as a new copy.
     */
   
public $record;

   
/**
     * Default values for settings.
     *
     * - recursive: whether to copy hasMany and hasOne records
     * - habtm: whether to copy hasAndBelongsToMany associations
     * - stripFields: fields to strip during copy process
     * - ignore: aliases of any associations that should be ignored, using dot (.) notation.
     * will look in the $this->contain array.
     */
   
protected $_defaults = [
       
'recursive' => false,
       
'habtm' => false,
       
'autoFields' => [
           
'title',
           
'slug',
           
'alias',
        ],
       
'stripFields' => [
           
'id',
           
'created',
           
'modified',
           
'updated',
           
'lft',
           
'status',
           
'rght'
       
],
       
'ignore' => [
        ],
       
'masterKey' => null
   
];

   
/**
     * Holds the value of the original id
     */
   
protected $_originalId;

   
/**
     * Constructor
     */
   
public function __construct(Table $table, array $config = [])
    {
       
$config = Hash::merge($this->_defaults, $config);
       
parent::__construct($table, $config);
    }

   
/**
     * Copy method.
     *
     * @param Table $id model object
     * @return bool
     */
   
public function copy($id)
    {
       
$this->_originalId = $id;
       
$table = $this->_table;
       
$this->contain = $this->generateContain();
       
$this->record = $table->find()->where([
               
$table->aliasField($table->getPrimaryKey()) => $id
           
])
            ->
contain($this->contain)
            ->
first();

        if (empty(
$this->record)) {
            return
false;
        }

        if (!
$this->_convertData()) {
            return
false;
        }

       
$result = false;
        try {
           
$result = $this->_table->save($this->record, [
               
'associated' => true,
            ]);
        } catch (
PDOException $e) {
           
Log:error('Error executing _copyRecord: ' . $e->getMessage());
        }

        return
$result;
    }

   
/**
     * Wrapper method that combines the results of _recursiveChildContain()
     * with the models' HABTM associations.
     *
     * @return array
     */
   
public function generateContain()
    {
       
$contain = [];
       
$table = $this->_table;
       
$belongsToMany = $table->associations()->getByType('BelongsToMany');
        foreach (
$belongsToMany as $assoc) {
           
$contain[$assoc->junction()->getAlias()] = [];
        }
       
$contain = array_merge($this->_recursiveChildContain($table), $contain);
       
$contain = $this->_removeIgnored($contain);

        return
$contain;
    }

   
/**
     * Removes any ignored associations, as defined in the model settings, from
     * the $this->contain array.
     *
     * @param array $contain data
     * @return bool
     */
   
protected function _removeIgnored($contain)
    {
       
$ignore = array_unique($this->getConfig('ignore'));
        if (!
$ignore) {
            return
$contain;
        }
        foreach (
$ignore as $path) {
           
$contain = Hash::remove($contain, $path);
        }

        return
$contain;
    }

   
/**
     * Strips primary keys and other unwanted fields
     * from hasOne and hasMany records.
     *
     * @param object $table model object
     * @param array $record
     * @return array $record
     */
   
protected function _convertChildren(Table $table, Entity $record)
    {
       
$assocs = $table->associations();
       
$children = array_merge($assocs->getByType('HasMany'), $assocs->getByType('HasOne'));
        foreach (
$children as $key => $val) {
           
$property = $val->getProperty();
            if (!
$record->has($property)) {
                continue;
            }
           
$child = $record->{$property};

            if (
is_array($child)) {
                foreach (
$child as $innerKey => $innerVal) {
                   
$child[$innerKey] = $this->_stripFields($innerVal);

                   
$foreignKey = $val->getForeignKey();
                    if (
$innerVal->has($foreignKey)) {
                       
$innerVal->unsetProperty($foreignKey);
                    }

                   
$child[$innerKey] = $this->_convertChildren($val->getTarget(), $child[$innerKey]);
                }
            } elseif (
$child instanceof EntityInterface) {
               
$child = $this->_stripFields($child);

               
$foreignKey = $val->getForeignKey();
                if (
$child->has($foreignKey)) {
                   
$child->unsetProperty($foreignKey);
                }

               
$child = $this->_convertChildren($val->getTarget(), $child);
            }
           
$record->{$property} = $child;
        }

        return
$record;
    }

   
/**
     * Strips primary and parent foreign keys (where applicable)
     * from $this->record in preparation for saving.
     *
     * When `autoFields` is set, it will iterate listed fields and append
     * ' (copy)' for titles or '-copy' for slug/alias fields.
     *
     * Plugins can also perform custom/additional data conversion by listening
     * on `Behavior.Copyable.convertData`
     *
     * @return \Cake\ORM\Entity $this->record
     */
   
protected function _convertData()
    {
       
$this->record = clone $this->record;
       
$this->_stripFields($this->record);

       
$this->record = $this->_convertHabtm($this->_table, $this->record);
       
$this->record = $this->_convertChildren($this->_table, $this->record);

       
$autoFields = (array)$this->getConfig('autoFields');
       
$slugFields = ['slug', 'alias'];
        foreach (
$autoFields as $field) {
            if (!
$this->record->has($field)) {
                continue;
            }
            if (
in_array($field, $slugFields)) {
               
$this->record->{$field} .= '-copy';
            } else {
               
$this->record->{$field} .= ' (copy)';
            }
        }

       
$eventName = 'Behavior.Copyable.convertData';
       
$event = Croogo::dispatchEvent($eventName, $this->_table, [
           
'record' => $this->record,
        ]);

       
$this->record = $event->getData('record');

        return
$this->record;
    }

   
/**
     * Loops through any HABTM results in $this->record and plucks out
     * the join table info, stripping out the join table primary
     * key and the primary key of $Model. This is done instead of
     * a simple collection of IDs of the associated records, since
     * HABTM join tables may contain extra information (sorting
     * order, etc).
     *
     * @param \Cake\ORM\Table $table Table object
     * @param \Cake\ORM\Entity $record
     * @return \Cake\ORM\Entity Modified $record
     */
   
protected function _convertHabtm(Table $table, $record)
    {
        if (!
$this->getConfig('habtm')) {
            return
$record;
        }

       
$belongsToMany = $table->associations()->getByType('BelongsToMany');
        foreach (
$belongsToMany as $key => $val) {
           
// retrieve the reverse association
           
$hasMany = $val->getTarget()->association($val->junction()->getAlias());
           
$property = $hasMany->getProperty();

            if (!
$record->has($property) || empty($record->{$property})) {
                continue;
            }

            foreach (
$record->{$property} as $joinKey => $joinVal) {
               
$joinVal = $this->_stripFields($joinVal);
               
$foreignKey = $val->getForeignKey();
                if (
$joinVal->has($foreignKey)) {
                   
$joinVal->unsetProperty($foreignKey);
                }
            }
        }

        return
$record;
    }

   
/**
     * Generates a contain array for Containable behavior by
     * recursively looping through $Model->hasMany and
     * $Model->hasOne associations.
     *
     * @param object $table Model object
     * @return array
     */
   
protected function _recursiveChildContain(Table $table)
    {
       
$contain = [];
        if (!
$this->getConfig('recursive')) {
            return
$contain;
        }

       
$assocs = $table->associations();
       
$children = array_merge($assocs->getByType('HasMany'), $assocs->getByType('HasOne'));
        foreach (
$children as $child) {
           
$target = $child->getTarget();
            if (
$table->getAlias() == $target->getAlias()) {
                continue;
            }
           
$contain[$target->getAlias()] = $this->_recursiveChildContain($target);
        }

        return
$contain;
    }

   
/**
     * Strips unwanted fields from $record, taken from
     * the 'stripFields' setting.
     *
     * @param array $record
     * @return \Cake\ORM\Entity
     */
   
protected function _stripFields($record)
    {
       
$stripFields = (array)$this->getConfig('stripFields');
        foreach (
$stripFields as $field) {
            if (
$record->has($field)) {
               
$record->unsetProperty($field);
               
$record->isNew(true);
            }
        }

        return
$record;
    }
}