CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Team
    • Issues (Github)
    • YouTube Channel
    • Get Involved
    • Bakery
    • Featured Resources
    • Newsletter
    • Certification
    • My CakePHP
    • CakeFest
    • Facebook
    • Twitter
    • Help & Support
    • Forum
    • Stack Overflow
    • IRC
    • Slack
    • Paid Support
CakePHP

C CakePHP 3.7 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.7
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Namespaces

  • Cake
    • Auth
      • Storage
    • Cache
      • Engine
    • Collection
      • Iterator
    • Command
    • Console
      • Exception
    • Controller
      • Component
      • Exception
    • Core
      • Configure
        • Engine
      • Exception
      • Retry
    • Database
      • Driver
      • Exception
      • Expression
      • Schema
      • Statement
      • Type
    • Datasource
      • Exception
    • Error
      • Middleware
    • Event
      • Decorator
    • Filesystem
    • Form
    • Http
      • Client
        • Adapter
        • Auth
      • Cookie
      • Exception
      • Middleware
      • Session
    • I18n
      • Formatter
      • Middleware
      • Parser
    • Log
      • Engine
    • Mailer
      • Exception
      • Transport
    • Network
      • Exception
    • ORM
      • Association
      • Behavior
        • Translate
      • Exception
      • Locator
      • Rule
    • Routing
      • Exception
      • Filter
      • Middleware
      • Route
    • Shell
      • Helper
      • Task
    • TestSuite
      • Fixture
      • Stub
    • Utility
      • Exception
    • Validation
    • View
      • Exception
      • Form
      • Helper
      • Widget
  • None

