Passed
Push — master ( f0f4df...4d745c )
by Dāvis
10:06
created

TranslatableListener::isSkipOnLoad()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Sludio\HelperBundle\Translatable\Listener;
4
5
use Doctrine\Common\EventArgs;
6
use Doctrine\ORM\ORMInvalidArgumentException;
0 ignored issues
show
Bug introduced by
The type Doctrine\ORM\ORMInvalidArgumentException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
7
use Gedmo\Exception\InvalidArgumentException;
8
use Gedmo\Exception\RuntimeException;
9
use Gedmo\Tool\Wrapper\AbstractWrapper;
10
use Gedmo\Translatable\Mapping\Event\TranslatableAdapter;
11
use Gedmo\Translatable\TranslatableListener as BaseListener;
12
13
/**
14
 * The translation listener handles the generation and
15
 * loading of translations for entities which implements
16
 * the Translatable interface.
17
 *
18
 * This behavior can impact the performance of your application
19
 * since it does an additional query for each field to translate.
20
 *
21
 * Nevertheless the annotation metadata is properly cached and
22
 * it is not a big overhead to lookup all entity annotations since
23
 * the caching is activated for metadata
24
 *
25
 */
26
class TranslatableListener extends BaseListener
27
{
28
    /**
29
     * Query hint to override the fallback of translations
30
     * integer 1 for true, 0 false.
31
     */
32
    const HINT_FALLBACK = 'gedmo.translatable.fallback';
33
34
    /**
35
     * Query hint to override the fallback locale.
36
     */
37
    const HINT_TRANSLATABLE_LOCALE = 'gedmo.translatable.locale';
38
39
    /**
40
     * Query hint to use inner join strategy for translations.
41
     */
42
    const HINT_INNER_JOIN = 'gedmo.translatable.inner_join.translations';
43
44
    /**
45
     * Locale which is set on this listener.
46
     * If Entity being translated has locale defined it
47
     * will override this one.
48
     *
49
     * @var string
50
     */
51
    protected $locale = 'en_US';
52
53
    /**
54
     * Default locale, this changes behavior
55
     * to not update the original record field if locale
56
     * which is used for updating is not default. This
57
     * will load the default translation in other locales
58
     * if record is not translated yet.
59
     *
60
     * @var string
61
     */
62
    private $defaultLocale = 'en_US';
63
64
    /**
65
     * If this is set to false, when if entity does
66
     * not have a translation for requested locale
67
     * it will show a blank value.
68
     *
69
     * @var bool
70
     */
71
    private $translationFallback = false;
72
73
    /**
74
     * List of translations which do not have the foreign
75
     * key generated yet - MySQL case. These translations
76
     * will be updated with new keys on postPersist event.
77
     *
78
     * @var array
79
     */
80
    private $pendingTranslationInserts = [];
81
82
    /**
83
     * Currently in case if there is TranslationQueryWalker
84
     * in charge. We need to skip issuing additional queries
85
     * on load.
86
     *
87
     * @var bool
88
     */
89
    private $skipOnLoad = true;
90
91
    /**
92
     * Tracks locale the objects currently translated in.
93
     *
94
     * @var array
95
     */
96
    private $translatedInLocale = [];
97
98
    /**
99
     * Whether or not, to persist default locale
100
     * translation or keep it in original record.
101
     *
102
     * @var bool
103
     */
104
    private $persistDefaultLocaleTranslation = false;
105
106
    /**
107
     * Tracks translation object for default locale.
108
     *
109
     * @var array
110
     */
111
    private $translationInDefaultLocale = [];
112
113
    /**
114
     * Specifies the list of events to listen.
115
     *
116
     * @return array
117
     */
118
    public function getSubscribedEvents()
119
    {
120
        return [
121
            'postLoad',
122
            'postPersist',
123
            'preFlush',
124
            'onFlush',
125
            'loadClassMetadata',
126
        ];
127
    }
128
129
    /**
130
     * Set to skip or not onLoad event.
131
     *
132
     * @param bool $bool
133
     *
134
     * @return static
135
     */
136
    public function setSkipOnLoad($bool)
137
    {
138
        $this->skipOnLoad = (bool)$bool;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Whether or not, to persist default locale
145
     * translation or keep it in original record.
146
     *
147
     * @param bool $bool
148
     *
149
     * @return static
150
     */
151
    public function setPersistDefaultLocaleTranslation($bool)
152
    {
153
        $this->persistDefaultLocaleTranslation = (bool)$bool;
154
155
        return $this;
156
    }
157
158
    /**
159
     * Check if should persist default locale
160
     * translation or keep it in original record.
161
     *
162
     * @return bool
163
     */
164
    public function getPersistDefaultLocaleTranslation()
165
    {
166
        return (bool)$this->persistDefaultLocaleTranslation;
167
    }
168
169
    /**
170
     * Add additional $translation for pending $oid object
171
     * which is being inserted.
172
     *
173
     * @param string $oid
174
     * @param object $translation
175
     */
176
    public function addPendingTranslationInsert($oid, $translation)
177
    {
178
        $this->pendingTranslationInserts[$oid][] = $translation;
179
    }
180
181
    /**
182
     * Maps additional metadata.
183
     *
184
     * @param EventArgs $eventArgs
185
     */
186
    public function loadClassMetadata(EventArgs $eventArgs)
187
    {
188
        $ea = $this->getEventAdapter($eventArgs);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
189
        $this->loadMetadataForObjectClass($ea->getObjectManager(), $eventArgs->getClassMetadata());
0 ignored issues
show
Bug introduced by
The method getClassMetadata() does not exist on Doctrine\Common\EventArgs. It seems like you code against a sub-type of Doctrine\Common\EventArgs such as Doctrine\Common\Persiste...dClassMetadataEventArgs. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

189
        $this->loadMetadataForObjectClass($ea->getObjectManager(), $eventArgs->/** @scrutinizer ignore-call */ getClassMetadata());
Loading history...
190
    }
191
192
    /**
193
     * Get the translation class to be used
194
     * for the object $class.
195
     *
196
     * @param TranslatableAdapter $ea
197
     * @param string              $class
198
     *
199
     * @return string
200
     */
201
    public function getTranslationClass(TranslatableAdapter $ea, $class)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
202
    {
203
        return isset(self::$configurations[$this->name][$class]['translationClass']) ? self::$configurations[$this->name][$class]['translationClass'] : $ea->getDefaultTranslationClass();
204
    }
205
206
    /**
207
     * Enable or disable translation fallback
208
     * to original record value.
209
     *
210
     * @param bool $bool
211
     *
212
     * @return static
213
     */
214
    public function setTranslationFallback($bool)
215
    {
216
        $this->translationFallback = (bool)$bool;
217
218
        return $this;
219
    }
220
221
    /**
222
     * Weather or not is using the translation
223
     * fallback to original record.
224
     *
225
     * @return bool
226
     */
227
    public function getTranslationFallback()
228
    {
229
        return $this->translationFallback;
230
    }
231
232
    /**
233
     * Set the locale to use for translation listener.
234
     *
235
     * @param string $locale
236
     *
237
     * @return static
238
     */
239
    public function setTranslatableLocale($locale)
240
    {
241
        $this->validateLocale($locale);
242
        $this->locale = $locale;
243
244
        return $this;
245
    }
246
247
    /**
248
     * Sets the default locale, this changes behavior
249
     * to not update the original record field if locale
250
     * which is used for updating is not default.
251
     *
252
     * @param string $locale
253
     *
254
     * @return static
255
     */
256
    public function setDefaultLocale($locale)
257
    {
258
        $this->validateLocale($locale);
259
        $this->defaultLocale = $locale;
260
261
        return $this;
262
    }
263
264
    /**
265
     * Gets the default locale.
266
     *
267
     * @return string
268
     */
269
    public function getDefaultLocale()
270
    {
271
        return $this->defaultLocale;
272
    }
273
274
    /**
275
     * Get currently set global locale, used
276
     * extensively during query execution.
277
     *
278
     * @return string
279
     */
280
    public function getListenerLocale()
281
    {
282
        return $this->locale;
283
    }
284
285
    /**
286
     * Gets the locale to use for translation. Loads object
287
     * defined locale first..
288
     *
289
     * @param object $object
290
     * @param object $meta
291
     * @param object $om
292
     *
293
     * @throws \Gedmo\Exception\RuntimeException - if language or locale property is not
294
     *                                           found in entity
295
     *
296
     * @return string
297
     */
298
    public function getTranslatableLocale($object, $meta, $om = null)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $om. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
299
    {
300
        $locale = $this->locale;
301
        if (isset(self::$configurations[$this->name][$meta->name]['locale'])) {
302
            /** @var \ReflectionClass $class */
303
            $class = $meta->getReflectionClass();
304
            $reflectionProperty = $class->getProperty(self::$configurations[$this->name][$meta->name]['locale']);
305
            if (!$reflectionProperty) {
306
                $column = self::$configurations[$this->name][$meta->name]['locale'];
307
                throw new RuntimeException("There is no locale or language property ({$column}) found on object: {$meta->name}");
308
            }
309
            $reflectionProperty->setAccessible(true);
310
            $value = $reflectionProperty->getValue($object);
311
            if (is_object($value) && method_exists($value, '__toString')) {
312
                $value = (string)$value;
313
            }
314
            if ($this->isValidLocale($value)) {
315
                $locale = $value;
316
            }
317
        }
318
319
        return $locale;
320
    }
321
322
    /**
323
     * Handle translation changes in default locale.
324
     *
325
     * This has to be done in the preFlush because, when an entity has been loaded
326
     * in a different locale, no changes will be detected.
327
     *
328
     * @param EventArgs $args
329
     */
330
    public function preFlush(EventArgs $args)
331
    {
332
        $ea = $this->getEventAdapter($args);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
333
        $om = $ea->getObjectManager();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $om. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
334
        $uow = $om->getUnitOfWork();
0 ignored issues
show
Bug introduced by
The method getUnitOfWork() does not exist on Doctrine\Common\Persistence\ObjectManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

334
        $uow = $om->/** @scrutinizer ignore-call */ getUnitOfWork();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
335
336
        foreach ($this->translationInDefaultLocale as $oid => $fields) {
337
            $trans = reset($fields);
338
            if ($ea->usesPersonalTranslation(get_class($trans))) {
339
                $entity = $trans->getObject();
340
            } else {
341
                $entity = $uow->tryGetById($trans->getForeignKey(), $trans->getObjectClass());
342
            }
343
344
            if (!$entity) {
345
                continue;
346
            }
347
348
            try {
349
                $uow->scheduleForUpdate($entity);
350
            } catch (ORMInvalidArgumentException $e) {
351
                foreach ($fields as $field => $trans) {
352
                    $this->removeTranslationInDefaultLocale($oid, $field);
353
                }
354
            }
355
        }
356
    }
357
358
    /**
359
     * Looks for translatable objects being inserted or updated
360
     * for further processing.
361
     *
362
     * @param EventArgs $args
363
     */
364
    public function onFlush(EventArgs $args)
365
    {
366
        $ea = $this->getEventAdapter($args);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
367
        $om = $ea->getObjectManager();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $om. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
368
        $uow = $om->getUnitOfWork();
369
        // check all scheduled inserts for Translatable objects
370 View Code Duplication
        foreach ($ea->getScheduledObjectInsertions($uow) as $object) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
371
            $meta = $om->getClassMetadata(get_class($object));
372
            $config = $this->getConfiguration($om, $meta->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
373
            if (isset($config['fields'])) {
374
                $this->handleTranslatableObjectUpdate($ea, $object, true);
375
            }
376
        }
377
        // check all scheduled updates for Translatable entities
378 View Code Duplication
        foreach ($ea->getScheduledObjectUpdates($uow) as $object) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
379
            $meta = $om->getClassMetadata(get_class($object));
380
            $config = $this->getConfiguration($om, $meta->name);
381
            if (isset($config['fields'])) {
382
                $this->handleTranslatableObjectUpdate($ea, $object, false);
383
            }
384
        }
385
        // check scheduled deletions for Translatable entities
386
        foreach ($ea->getScheduledObjectDeletions($uow) as $object) {
387
            $meta = $om->getClassMetadata(get_class($object));
388
            $config = $this->getConfiguration($om, $meta->name);
389
            if (isset($config['fields'])) {
390
                $wrapped = AbstractWrapper::wrap($object, $om);
391
                $transClass = $this->getTranslationClass($ea, $meta->name);
392
                $ea->removeAssociatedTranslations($wrapped, $transClass, $config['useObjectClass']);
393
            }
394
        }
395
    }
396
397
    /**
398
     * Checks for inserted object to update their translation
399
     * foreign keys.
400
     *
401
     * @param EventArgs $args
402
     */
403
    public function postPersist(EventArgs $args)
404
    {
405
        $ea = $this->getEventAdapter($args);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
406
        $om = $ea->getObjectManager();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $om. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
407
        $object = $ea->getObject();
408
        $meta = $om->getClassMetadata(get_class($object));
409
        // check if entity is tracked by translatable and without foreign key
410
        if ($this->getConfiguration($om, $meta->name) && count($this->pendingTranslationInserts)) {
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
411
            $oid = spl_object_hash($object);
412
            if (array_key_exists($oid, $this->pendingTranslationInserts)) {
413
                // load the pending translations without key
414
                $wrapped = AbstractWrapper::wrap($object, $om);
415
                $objectId = $wrapped->getIdentifier();
416
                $translationClass = $this->getTranslationClass($ea, get_class($object));
417
                foreach ($this->pendingTranslationInserts[$oid] as $translation) {
418
                    if ($ea->usesPersonalTranslation($translationClass)) {
419
                        $translation->setObject($objectId);
420
                    } else {
421
                        $translation->setForeignKey($objectId);
422
                    }
423
                    $ea->insertTranslationRecord($translation);
424
                }
425
                unset($this->pendingTranslationInserts[$oid]);
426
            }
427
        }
428
    }
429
430
    /**
431
     * After object is loaded, listener updates the translations
432
     * by currently used locale.
433
     *
434
     * @param EventArgs $args
435
     */
436
    public function postLoad(EventArgs $args)
437
    {
438
        $ea = $this->getEventAdapter($args);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
439
        $om = $ea->getObjectManager();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $om. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
440
        $object = $ea->getObject();
441
        $meta = $om->getClassMetadata(get_class($object));
442
        $config = $this->getConfiguration($om, $meta->name);
0 ignored issues
show
Bug introduced by
Accessing name on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
443
        if (isset($config['fields'])) {
444
            $locale = $this->getTranslatableLocale($object, $meta, $om);
445
            $oid = spl_object_hash($object);
446
            $this->translatedInLocale[$oid] = $locale;
447
        }
448
449
        if ($this->skipOnLoad) {
450
            return;
451
        }
452
453
        if (isset($config['fields']) && ($locale !== $this->defaultLocale || $this->persistDefaultLocaleTranslation)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $locale does not seem to be defined for all execution paths leading up to this point.
Loading history...
454
            // fetch translations
455
            $translationClass = $this->getTranslationClass($ea, $config['useObjectClass']);
456
            $result = $ea->loadTranslations($object, $translationClass, $locale, $config['useObjectClass']);
457
            // translate object's translatable properties
458
            foreach ($config['fields'] as $field) {
459
                $translated = '';
460
                foreach ((array)$result as $entry) {
461
                    if ($entry['field'] == $field) {
462
                        $translated = $entry['content'];
463
                        break;
464
                    }
465
                }
466
                // update translation
467
                if ($translated || (!$this->translationFallback && (!isset($config['fallback'][$field]) || !$config['fallback'][$field])) || ($this->translationFallback && isset($config['fallback'][$field]) && !$config['fallback'][$field])) {
468
                    $ea->setTranslationValue($object, $field, $translated);
469
                    // ensure clean changeset
470
                    $ea->setOriginalObjectProperty($om->getUnitOfWork(), $oid, $field, $meta->getReflectionProperty($field)
0 ignored issues
show
Bug introduced by
The method getReflectionProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getReflectionClass()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

470
                    $ea->setOriginalObjectProperty($om->getUnitOfWork(), $oid, $field, $meta->/** @scrutinizer ignore-call */ getReflectionProperty($field)

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Comprehensibility Best Practice introduced by
The variable $oid does not seem to be defined for all execution paths leading up to this point.
Loading history...
471
                        ->getValue($object));
472
                }
473
            }
474
        }
475
    }
476
477
    /**
478
     * {@inheritdoc}
479
     */
480
    protected function getNamespace()
481
    {
482
        return 'Sludio\HelperBundle\Translatable';
483
    }
484
485
    /**
486
     * Validates the given locale.
487
     *
488
     * @param string $locale - locale to validate
489
     *
490
     * @throws \Gedmo\Exception\InvalidArgumentException if locale is not valid
491
     */
492
    protected function validateLocale($locale)
493
    {
494
        if (!$this->isValidLocale($locale)) {
495
            throw new InvalidArgumentException('Locale or language cannot be empty and must be set through Listener or Entity');
496
        }
497
    }
498
499
    /**
500
     * Check if the given locale is valid.
501
     *
502
     * @param string $locale - locale to check
503
     *
504
     * @return bool
505
     */
506
    private function isValidlocale($locale)
507
    {
508
        return is_string($locale) && strlen($locale);
509
    }
510
511
    /**
512
     * Creates the translation for object being flushed.
513
     *
514
     * @param TranslatableAdapter $ea
515
     * @param object              $object
516
     * @param bool                $isInsert
517
     *
518
     * @throws \UnexpectedValueException - if locale is not valid, or
519
     *                                   primary key is composite, missing or invalid
520
     */
521
    private function handleTranslatableObjectUpdate(TranslatableAdapter $ea, $object, $isInsert)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
522
    {
523
        $om = $ea->getObjectManager();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $om. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
524
        $wrapped = AbstractWrapper::wrap($object, $om);
525
        $meta = $wrapped->getMetadata();
526
        $config = $this->getConfiguration($om, $meta->name);
527
        // no need cache, metadata is loaded only once in MetadataFactoryClass
528
        $translationClass = $this->getTranslationClass($ea, $config['useObjectClass']);
529
        $translationMetadata = $om->getClassMetadata($translationClass);
530
531
        // check for the availability of the primary key
532
        $objectId = $wrapped->getIdentifier();
533
        // load the currently used locale
534
        $locale = $this->getTranslatableLocale($object, $meta, $om);
535
536
        $uow = $om->getUnitOfWork();
537
        $oid = spl_object_hash($object);
538
        $changeSet = $ea->getObjectChangeSet($uow, $object);
539
        $translatableFields = $config['fields'];
540
        foreach ($translatableFields as $field) {
541
            $wasPersistedSeparetely = false;
542
            $skip = isset($this->translatedInLocale[$oid]) && $locale === $this->translatedInLocale[$oid];
543
            $skip = $skip && !isset($changeSet[$field]) && !$this->getTranslationInDefaultLocale($oid, $field);
544
            if ($skip) {
545
                continue; // locale is same and nothing changed
546
            }
547
            $translation = null;
548
            foreach ($ea->getScheduledObjectInsertions($uow) as $trans) {
549
                if ($locale !== $this->defaultLocale && get_class($trans) === $translationClass && $trans->getLocale() === $this->defaultLocale && $trans->getField() === $field && $this->belongsToObject($ea, $trans, $object)) {
550
                    $this->setTranslationInDefaultLocale($oid, $field, $trans);
551
                    break;
552
                }
553
            }
554
555
            // lookup persisted translations
556
            foreach ($ea->getScheduledObjectInsertions($uow) as $trans) {
557
                if (get_class($trans) !== $translationClass || $trans->getLocale() !== $locale || $trans->getField() !== $field) {
558
                    continue;
559
                }
560
561
                if ($ea->usesPersonalTranslation($translationClass)) {
562
                    $wasPersistedSeparetely = $trans->getObject() === $object;
563
                } else {
564
                    $wasPersistedSeparetely = $trans->getObjectClass() === $config['useObjectClass'] && $trans->getForeignKey() === $objectId;
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $wasPersistedSeparetely ...ignKey() === $objectId), Probably Intended Meaning: ($wasPersistedSeparetely...eignKey() === $objectId
Loading history...
565
                }
566
567
                if ($wasPersistedSeparetely) {
568
                    $translation = $trans;
569
                    break;
570
                }
571
            }
572
573
            // check if translation already is created
574
            if (!$isInsert && !$translation) {
575
                $translation = $ea->findTranslation($wrapped, $locale, $field, $translationClass, $config['useObjectClass']);
576
            }
577
578
            // create new translation if translation not already created and locale is different from default locale, otherwise, we have the date in the original record
579
            $persistNewTranslation = !$translation && ($locale !== $this->defaultLocale || $this->persistDefaultLocaleTranslation);
580
            if ($persistNewTranslation) {
581
                $translation = $translationMetadata->newInstance();
0 ignored issues
show
Bug introduced by
The method newInstance() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

581
                $translation = $translationMetadata->/** @scrutinizer ignore-call */ newInstance();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
582
                $translation->setLocale($locale);
583
                $translation->setField($field);
584
                if ($ea->usesPersonalTranslation($translationClass)) {
585
                    $translation->setObject($object);
586
                } else {
587
                    $translation->setObjectClass($config['useObjectClass']);
588
                    $translation->setForeignKey($objectId);
589
                }
590
            }
591
592
            if ($translation) {
593
                // set the translated field, take value using reflection
594
                $content = $ea->getTranslationValue($object, $field);
595
                $translation->setContent($content);
596
                // check if need to update in database
597
                $transWrapper = AbstractWrapper::wrap($translation, $om);
598
                if (((is_null($content) && !$isInsert) || is_bool($content) || is_int($content) || (is_string($content) && strlen($content) > 0) || !empty($content)) && ($isInsert || !$transWrapper->getIdentifier() || isset($changeSet[$field]))) {
599
                    if ($isInsert && !$objectId && !$ea->usesPersonalTranslation($translationClass)) {
600
                        // if we do not have the primary key yet available
601
                        // keep this translation in memory to insert it later with foreign key
602
                        $this->pendingTranslationInserts[spl_object_hash($object)][] = $translation;
603
                    } else {
604
                        // persist and compute change set for translation
605
                        if ($wasPersistedSeparetely) {
606
                            $ea->recomputeSingleObjectChangeset($uow, $translationMetadata, $translation);
607
                        } else {
608
                            $om->persist($translation);
609
                            $uow->computeChangeSet($translationMetadata, $translation);
610
                        }
611
                    }
612
                }
613
            }
614
615
            if ($isInsert && $this->getTranslationInDefaultLocale($oid, $field) !== null) {
616
                // We can't rely on object field value which is created in non-default locale.
617
                // If we provide translation for default locale as well, the latter is considered to be trusted
618
                // and object content should be overridden.
619
                $wrapped->setPropertyValue($field, $this->getTranslationInDefaultLocale($oid, $field)->getContent());
620
                $ea->recomputeSingleObjectChangeset($uow, $meta, $object);
621
                $this->removeTranslationInDefaultLocale($oid, $field);
622
            }
623
        }
624
        $this->translatedInLocale[$oid] = $locale;
625
        // check if we have default translation and need to reset the translation
626
        if (!$isInsert && strlen($this->defaultLocale)) {
627
            $this->validateLocale($this->defaultLocale);
628
            $modifiedChangeSet = $changeSet;
629
            foreach ($changeSet as $field => $changes) {
630
                if (in_array($field, $translatableFields)) {
631
                    if ($locale !== $this->defaultLocale) {
632
                        $ea->setOriginalObjectProperty($uow, $oid, $field, $changes[0]);
633
                        unset($modifiedChangeSet[$field]);
634
                    }
635
                }
636
            }
637
            $ea->recomputeSingleObjectChangeset($uow, $meta, $object);
638
            // cleanup current changeset only if working in a another locale different than de default one, otherwise the changeset will always be reverted
639
            if ($locale !== $this->defaultLocale) {
640
                $ea->clearObjectChangeSet($uow, $oid);
641
                // recompute changeset only if there are changes other than reverted translations
642
                if ($modifiedChangeSet || $this->hasTranslationsInDefaultLocale($oid)) {
643
                    foreach ($modifiedChangeSet as $field => $changes) {
644
                        $ea->setOriginalObjectProperty($uow, $oid, $field, $changes[0]);
645
                    }
646
                    foreach ($translatableFields as $field) {
647
                        if ($this->getTranslationInDefaultLocale($oid, $field) !== null) {
648
                            $wrapped->setPropertyValue($field, $this->getTranslationInDefaultLocale($oid, $field)
649
                                ->getContent());
650
                            $this->removeTranslationInDefaultLocale($oid, $field);
651
                        }
652
                    }
653
                    $ea->recomputeSingleObjectChangeset($uow, $meta, $object);
654
                }
655
            }
656
        }
657
    }
658
659
    /**
660
     * Sets translation object which represents translation in default language.
661
     *
662
     * @param string $oid   hash of basic entity
663
     * @param string $field field of basic entity
664
     * @param mixed  $trans Translation object
665
     */
666
    public function setTranslationInDefaultLocale($oid, $field, $trans)
667
    {
668
        if (!isset($this->translationInDefaultLocale[$oid])) {
669
            $this->translationInDefaultLocale[$oid] = [];
670
        }
671
        $this->translationInDefaultLocale[$oid][$field] = $trans;
672
    }
673
674
    /**
675
     * @return bool
676
     */
677
    public function isSkipOnLoad()
678
    {
679
        return $this->skipOnLoad;
680
    }
681
682
    /**
683
     * Removes translation object which represents translation in default language.
684
     * This is for internal use only.
685
     *
686
     * @param string $oid   hash of the basic entity
687
     * @param string $field field of basic entity
688
     */
689
    private function removeTranslationInDefaultLocale($oid, $field)
690
    {
691
        if (isset($this->translationInDefaultLocale[$oid])) {
692
            if (isset($this->translationInDefaultLocale[$oid][$field])) {
693
                unset($this->translationInDefaultLocale[$oid][$field]);
694
            }
695
            if (!$this->translationInDefaultLocale[$oid]) {
696
                // We removed the final remaining elements from the
697
                // translationInDefaultLocale[$oid] array, so we might as well
698
                // completely remove the entry at $oid.
699
                unset($this->translationInDefaultLocale[$oid]);
700
            }
701
        }
702
    }
703
704
    /**
705
     * Gets translation object which represents translation in default language.
706
     * This is for internal use only.
707
     *
708
     * @param string $oid   hash of the basic entity
709
     * @param string $field field of basic entity
710
     *
711
     * @return mixed Returns translation object if it exists or NULL otherwise
712
     */
713
    private function getTranslationInDefaultLocale($oid, $field)
714
    {
715
        if (array_key_exists($oid, $this->translationInDefaultLocale)) {
716
            if (array_key_exists($field, $this->translationInDefaultLocale[$oid])) {
717
                $ret = $this->translationInDefaultLocale[$oid][$field];
718
            } else {
719
                $ret = null;
720
            }
721
        } else {
722
            $ret = null;
723
        }
724
725
        return $ret;
726
    }
727
728
    /**
729
     * Check if object has any translation object which represents translation in default language.
730
     * This is for internal use only.
731
     *
732
     * @param string $oid hash of the basic entity
733
     *
734
     * @return bool
735
     */
736
    public function hasTranslationsInDefaultLocale($oid)
737
    {
738
        return array_key_exists($oid, $this->translationInDefaultLocale);
739
    }
740
741
    /**
742
     * Checks if the translation entity belongs to the object in question.
743
     *
744
     * @param TranslatableAdapter $ea
745
     * @param object              $trans
746
     * @param object              $object
747
     *
748
     * @return bool
749
     */
750
    private function belongsToObject(TranslatableAdapter $ea, $trans, $object)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ea. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
751
    {
752
        if ($ea->usesPersonalTranslation(get_class($trans))) {
753
            return $trans->getObject() === $object;
754
        }
755
756
        return $trans->getForeignKey() === $object->getId() && ($trans->getObjectClass() === get_class($object));
757
    }
758
}
759