|
|
@@ -14,15 +14,9 @@
|
|
|
*/
|
|
|
namespace Cake\ORM\Behavior;
|
|
|
|
|
|
-use ArrayObject;
|
|
|
-use Cake\Collection\Collection;
|
|
|
-use Cake\Datasource\EntityInterface;
|
|
|
-use Cake\Datasource\QueryInterface;
|
|
|
-use Cake\Event\Event;
|
|
|
use Cake\I18n\I18n;
|
|
|
use Cake\ORM\Behavior;
|
|
|
-use Cake\ORM\Entity;
|
|
|
-use Cake\ORM\Locator\LocatorAwareTrait;
|
|
|
+use Cake\ORM\Behavior\Translate\EavStrategy;
|
|
|
use Cake\ORM\PropertyMarshalInterface;
|
|
|
use Cake\ORM\Query;
|
|
|
use Cake\ORM\Table;
|
|
|
@@ -42,31 +36,6 @@ use Cake\Utility\Inflector;
|
|
|
*/
|
|
|
class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
{
|
|
|
-
|
|
|
- use LocatorAwareTrait;
|
|
|
-
|
|
|
- /**
|
|
|
- * Table instance
|
|
|
- *
|
|
|
- * @var \Cake\ORM\Table
|
|
|
- */
|
|
|
- protected $_table;
|
|
|
-
|
|
|
- /**
|
|
|
- * The locale name that will be used to override fields in the bound table
|
|
|
- * from the translations table
|
|
|
- *
|
|
|
- * @var string
|
|
|
- */
|
|
|
- protected $_locale;
|
|
|
-
|
|
|
- /**
|
|
|
- * Instance of Table responsible for translating
|
|
|
- *
|
|
|
- * @var \Cake\ORM\Table
|
|
|
- */
|
|
|
- protected $_translationTable;
|
|
|
-
|
|
|
/**
|
|
|
* Default config
|
|
|
*
|
|
|
@@ -79,20 +48,27 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
'implementedMethods' => [
|
|
|
'setLocale' => 'setLocale',
|
|
|
'getLocale' => 'getLocale',
|
|
|
- 'translationField' => 'translationField'
|
|
|
+ 'translationField' => 'translationField',
|
|
|
],
|
|
|
+ 'strategyClass' => EavStrategy::class,
|
|
|
'fields' => [],
|
|
|
- 'translationTable' => 'I18n',
|
|
|
'defaultLocale' => '',
|
|
|
'referenceName' => '',
|
|
|
'allowEmptyTranslations' => true,
|
|
|
'onlyTranslated' => false,
|
|
|
'strategy' => 'subquery',
|
|
|
'tableLocator' => null,
|
|
|
- 'validator' => false
|
|
|
+ 'validator' => false,
|
|
|
];
|
|
|
|
|
|
/**
|
|
|
+ * Translation strategy instance.
|
|
|
+ *
|
|
|
+ * @var object|null
|
|
|
+ */
|
|
|
+ protected $strategy;
|
|
|
+
|
|
|
+ /**
|
|
|
* Constructor
|
|
|
*
|
|
|
* @param \Cake\ORM\Table $table The table this behavior is attached to.
|
|
|
@@ -102,13 +78,9 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
{
|
|
|
$config += [
|
|
|
'defaultLocale' => I18n::getDefaultLocale(),
|
|
|
- 'referenceName' => $this->_referenceName($table)
|
|
|
+ 'referenceName' => $this->referenceName($table),
|
|
|
];
|
|
|
|
|
|
- if (isset($config['tableLocator'])) {
|
|
|
- $this->_tableLocator = $config['tableLocator'];
|
|
|
- }
|
|
|
-
|
|
|
parent::__construct($table, $config);
|
|
|
}
|
|
|
|
|
|
@@ -120,254 +92,54 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
*/
|
|
|
public function initialize(array $config)
|
|
|
{
|
|
|
- $this->_translationTable = $this->getTableLocator()->get($this->_config['translationTable']);
|
|
|
-
|
|
|
- $this->setupFieldAssociations(
|
|
|
- $this->_config['fields'],
|
|
|
- $this->_config['translationTable'],
|
|
|
- $this->_config['referenceName'],
|
|
|
- $this->_config['strategy']
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Creates the associations between the bound table and every field passed to
|
|
|
- * this method.
|
|
|
- *
|
|
|
- * Additionally it creates a `i18n` HasMany association that will be
|
|
|
- * used for fetching all translations for each record in the bound table
|
|
|
- *
|
|
|
- * @param array $fields list of fields to create associations for
|
|
|
- * @param string $table the table name to use for storing each field translation
|
|
|
- * @param string $model the model field value
|
|
|
- * @param string $strategy the strategy used in the _i18n association
|
|
|
- *
|
|
|
- * @return void
|
|
|
- */
|
|
|
- public function setupFieldAssociations($fields, $table, $model, $strategy)
|
|
|
- {
|
|
|
- $targetAlias = $this->_translationTable->getAlias();
|
|
|
- $alias = $this->_table->getAlias();
|
|
|
- $filter = $this->_config['onlyTranslated'];
|
|
|
- $tableLocator = $this->getTableLocator();
|
|
|
-
|
|
|
- foreach ($fields as $field) {
|
|
|
- $name = $alias . '_' . $field . '_translation';
|
|
|
-
|
|
|
- if (!$tableLocator->exists($name)) {
|
|
|
- $fieldTable = $tableLocator->get($name, [
|
|
|
- 'className' => $table,
|
|
|
- 'alias' => $name,
|
|
|
- 'table' => $this->_translationTable->getTable()
|
|
|
- ]);
|
|
|
- } else {
|
|
|
- $fieldTable = $tableLocator->get($name);
|
|
|
- }
|
|
|
-
|
|
|
- $conditions = [
|
|
|
- $name . '.model' => $model,
|
|
|
- $name . '.field' => $field,
|
|
|
- ];
|
|
|
- if (!$this->_config['allowEmptyTranslations']) {
|
|
|
- $conditions[$name . '.content !='] = '';
|
|
|
- }
|
|
|
-
|
|
|
- $this->_table->hasOne($name, [
|
|
|
- 'targetTable' => $fieldTable,
|
|
|
- 'foreignKey' => 'foreign_key',
|
|
|
- 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT,
|
|
|
- 'conditions' => $conditions,
|
|
|
- 'propertyName' => $field . '_translation'
|
|
|
- ]);
|
|
|
- }
|
|
|
-
|
|
|
- $conditions = ["$targetAlias.model" => $model];
|
|
|
- if (!$this->_config['allowEmptyTranslations']) {
|
|
|
- $conditions["$targetAlias.content !="] = '';
|
|
|
- }
|
|
|
-
|
|
|
- $this->_table->hasMany($targetAlias, [
|
|
|
- 'className' => $table,
|
|
|
- 'foreignKey' => 'foreign_key',
|
|
|
- 'strategy' => $strategy,
|
|
|
- 'conditions' => $conditions,
|
|
|
- 'propertyName' => '_i18n',
|
|
|
- 'dependent' => true
|
|
|
- ]);
|
|
|
+ $this->getStrategy();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Callback method that listens to the `beforeFind` event in the bound
|
|
|
- * table. It modifies the passed query by eager loading the translated fields
|
|
|
- * and adding a formatter to copy the values into the main table records.
|
|
|
+ * Get strategy class instance.
|
|
|
*
|
|
|
- * @param \Cake\Event\Event $event The beforeFind event that was fired.
|
|
|
- * @param \Cake\ORM\Query $query Query
|
|
|
- * @param \ArrayObject $options The options for the query
|
|
|
- * @return void
|
|
|
+ * @return object
|
|
|
*/
|
|
|
- public function beforeFind(Event $event, Query $query, $options)
|
|
|
+ public function getStrategy()
|
|
|
{
|
|
|
- $locale = $this->getLocale();
|
|
|
-
|
|
|
- if ($locale === $this->getConfig('defaultLocale')) {
|
|
|
- return;
|
|
|
+ if ($this->strategy !== null) {
|
|
|
+ return $this->strategy;
|
|
|
}
|
|
|
|
|
|
- $conditions = function ($field, $locale, $query, $select) {
|
|
|
- return function ($q) use ($field, $locale, $query, $select) {
|
|
|
- /* @var \Cake\Datasource\QueryInterface $q */
|
|
|
- $q->where([$q->getRepository()->aliasField('locale') => $locale]);
|
|
|
-
|
|
|
- /* @var \Cake\ORM\Query $query */
|
|
|
- if ($query->isAutoFieldsEnabled() ||
|
|
|
- in_array($field, $select, true) ||
|
|
|
- in_array($this->_table->aliasField($field), $select, true)
|
|
|
- ) {
|
|
|
- $q->select(['id', 'content']);
|
|
|
- }
|
|
|
-
|
|
|
- return $q;
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- $contain = [];
|
|
|
- $fields = $this->_config['fields'];
|
|
|
- $alias = $this->_table->getAlias();
|
|
|
- $select = $query->clause('select');
|
|
|
-
|
|
|
- $changeFilter = isset($options['filterByCurrentLocale']) &&
|
|
|
- $options['filterByCurrentLocale'] !== $this->_config['onlyTranslated'];
|
|
|
-
|
|
|
- foreach ($fields as $field) {
|
|
|
- $name = $alias . '_' . $field . '_translation';
|
|
|
-
|
|
|
- $contain[$name]['queryBuilder'] = $conditions(
|
|
|
- $field,
|
|
|
- $locale,
|
|
|
- $query,
|
|
|
- $select
|
|
|
- );
|
|
|
-
|
|
|
- if ($changeFilter) {
|
|
|
- $filter = $options['filterByCurrentLocale'] ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT;
|
|
|
- $contain[$name]['joinType'] = $filter;
|
|
|
- }
|
|
|
- }
|
|
|
+ $config = array_diff_key(
|
|
|
+ $this->_config,
|
|
|
+ ['implementedFinders', 'implementedMethods', 'strategyClass']
|
|
|
+ );
|
|
|
+ $this->strategy = new $this->_config['strategyClass']($this->_table, $config);
|
|
|
|
|
|
- $query->contain($contain);
|
|
|
- $query->formatResults(function ($results) use ($locale) {
|
|
|
- return $this->_rowMapper($results, $locale);
|
|
|
- }, $query::PREPEND);
|
|
|
+ return $this->strategy;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Modifies the entity before it is saved so that translated fields are persisted
|
|
|
- * in the database too.
|
|
|
+ * Set strategy class instance.
|
|
|
*
|
|
|
- * @param \Cake\Event\Event $event The beforeSave event that was fired
|
|
|
- * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved
|
|
|
- * @param \ArrayObject $options the options passed to the save method
|
|
|
- * @return void
|
|
|
+ * @param object $strategy Strategy class instance.
|
|
|
+ * @return $this
|
|
|
*/
|
|
|
- public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
|
|
|
+ public function setStrategy($strategy)
|
|
|
{
|
|
|
- $locale = $entity->get('_locale') ?: $this->getLocale();
|
|
|
- $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]];
|
|
|
- $options['associated'] = $newOptions + $options['associated'];
|
|
|
-
|
|
|
- // Check early if empty translations are present in the entity.
|
|
|
- // If this is the case, unset them to prevent persistence.
|
|
|
- // This only applies if $this->_config['allowEmptyTranslations'] is false
|
|
|
- if ($this->_config['allowEmptyTranslations'] === false) {
|
|
|
- $this->_unsetEmptyFields($entity);
|
|
|
- }
|
|
|
-
|
|
|
- $this->_bundleTranslatedFields($entity);
|
|
|
- $bundled = $entity->get('_i18n') ?: [];
|
|
|
- $noBundled = count($bundled) === 0;
|
|
|
-
|
|
|
- // No additional translation records need to be saved,
|
|
|
- // as the entity is in the default locale.
|
|
|
- if ($noBundled && $locale === $this->getConfig('defaultLocale')) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- $values = $entity->extract($this->_config['fields'], true);
|
|
|
- $fields = array_keys($values);
|
|
|
- $noFields = empty($fields);
|
|
|
-
|
|
|
- // If there are no fields and no bundled translations, or both fields
|
|
|
- // in the default locale and bundled translations we can
|
|
|
- // skip the remaining logic as its not necessary.
|
|
|
- if ($noFields && $noBundled || ($fields && $bundled)) {
|
|
|
- return;
|
|
|
- }
|
|
|
+ $this->strategy = $strategy;
|
|
|
|
|
|
- $primaryKey = (array)$this->_table->getPrimaryKey();
|
|
|
- $key = $entity->get(current($primaryKey));
|
|
|
-
|
|
|
- // When we have no key and bundled translations, we
|
|
|
- // need to mark the entity dirty so the root
|
|
|
- // entity persists.
|
|
|
- if ($noFields && $bundled && !$key) {
|
|
|
- foreach ($this->_config['fields'] as $field) {
|
|
|
- $entity->setDirty($field, true);
|
|
|
- }
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if ($noFields) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- $model = $this->_config['referenceName'];
|
|
|
- $preexistent = $this->_translationTable->find()
|
|
|
- ->select(['id', 'field'])
|
|
|
- ->where([
|
|
|
- 'field IN' => $fields,
|
|
|
- 'locale' => $locale,
|
|
|
- 'foreign_key' => $key,
|
|
|
- 'model' => $model
|
|
|
- ])
|
|
|
- ->enableBufferedResults(false)
|
|
|
- ->all()
|
|
|
- ->indexBy('field');
|
|
|
-
|
|
|
- $modified = [];
|
|
|
- foreach ($preexistent as $field => $translation) {
|
|
|
- $translation->set('content', $values[$field]);
|
|
|
- $modified[$field] = $translation;
|
|
|
- }
|
|
|
-
|
|
|
- $new = array_diff_key($values, $modified);
|
|
|
- foreach ($new as $field => $content) {
|
|
|
- $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [
|
|
|
- 'useSetters' => false,
|
|
|
- 'markNew' => true
|
|
|
- ]);
|
|
|
- }
|
|
|
-
|
|
|
- $entity->set('_i18n', array_merge($bundled, array_values($modified + $new)));
|
|
|
- $entity->set('_locale', $locale, ['setter' => false]);
|
|
|
- $entity->setDirty('_locale', false);
|
|
|
-
|
|
|
- foreach ($fields as $field) {
|
|
|
- $entity->setDirty($field, false);
|
|
|
- }
|
|
|
+ return $this;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Unsets the temporary `_i18n` property after the entity has been saved
|
|
|
+ * Gets the Model callbacks this behavior is interested in.
|
|
|
*
|
|
|
- * @param \Cake\Event\Event $event The beforeSave event that was fired
|
|
|
- * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved
|
|
|
- * @return void
|
|
|
+ * @return array
|
|
|
*/
|
|
|
- public function afterSave(Event $event, EntityInterface $entity)
|
|
|
+ public function implementedEvents()
|
|
|
{
|
|
|
- $entity->unsetProperty('_i18n');
|
|
|
+ return [
|
|
|
+ 'Model.beforeFind' => 'beforeFind',
|
|
|
+ 'Model.beforeSave' => 'beforeSave',
|
|
|
+ 'Model.afterSave' => 'afterSave',
|
|
|
+ ];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -379,37 +151,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
*/
|
|
|
public function buildMarshalMap($marshaller, $map, $options)
|
|
|
{
|
|
|
- if (isset($options['translations']) && !$options['translations']) {
|
|
|
- return [];
|
|
|
- }
|
|
|
-
|
|
|
- return [
|
|
|
- '_translations' => function ($value, $entity) use ($marshaller, $options) {
|
|
|
- /* @var \Cake\Datasource\EntityInterface $entity */
|
|
|
- $translations = $entity->get('_translations');
|
|
|
- foreach ($this->_config['fields'] as $field) {
|
|
|
- $options['validate'] = $this->_config['validator'];
|
|
|
- $errors = [];
|
|
|
- if (!is_array($value)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- foreach ($value as $language => $fields) {
|
|
|
- if (!isset($translations[$language])) {
|
|
|
- $translations[$language] = $this->_table->newEntity();
|
|
|
- }
|
|
|
- $marshaller->merge($translations[$language], $fields, $options);
|
|
|
- if ((bool)$translations[$language]->getErrors()) {
|
|
|
- $errors[$language] = $translations[$language]->getErrors();
|
|
|
- }
|
|
|
- }
|
|
|
- // Set errors into the root entity, so validation errors
|
|
|
- // match the original form data position.
|
|
|
- $entity->setErrors($errors);
|
|
|
- }
|
|
|
-
|
|
|
- return $translations;
|
|
|
- }
|
|
|
- ];
|
|
|
+ return $this->getStrategy()->buildMarshalMap($marshaller, $map, $options);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -434,7 +176,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
*/
|
|
|
public function setLocale($locale)
|
|
|
{
|
|
|
- $this->_locale = $locale;
|
|
|
+ $this->getStrategy()->setLocale($locale);
|
|
|
|
|
|
return $this;
|
|
|
}
|
|
|
@@ -451,7 +193,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
*/
|
|
|
public function getLocale()
|
|
|
{
|
|
|
- return $this->_locale ?: I18n::getLocale();
|
|
|
+ return $this->getStrategy()->getLocale();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -466,17 +208,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
*/
|
|
|
public function translationField($field)
|
|
|
{
|
|
|
- $table = $this->_table;
|
|
|
- if ($this->getLocale() === $this->getConfig('defaultLocale')) {
|
|
|
- return $table->aliasField($field);
|
|
|
- }
|
|
|
- $associationName = $table->getAlias() . '_' . $field . '_translation';
|
|
|
-
|
|
|
- if ($table->associations()->has($associationName)) {
|
|
|
- return $associationName . '.content';
|
|
|
- }
|
|
|
-
|
|
|
- return $table->aliasField($field);
|
|
|
+ return $this->getStrategy()->translationField($field);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -503,8 +235,8 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
*/
|
|
|
public function findTranslations(Query $query, array $options)
|
|
|
{
|
|
|
- $locales = isset($options['locales']) ? $options['locales'] : [];
|
|
|
- $targetAlias = $this->_translationTable->getAlias();
|
|
|
+ $locales = $options['locales'] ?? [];
|
|
|
+ $targetAlias = $this->getStrategy()->getTranslationTable()->getAlias();
|
|
|
|
|
|
return $query
|
|
|
->contain([$targetAlias => function ($query) use ($locales, $targetAlias) {
|
|
|
@@ -515,7 +247,19 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
|
|
|
return $query;
|
|
|
}])
|
|
|
- ->formatResults([$this, 'groupTranslations'], $query::PREPEND);
|
|
|
+ ->formatResults([$this->getStrategy(), 'groupTranslations'], $query::PREPEND);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Proxy method calls to strategy class instance.
|
|
|
+ *
|
|
|
+ * @param string $method Method name.
|
|
|
+ * @param array $args Method arguments.
|
|
|
+ * @return mixed
|
|
|
+ */
|
|
|
+ public function __call($method, $args)
|
|
|
+ {
|
|
|
+ return call_user_func_array([$this->strategy, $method], $args);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -529,7 +273,7 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
* @param \Cake\ORM\Table $table The table class to get a reference name for.
|
|
|
* @return string
|
|
|
*/
|
|
|
- protected function _referenceName(Table $table)
|
|
|
+ protected function referenceName(Table $table)
|
|
|
{
|
|
|
$name = namespaceSplit(get_class($table));
|
|
|
$name = substr(end($name), 0, -5);
|
|
|
@@ -540,203 +284,4 @@ class TranslateBehavior extends Behavior implements PropertyMarshalInterface
|
|
|
|
|
|
return $name;
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- * Modifies the results from a table find in order to merge the translated fields
|
|
|
- * into each entity for a given locale.
|
|
|
- *
|
|
|
- * @param \Cake\Datasource\ResultSetInterface $results Results to map.
|
|
|
- * @param string $locale Locale string
|
|
|
- * @return \Cake\Collection\CollectionInterface
|
|
|
- */
|
|
|
- protected function _rowMapper($results, $locale)
|
|
|
- {
|
|
|
- return $results->map(function ($row) use ($locale) {
|
|
|
- if ($row === null) {
|
|
|
- return $row;
|
|
|
- }
|
|
|
- $hydrated = !is_array($row);
|
|
|
-
|
|
|
- foreach ($this->_config['fields'] as $field) {
|
|
|
- $name = $field . '_translation';
|
|
|
- $translation = isset($row[$name]) ? $row[$name] : null;
|
|
|
-
|
|
|
- if ($translation === null || $translation === false) {
|
|
|
- unset($row[$name]);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- $content = isset($translation['content']) ? $translation['content'] : null;
|
|
|
- if ($content !== null) {
|
|
|
- $row[$field] = $content;
|
|
|
- }
|
|
|
-
|
|
|
- unset($row[$name]);
|
|
|
- }
|
|
|
-
|
|
|
- $row['_locale'] = $locale;
|
|
|
- if ($hydrated) {
|
|
|
- /* @var \Cake\Datasource\EntityInterface $row */
|
|
|
- $row->clean();
|
|
|
- }
|
|
|
-
|
|
|
- return $row;
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Modifies the results from a table find in order to merge full translation records
|
|
|
- * into each entity under the `_translations` key
|
|
|
- *
|
|
|
- * @param \Cake\Datasource\ResultSetInterface $results Results to modify.
|
|
|
- * @return \Cake\Collection\CollectionInterface
|
|
|
- */
|
|
|
- public function groupTranslations($results)
|
|
|
- {
|
|
|
- return $results->map(function ($row) {
|
|
|
- if (!$row instanceof EntityInterface) {
|
|
|
- return $row;
|
|
|
- }
|
|
|
- $translations = (array)$row->get('_i18n');
|
|
|
- if (empty($translations) && $row->get('_translations')) {
|
|
|
- return $row;
|
|
|
- }
|
|
|
- $grouped = new Collection($translations);
|
|
|
-
|
|
|
- $result = [];
|
|
|
- foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) {
|
|
|
- $entityClass = $this->_table->getEntityClass();
|
|
|
- $translation = new $entityClass($keys + ['locale' => $locale], [
|
|
|
- 'markNew' => false,
|
|
|
- 'useSetters' => false,
|
|
|
- 'markClean' => true
|
|
|
- ]);
|
|
|
- $result[$locale] = $translation;
|
|
|
- }
|
|
|
-
|
|
|
- $options = ['setter' => false, 'guard' => false];
|
|
|
- $row->set('_translations', $result, $options);
|
|
|
- unset($row['_i18n']);
|
|
|
- $row->clean();
|
|
|
-
|
|
|
- return $row;
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Helper method used to generated multiple translated field entities
|
|
|
- * out of the data found in the `_translations` property in the passed
|
|
|
- * entity. The result will be put into its `_i18n` property
|
|
|
- *
|
|
|
- * @param \Cake\Datasource\EntityInterface $entity Entity
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _bundleTranslatedFields($entity)
|
|
|
- {
|
|
|
- $translations = (array)$entity->get('_translations');
|
|
|
-
|
|
|
- if (empty($translations) && !$entity->isDirty('_translations')) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- $fields = $this->_config['fields'];
|
|
|
- $primaryKey = (array)$this->_table->getPrimaryKey();
|
|
|
- $key = $entity->get(current($primaryKey));
|
|
|
- $find = [];
|
|
|
- $contents = [];
|
|
|
-
|
|
|
- foreach ($translations as $lang => $translation) {
|
|
|
- foreach ($fields as $field) {
|
|
|
- if (!$translation->isDirty($field)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key];
|
|
|
- $contents[] = new Entity(['content' => $translation->get($field)], [
|
|
|
- 'useSetters' => false
|
|
|
- ]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (empty($find)) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- $results = $this->_findExistingTranslations($find);
|
|
|
-
|
|
|
- foreach ($find as $i => $translation) {
|
|
|
- if (!empty($results[$i])) {
|
|
|
- $contents[$i]->set('id', $results[$i], ['setter' => false]);
|
|
|
- $contents[$i]->isNew(false);
|
|
|
- } else {
|
|
|
- $translation['model'] = $this->_config['referenceName'];
|
|
|
- $contents[$i]->set($translation, ['setter' => false, 'guard' => false]);
|
|
|
- $contents[$i]->isNew(true);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $entity->set('_i18n', $contents);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Unset empty translations to avoid persistence.
|
|
|
- *
|
|
|
- * Should only be called if $this->_config['allowEmptyTranslations'] is false.
|
|
|
- *
|
|
|
- * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside.
|
|
|
- * @return void
|
|
|
- */
|
|
|
- protected function _unsetEmptyFields(EntityInterface $entity)
|
|
|
- {
|
|
|
- $translations = (array)$entity->get('_translations');
|
|
|
- foreach ($translations as $locale => $translation) {
|
|
|
- $fields = $translation->extract($this->_config['fields'], false);
|
|
|
- foreach ($fields as $field => $value) {
|
|
|
- if (strlen($value) === 0) {
|
|
|
- $translation->unsetProperty($field);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $translation = $translation->extract($this->_config['fields']);
|
|
|
-
|
|
|
- // If now, the current locale property is empty,
|
|
|
- // unset it completely.
|
|
|
- if (empty(array_filter($translation))) {
|
|
|
- unset($entity->get('_translations')[$locale]);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // If now, the whole _translations property is empty,
|
|
|
- // unset it completely and return
|
|
|
- if (empty($entity->get('_translations'))) {
|
|
|
- $entity->unsetProperty('_translations');
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Returns the ids found for each of the condition arrays passed for the translations
|
|
|
- * table. Each records is indexed by the corresponding position to the conditions array
|
|
|
- *
|
|
|
- * @param array $ruleSet an array of arary of conditions to be used for finding each
|
|
|
- * @return array
|
|
|
- */
|
|
|
- protected function _findExistingTranslations($ruleSet)
|
|
|
- {
|
|
|
- $association = $this->_table->getAssociation($this->_translationTable->getAlias());
|
|
|
-
|
|
|
- $query = $association->find()
|
|
|
- ->select(['id', 'num' => 0])
|
|
|
- ->where(current($ruleSet))
|
|
|
- ->enableHydration(false)
|
|
|
- ->enableBufferedResults(false);
|
|
|
-
|
|
|
- unset($ruleSet[0]);
|
|
|
- foreach ($ruleSet as $i => $conditions) {
|
|
|
- $q = $association->find()
|
|
|
- ->select(['id', 'num' => $i])
|
|
|
- ->where($conditions);
|
|
|
- $query->unionAll($q);
|
|
|
- }
|
|
|
-
|
|
|
- return $query->all()->combine('num', 'id')->toArray();
|
|
|
- }
|
|
|
}
|