Classes

  • CounterCacheBehavior
  • TimestampBehavior
  • TranslateBehavior
  • TreeBehavior
  1: <?php
  2: /**
  3:  * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4:  * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  5:  *
  6:  * Licensed under The MIT License
  7:  * For full copyright and license information, please see the LICENSE.txt
  8:  * Redistributions of files must retain the above copyright notice.
  9:  *
 10:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
 11:  * @link          https://cakephp.org CakePHP(tm) Project
 12:  * @since         3.0.0
 13:  * @license       https://opensource.org/licenses/mit-license.php MIT License
 14:  */
 15: namespace Cake\ORM\Behavior;
 16: 
 17: use ArrayObject;
 18: use Cake\Collection\Collection;
 19: use Cake\Datasource\EntityInterface;
 20: use Cake\Datasource\QueryInterface;
 21: use Cake\Event\Event;
 22: use Cake\I18n\I18n;
 23: use Cake\ORM\Behavior;
 24: use Cake\ORM\Entity;
 25: use Cake\ORM\Locator\LocatorAwareTrait;
 26: use Cake\ORM\PropertyMarshalInterface;
 27: use Cake\ORM\Query;
 28: use Cake\ORM\Table;
 29: use Cake\Utility\Inflector;
 30: 
 31: /**
 32:  * This behavior provides a way to translate dynamic data by keeping translations
 33:  * in a separate table linked to the original record from another one. Translated
 34:  * fields can be configured to override those in the main table when fetched or
 35:  * put aside into another property for the same entity.
 36:  *
 37:  * If you wish to override fields, you need to call the `locale` method in this
 38:  * behavior for setting the language you want to fetch from the translations table.
 39:  *
 40:  * If you want to bring all or certain languages for each of the fetched records,
 41:  * you can use the custom `translations` finders that is exposed to the table.
 42:  */
 43: class TranslateBehavior extends Behavior implements PropertyMarshalInterface
 44: {
 45: 
 46:     use LocatorAwareTrait;
 47: 
 48:     /**
 49:      * Table instance
 50:      *
 51:      * @var \Cake\ORM\Table
 52:      */
 53:     protected $_table;
 54: 
 55:     /**
 56:      * The locale name that will be used to override fields in the bound table
 57:      * from the translations table
 58:      *
 59:      * @var string
 60:      */
 61:     protected $_locale;
 62: 
 63:     /**
 64:      * Instance of Table responsible for translating
 65:      *
 66:      * @var \Cake\ORM\Table
 67:      */
 68:     protected $_translationTable;
 69: 
 70:     /**
 71:      * Default config
 72:      *
 73:      * These are merged with user-provided configuration when the behavior is used.
 74:      *
 75:      * @var array
 76:      */
 77:     protected $_defaultConfig = [
 78:         'implementedFinders' => ['translations' => 'findTranslations'],
 79:         'implementedMethods' => [
 80:             'setLocale' => 'setLocale',
 81:             'getLocale' => 'getLocale',
 82:             'locale' => 'locale',
 83:             'translationField' => 'translationField'
 84:         ],
 85:         'fields' => [],
 86:         'translationTable' => 'I18n',
 87:         'defaultLocale' => '',
 88:         'referenceName' => '',
 89:         'allowEmptyTranslations' => true,
 90:         'onlyTranslated' => false,
 91:         'strategy' => 'subquery',
 92:         'tableLocator' => null,
 93:         'validator' => false
 94:     ];
 95: 
 96:     /**
 97:      * Constructor
 98:      *
 99:      * @param \Cake\ORM\Table $table The table this behavior is attached to.
100:      * @param array $config The config for this behavior.
101:      */
102:     public function __construct(Table $table, array $config = [])
103:     {
104:         $config += [
105:             'defaultLocale' => I18n::getDefaultLocale(),
106:             'referenceName' => $this->_referenceName($table)
107:         ];
108: 
109:         if (isset($config['tableLocator'])) {
110:             $this->_tableLocator = $config['tableLocator'];
111:         } else {
112:             $this->_tableLocator = $table->associations()->getTableLocator();
113:         }
114: 
115:         parent::__construct($table, $config);
116:     }
117: 
118:     /**
119:      * Initialize hook
120:      *
121:      * @param array $config The config for this behavior.
122:      * @return void
123:      */
124:     public function initialize(array $config)
125:     {
126:         $this->_translationTable = $this->getTableLocator()->get($this->_config['translationTable']);
127: 
128:         $this->setupFieldAssociations(
129:             $this->_config['fields'],
130:             $this->_config['translationTable'],
131:             $this->_config['referenceName'],
132:             $this->_config['strategy']
133:         );
134:     }
135: 
136:     /**
137:      * Creates the associations between the bound table and every field passed to
138:      * this method.
139:      *
140:      * Additionally it creates a `i18n` HasMany association that will be
141:      * used for fetching all translations for each record in the bound table
142:      *
143:      * @param array $fields list of fields to create associations for
144:      * @param string $table the table name to use for storing each field translation
145:      * @param string $model the model field value
146:      * @param string $strategy the strategy used in the _i18n association
147:      *
148:      * @return void
149:      */
150:     public function setupFieldAssociations($fields, $table, $model, $strategy)
151:     {
152:         $targetAlias = $this->_translationTable->getAlias();
153:         $alias = $this->_table->getAlias();
154:         $filter = $this->_config['onlyTranslated'];
155:         $tableLocator = $this->getTableLocator();
156: 
157:         foreach ($fields as $field) {
158:             $name = $alias . '_' . $field . '_translation';
159: 
160:             if (!$tableLocator->exists($name)) {
161:                 $fieldTable = $tableLocator->get($name, [
162:                     'className' => $table,
163:                     'alias' => $name,
164:                     'table' => $this->_translationTable->getTable()
165:                 ]);
166:             } else {
167:                 $fieldTable = $tableLocator->get($name);
168:             }
169: 
170:             $conditions = [
171:                 $name . '.model' => $model,
172:                 $name . '.field' => $field,
173:             ];
174:             if (!$this->_config['allowEmptyTranslations']) {
175:                 $conditions[$name . '.content !='] = '';
176:             }
177: 
178:             $this->_table->hasOne($name, [
179:                 'targetTable' => $fieldTable,
180:                 'foreignKey' => 'foreign_key',
181:                 'joinType' => $filter ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT,
182:                 'conditions' => $conditions,
183:                 'propertyName' => $field . '_translation'
184:             ]);
185:         }
186: 
187:         $conditions = ["$targetAlias.model" => $model];
188:         if (!$this->_config['allowEmptyTranslations']) {
189:             $conditions["$targetAlias.content !="] = '';
190:         }
191: 
192:         $this->_table->hasMany($targetAlias, [
193:             'className' => $table,
194:             'foreignKey' => 'foreign_key',
195:             'strategy' => $strategy,
196:             'conditions' => $conditions,
197:             'propertyName' => '_i18n',
198:             'dependent' => true
199:         ]);
200:     }
201: 
202:     /**
203:      * Callback method that listens to the `beforeFind` event in the bound
204:      * table. It modifies the passed query by eager loading the translated fields
205:      * and adding a formatter to copy the values into the main table records.
206:      *
207:      * @param \Cake\Event\Event $event The beforeFind event that was fired.
208:      * @param \Cake\ORM\Query $query Query
209:      * @param \ArrayObject $options The options for the query
210:      * @return void
211:      */
212:     public function beforeFind(Event $event, Query $query, $options)
213:     {
214:         $locale = $this->getLocale();
215: 
216:         if ($locale === $this->getConfig('defaultLocale')) {
217:             return;
218:         }
219: 
220:         $conditions = function ($field, $locale, $query, $select) {
221:             return function ($q) use ($field, $locale, $query, $select) {
222:                 /* @var \Cake\Datasource\QueryInterface $q */
223:                 $q->where([$q->getRepository()->aliasField('locale') => $locale]);
224: 
225:                 /* @var \Cake\ORM\Query $query */
226:                 if ($query->isAutoFieldsEnabled() ||
227:                     in_array($field, $select, true) ||
228:                     in_array($this->_table->aliasField($field), $select, true)
229:                 ) {
230:                     $q->select(['id', 'content']);
231:                 }
232: 
233:                 return $q;
234:             };
235:         };
236: 
237:         $contain = [];
238:         $fields = $this->_config['fields'];
239:         $alias = $this->_table->getAlias();
240:         $select = $query->clause('select');
241: 
242:         $changeFilter = isset($options['filterByCurrentLocale']) &&
243:             $options['filterByCurrentLocale'] !== $this->_config['onlyTranslated'];
244: 
245:         foreach ($fields as $field) {
246:             $name = $alias . '_' . $field . '_translation';
247: 
248:             $contain[$name]['queryBuilder'] = $conditions(
249:                 $field,
250:                 $locale,
251:                 $query,
252:                 $select
253:             );
254: 
255:             if ($changeFilter) {
256:                 $filter = $options['filterByCurrentLocale'] ? QueryInterface::JOIN_TYPE_INNER : QueryInterface::JOIN_TYPE_LEFT;
257:                 $contain[$name]['joinType'] = $filter;
258:             }
259:         }
260: 
261:         $query->contain($contain);
262:         $query->formatResults(function ($results) use ($locale) {
263:             return $this->_rowMapper($results, $locale);
264:         }, $query::PREPEND);
265:     }
266: 
267:     /**
268:      * Modifies the entity before it is saved so that translated fields are persisted
269:      * in the database too.
270:      *
271:      * @param \Cake\Event\Event $event The beforeSave event that was fired
272:      * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved
273:      * @param \ArrayObject $options the options passed to the save method
274:      * @return void
275:      */
276:     public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
277:     {
278:         $locale = $entity->get('_locale') ?: $this->getLocale();
279:         $newOptions = [$this->_translationTable->getAlias() => ['validate' => false]];
280:         $options['associated'] = $newOptions + $options['associated'];
281: 
282:         // Check early if empty translations are present in the entity.
283:         // If this is the case, unset them to prevent persistence.
284:         // This only applies if $this->_config['allowEmptyTranslations'] is false
285:         if ($this->_config['allowEmptyTranslations'] === false) {
286:             $this->_unsetEmptyFields($entity);
287:         }
288: 
289:         $this->_bundleTranslatedFields($entity);
290:         $bundled = $entity->get('_i18n') ?: [];
291:         $noBundled = count($bundled) === 0;
292: 
293:         // No additional translation records need to be saved,
294:         // as the entity is in the default locale.
295:         if ($noBundled && $locale === $this->getConfig('defaultLocale')) {
296:             return;
297:         }
298: 
299:         $values = $entity->extract($this->_config['fields'], true);
300:         $fields = array_keys($values);
301:         $noFields = empty($fields);
302: 
303:         // If there are no fields and no bundled translations, or both fields
304:         // in the default locale and bundled translations we can
305:         // skip the remaining logic as its not necessary.
306:         if ($noFields && $noBundled || ($fields && $bundled)) {
307:             return;
308:         }
309: 
310:         $primaryKey = (array)$this->_table->getPrimaryKey();
311:         $key = $entity->get(current($primaryKey));
312: 
313:         // When we have no key and bundled translations, we
314:         // need to mark the entity dirty so the root
315:         // entity persists.
316:         if ($noFields && $bundled && !$key) {
317:             foreach ($this->_config['fields'] as $field) {
318:                 $entity->setDirty($field, true);
319:             }
320: 
321:             return;
322:         }
323: 
324:         if ($noFields) {
325:             return;
326:         }
327: 
328:         $model = $this->_config['referenceName'];
329:         $preexistent = $this->_translationTable->find()
330:             ->select(['id', 'field'])
331:             ->where([
332:                 'field IN' => $fields,
333:                 'locale' => $locale,
334:                 'foreign_key' => $key,
335:                 'model' => $model
336:             ])
337:             ->disableBufferedResults()
338:             ->all()
339:             ->indexBy('field');
340: 
341:         $modified = [];
342:         foreach ($preexistent as $field => $translation) {
343:             $translation->set('content', $values[$field]);
344:             $modified[$field] = $translation;
345:         }
346: 
347:         $new = array_diff_key($values, $modified);
348:         foreach ($new as $field => $content) {
349:             $new[$field] = new Entity(compact('locale', 'field', 'content', 'model'), [
350:                 'useSetters' => false,
351:                 'markNew' => true
352:             ]);
353:         }
354: 
355:         $entity->set('_i18n', array_merge($bundled, array_values($modified + $new)));
356:         $entity->set('_locale', $locale, ['setter' => false]);
357:         $entity->setDirty('_locale', false);
358: 
359:         foreach ($fields as $field) {
360:             $entity->setDirty($field, false);
361:         }
362:     }
363: 
364:     /**
365:      * Unsets the temporary `_i18n` property after the entity has been saved
366:      *
367:      * @param \Cake\Event\Event $event The beforeSave event that was fired
368:      * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved
369:      * @return void
370:      */
371:     public function afterSave(Event $event, EntityInterface $entity)
372:     {
373:         $entity->unsetProperty('_i18n');
374:     }
375: 
376:     /**
377:      * Add in `_translations` marshalling handlers. You can disable marshalling
378:      * of translations by setting `'translations' => false` in the options
379:      * provided to `Table::newEntity()` or `Table::patchEntity()`.
380:      *
381:      * {@inheritDoc}
382:      */
383:     public function buildMarshalMap($marshaller, $map, $options)
384:     {
385:         if (isset($options['translations']) && !$options['translations']) {
386:             return [];
387:         }
388: 
389:         return [
390:             '_translations' => function ($value, $entity) use ($marshaller, $options) {
391:                 /* @var \Cake\Datasource\EntityInterface $entity */
392:                 $translations = $entity->get('_translations');
393:                 foreach ($this->_config['fields'] as $field) {
394:                     $options['validate'] = $this->_config['validator'];
395:                     $errors = [];
396:                     if (!is_array($value)) {
397:                         return null;
398:                     }
399:                     foreach ($value as $language => $fields) {
400:                         if (!isset($translations[$language])) {
401:                             $translations[$language] = $this->_table->newEntity();
402:                         }
403:                         $marshaller->merge($translations[$language], $fields, $options);
404:                         if ((bool)$translations[$language]->getErrors()) {
405:                             $errors[$language] = $translations[$language]->getErrors();
406:                         }
407:                     }
408:                     // Set errors into the root entity, so validation errors
409:                     // match the original form data position.
410:                     $entity->setErrors($errors);
411:                 }
412: 
413:                 return $translations;
414:             }
415:         ];
416:     }
417: 
418:     /**
419:      * Sets the locale that should be used for all future find and save operations on
420:      * the table where this behavior is attached to.
421:      *
422:      * When fetching records, the behavior will include the content for the locale set
423:      * via this method, and likewise when saving data, it will save the data in that
424:      * locale.
425:      *
426:      * Note that in case an entity has a `_locale` property set, that locale will win
427:      * over the locale set via this method (and over the globally configured one for
428:      * that matter)!
429:      *
430:      * @param string|null $locale The locale to use for fetching and saving records. Pass `null`
431:      * in order to unset the current locale, and to make the behavior fall back to using the
432:      * globally configured locale.
433:      * @return $this
434:      * @see \Cake\ORM\Behavior\TranslateBehavior::getLocale()
435:      * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#retrieving-one-language-without-using-i18n-locale
436:      * @link https://book.cakephp.org/3.0/en/orm/behaviors/translate.html#saving-in-another-language
437:      */
438:     public function setLocale($locale)
439:     {
440:         $this->_locale = $locale;
441: 
442:         return $this;
443:     }
444: 
445:     /**
446:      * Returns the current locale.
447:      *
448:      * If no locale has been explicitly set via `setLocale()`, this method will return
449:      * the currently configured global locale.
450:      *
451:      * @return string
452:      * @see \Cake\I18n\I18n::getLocale()
453:      * @see \Cake\ORM\Behavior\TranslateBehavior::setLocale()
454:      */
455:     public function getLocale()
456:     {
457:         return $this->_locale ?: I18n::getLocale();
458:     }
459: 
460:     /**
461:      * Sets all future finds for the bound table to also fetch translated fields for
462:      * the passed locale. If no value is passed, it returns the currently configured
463:      * locale
464:      *
465:      * @deprecated 3.6.0 Use setLocale()/getLocale() instead.
466:      * @param string|null $locale The locale to use for fetching translated records
467:      * @return string
468:      */
469:     public function locale($locale = null)
470:     {
471:         deprecationWarning(
472:             get_called_class() . '::locale() is deprecated. ' .
473:             'Use setLocale()/getLocale() instead.'
474:         );
475: 
476:         if ($locale !== null) {
477:             $this->setLocale($locale);
478:         }
479: 
480:         return $this->getLocale();
481:     }
482: 
483:     /**
484:      * Returns a fully aliased field name for translated fields.
485:      *
486:      * If the requested field is configured as a translation field, the `content`
487:      * field with an alias of a corresponding association is returned. Table-aliased
488:      * field name is returned for all other fields.
489:      *
490:      * @param string $field Field name to be aliased.
491:      * @return string
492:      */
493:     public function translationField($field)
494:     {
495:         $table = $this->_table;
496:         if ($this->getLocale() === $this->getConfig('defaultLocale')) {
497:             return $table->aliasField($field);
498:         }
499:         $associationName = $table->getAlias() . '_' . $field . '_translation';
500: 
501:         if ($table->associations()->has($associationName)) {
502:             return $associationName . '.content';
503:         }
504: 
505:         return $table->aliasField($field);
506:     }
507: 
508:     /**
509:      * Custom finder method used to retrieve all translations for the found records.
510:      * Fetched translations can be filtered by locale by passing the `locales` key
511:      * in the options array.
512:      *
513:      * Translated values will be found for each entity under the property `_translations`,
514:      * containing an array indexed by locale name.
515:      *
516:      * ### Example:
517:      *
518:      * ```
519:      * $article = $articles->find('translations', ['locales' => ['eng', 'deu'])->first();
520:      * $englishTranslatedFields = $article->get('_translations')['eng'];
521:      * ```
522:      *
523:      * If the `locales` array is not passed, it will bring all translations found
524:      * for each record.
525:      *
526:      * @param \Cake\ORM\Query $query The original query to modify
527:      * @param array $options Options
528:      * @return \Cake\ORM\Query
529:      */
530:     public function findTranslations(Query $query, array $options)
531:     {
532:         $locales = isset($options['locales']) ? $options['locales'] : [];
533:         $targetAlias = $this->_translationTable->getAlias();
534: 
535:         return $query
536:             ->contain([$targetAlias => function ($query) use ($locales, $targetAlias) {
537:                 if ($locales) {
538:                     /* @var \Cake\Datasource\QueryInterface $query */
539:                     $query->where(["$targetAlias.locale IN" => $locales]);
540:                 }
541: 
542:                 return $query;
543:             }])
544:             ->formatResults([$this, 'groupTranslations'], $query::PREPEND);
545:     }
546: 
547:     /**
548:      * Determine the reference name to use for a given table
549:      *
550:      * The reference name is usually derived from the class name of the table object
551:      * (PostsTable -> Posts), however for autotable instances it is derived from
552:      * the database table the object points at - or as a last resort, the alias
553:      * of the autotable instance.
554:      *
555:      * @param \Cake\ORM\Table $table The table class to get a reference name for.
556:      * @return string
557:      */
558:     protected function _referenceName(Table $table)
559:     {
560:         $name = namespaceSplit(get_class($table));
561:         $name = substr(end($name), 0, -5);
562:         if (empty($name)) {
563:             $name = $table->getTable() ?: $table->getAlias();
564:             $name = Inflector::camelize($name);
565:         }
566: 
567:         return $name;
568:     }
569: 
570:     /**
571:      * Modifies the results from a table find in order to merge the translated fields
572:      * into each entity for a given locale.
573:      *
574:      * @param \Cake\Datasource\ResultSetInterface $results Results to map.
575:      * @param string $locale Locale string
576:      * @return \Cake\Collection\CollectionInterface
577:      */
578:     protected function _rowMapper($results, $locale)
579:     {
580:         return $results->map(function ($row) use ($locale) {
581:             if ($row === null) {
582:                 return $row;
583:             }
584:             $hydrated = !is_array($row);
585: 
586:             foreach ($this->_config['fields'] as $field) {
587:                 $name = $field . '_translation';
588:                 $translation = isset($row[$name]) ? $row[$name] : null;
589: 
590:                 if ($translation === null || $translation === false) {
591:                     unset($row[$name]);
592:                     continue;
593:                 }
594: 
595:                 $content = isset($translation['content']) ? $translation['content'] : null;
596:                 if ($content !== null) {
597:                     $row[$field] = $content;
598:                 }
599: 
600:                 unset($row[$name]);
601:             }
602: 
603:             $row['_locale'] = $locale;
604:             if ($hydrated) {
605:                 /* @var \Cake\Datasource\EntityInterface $row */
606:                 $row->clean();
607:             }
608: 
609:             return $row;
610:         });
611:     }
612: 
613:     /**
614:      * Modifies the results from a table find in order to merge full translation records
615:      * into each entity under the `_translations` key
616:      *
617:      * @param \Cake\Datasource\ResultSetInterface $results Results to modify.
618:      * @return \Cake\Collection\CollectionInterface
619:      */
620:     public function groupTranslations($results)
621:     {
622:         return $results->map(function ($row) {
623:             if (!$row instanceof EntityInterface) {
624:                 return $row;
625:             }
626:             $translations = (array)$row->get('_i18n');
627:             if (empty($translations) && $row->get('_translations')) {
628:                 return $row;
629:             }
630:             $grouped = new Collection($translations);
631: 
632:             $result = [];
633:             foreach ($grouped->combine('field', 'content', 'locale') as $locale => $keys) {
634:                 $entityClass = $this->_table->getEntityClass();
635:                 $translation = new $entityClass($keys + ['locale' => $locale], [
636:                     'markNew' => false,
637:                     'useSetters' => false,
638:                     'markClean' => true
639:                 ]);
640:                 $result[$locale] = $translation;
641:             }
642: 
643:             $options = ['setter' => false, 'guard' => false];
644:             $row->set('_translations', $result, $options);
645:             unset($row['_i18n']);
646:             $row->clean();
647: 
648:             return $row;
649:         });
650:     }
651: 
652:     /**
653:      * Helper method used to generated multiple translated field entities
654:      * out of the data found in the `_translations` property in the passed
655:      * entity. The result will be put into its `_i18n` property
656:      *
657:      * @param \Cake\Datasource\EntityInterface $entity Entity
658:      * @return void
659:      */
660:     protected function _bundleTranslatedFields($entity)
661:     {
662:         $translations = (array)$entity->get('_translations');
663: 
664:         if (empty($translations) && !$entity->isDirty('_translations')) {
665:             return;
666:         }
667: 
668:         $fields = $this->_config['fields'];
669:         $primaryKey = (array)$this->_table->getPrimaryKey();
670:         $key = $entity->get(current($primaryKey));
671:         $find = [];
672:         $contents = [];
673: 
674:         foreach ($translations as $lang => $translation) {
675:             foreach ($fields as $field) {
676:                 if (!$translation->isDirty($field)) {
677:                     continue;
678:                 }
679:                 $find[] = ['locale' => $lang, 'field' => $field, 'foreign_key' => $key];
680:                 $contents[] = new Entity(['content' => $translation->get($field)], [
681:                     'useSetters' => false
682:                 ]);
683:             }
684:         }
685: 
686:         if (empty($find)) {
687:             return;
688:         }
689: 
690:         $results = $this->_findExistingTranslations($find);
691: 
692:         foreach ($find as $i => $translation) {
693:             if (!empty($results[$i])) {
694:                 $contents[$i]->set('id', $results[$i], ['setter' => false]);
695:                 $contents[$i]->isNew(false);
696:             } else {
697:                 $translation['model'] = $this->_config['referenceName'];
698:                 $contents[$i]->set($translation, ['setter' => false, 'guard' => false]);
699:                 $contents[$i]->isNew(true);
700:             }
701:         }
702: 
703:         $entity->set('_i18n', $contents);
704:     }
705: 
706:     /**
707:      * Unset empty translations to avoid persistence.
708:      *
709:      * Should only be called if $this->_config['allowEmptyTranslations'] is false.
710:      *
711:      * @param \Cake\Datasource\EntityInterface $entity The entity to check for empty translations fields inside.
712:      * @return void
713:      */
714:     protected function _unsetEmptyFields(EntityInterface $entity)
715:     {
716:         $translations = (array)$entity->get('_translations');
717:         foreach ($translations as $locale => $translation) {
718:             $fields = $translation->extract($this->_config['fields'], false);
719:             foreach ($fields as $field => $value) {
720:                 if (strlen($value) === 0) {
721:                     $translation->unsetProperty($field);
722:                 }
723:             }
724: 
725:             $translation = $translation->extract($this->_config['fields']);
726: 
727:             // If now, the current locale property is empty,
728:             // unset it completely.
729:             if (empty(array_filter($translation))) {
730:                 unset($entity->get('_translations')[$locale]);
731:             }
732:         }
733: 
734:         // If now, the whole _translations property is empty,
735:         // unset it completely and return
736:         if (empty($entity->get('_translations'))) {
737:             $entity->unsetProperty('_translations');
738:         }
739:     }
740: 
741:     /**
742:      * Returns the ids found for each of the condition arrays passed for the translations
743:      * table. Each records is indexed by the corresponding position to the conditions array
744:      *
745:      * @param array $ruleSet an array of arary of conditions to be used for finding each
746:      * @return array
747:      */
748:     protected function _findExistingTranslations($ruleSet)
749:     {
750:         $association = $this->_table->getAssociation($this->_translationTable->getAlias());
751: 
752:         $query = $association->find()
753:             ->select(['id', 'num' => 0])
754:             ->where(current($ruleSet))
755:             ->disableHydration()
756:             ->disableBufferedResults();
757: 
758:         unset($ruleSet[0]);
759:         foreach ($ruleSet as $i => $conditions) {
760:             $q = $association->find()
761:                 ->select(['id', 'num' => $i])
762:                 ->where($conditions);
763:             $query->unionAll($q);
764:         }
765: 
766:         return $query->all()->combine('num', 'id')->toArray();
767:     }
768: }
769: 
Follow @CakePHP
#IRC
OpenHub
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Logos & Trademarks
  • Community
  • Team
  • Issues (Github)
  • YouTube Channel
  • Get Involved
  • Bakery
  • Featured Resources
  • Newsletter
  • Certification
  • My CakePHP
  • CakeFest
  • Facebook
  • Twitter
  • Help & Support
  • Forum
  • Stack Overflow
  • IRC
  • Slack
  • Paid Support

Generated using CakePHP API Docs