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.8 Red Velvet API

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