GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#45)
by
unknown
02:32
created

MultilingualBehavior::afterUpdate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 10
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 2
1
<?php
2
namespace omgdef\multilingual;
3
4
use Yii;
5
use yii\base\Behavior;
6
use yii\base\UnknownPropertyException;
7
use yii\base\InvalidConfigException;
8
use yii\db\ActiveQuery;
9
use yii\db\ActiveRecord;
10
use yii\helpers\Inflector;
11
use yii\validators\Validator;
12
13
class MultilingualBehavior extends Behavior
14
{
15
    /**
16
     * Multilingual attributes
17
     * @var array
18
     */
19
    public $attributes;
20
21
    /**
22
     * Available languages
23
     * It can be a simple array: array('fr', 'en') or an associative array: array('fr' => 'Français', 'en' => 'English')
24
     * For associative arrays, only the keys will be used.
25
     * @var array
26
     */
27
    public $languages;
28
29
    /**
30
     * @var string the default language.
31
     * Example: 'en'.
32
     */
33
    public $defaultLanguage;
34
35
    /**
36
     * @var string the name of the translation table
37
     */
38
    public $tableName;
39
40
    /**
41
     * @var string the name of translation model class.
42
     */
43
    public $langClassName;
44
45
    /**
46
     * @var string the name of the foreign key field of the translation table related to base model table.
47
     */
48
    public $langForeignKey;
49
50
    /**
51
     * @var string the prefix of the localized attributes in the lang table. Here to avoid collisions in queries.
52
     * In the translation table, the columns corresponding to the localized attributes have to be name like this: 'l_[name of the attribute]'
53
     * and the id column (primary key) like this : 'l_id'
54
     * Default to ''.
55
     */
56
    public $localizedPrefix = '';
57
58
    /**
59
     * @var string the name of the lang field of the translation table. Default to 'language'.
60
     */
61
    public $languageField = 'language';
62
63
    /**
64
     * @var boolean if this property is set to true required validators will be applied to all translation models.
65
     * Default to false.
66
     */
67
    public $requireTranslations = false;
68
69
    /**
70
     * @var boolean whether to force deletion of the associated translations when a base model is deleted.
71
     * Not needed if using foreign key with 'on delete cascade'.
72
     * Default to true.
73
     */
74
    public $forceDelete = true;
75
76
    /**
77
     * @var boolean whether to dynamically create translation model class.
78
     * If true, the translation model class will be generated on runtime with the use of the eval() function so no additional php file is needed.
79
     * See {@link createLangClass()}
80
     * Default to true.
81
     */
82
    public $dynamicLangClass = true;
83
84
    /**
85
     * @var boolean whether to abridge the language ID.
86
     * Default to true.
87
     */
88
    public $abridge = true;
89
90
    /**
91
     * @var string the name of the primary key field of the base model. Defaults to first value of Model::primaryKey.
92
     */
93
    public $ownerPrimaryKey;
94
95
    /**
96
     * @var boolean whether to check for existing translations on insert
97
     * Default to false
98
     */
99
    public $loadTranslationsOnInsert = false;
100
101
    private $currentLanguage;
102
    private $ownerClassName;
103
    private $langClassShortName;
104
    private $ownerClassShortName;
105
    private $langAttributes = [];
106
107
    /**
108
     * @var array excluded validators
109
     */
110
    private $excludedValidators = ['unique'];
111
112
    /**
113
     * @inheritdoc
114
     */
115 19
    public function events()
116
    {
117
        return [
118 19
            ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
119 19
            ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
120 19
            ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
121 19
            ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
122 19
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
123 19
        ];
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129 19
    public function attach($owner)
130
    {
131
        /** @var ActiveRecord $owner */
132 19
        parent::attach($owner);
133
134 19 View Code Duplication
        if (empty($this->languages) || !is_array($this->languages)) {
135 1
            throw new InvalidConfigException('Please specify array of available languages for the ' . get_class($this) . ' in the '
136 1
                . get_class($this->owner) . ' or in the application parameters', 101);
137
        }
138
139 19
        if (array_values($this->languages) !== $this->languages) { //associative array
140 19
            $this->languages = array_keys($this->languages);
141 19
        }
142
143 19
        $this->languages = array_unique(array_map(function ($language) {
144 19
            return $this->getLanguageBaseName($language);
145 19
        }, $this->languages));
146
147 19
        if (!$this->defaultLanguage) {
148 1
            $this->defaultLanguage = isset(Yii::$app->params['defaultLanguage']) && Yii::$app->params['defaultLanguage'] ?
149 1
                Yii::$app->params['defaultLanguage'] : Yii::$app->language;
150 1
        }
151
152 19
        $this->defaultLanguage = $this->getLanguageBaseName($this->defaultLanguage);
153
154 19
        if (!$this->currentLanguage) {
155 19
            $this->currentLanguage = $this->getLanguageBaseName(Yii::$app->language);
156 19
        }
157
158 19 View Code Duplication
        if (empty($this->attributes) || !is_array($this->attributes)) {
159 1
            throw new InvalidConfigException('Please specify multilingual attributes for the ' . get_class($this) . ' in the '
160 1
                . get_class($this->owner), 103);
161
        }
162
163 19
        if (!$this->langClassName) {
164 19
            $this->langClassName = get_class($this->owner) . 'Lang';
165 19
        }
166
167 19
        $this->langClassShortName = $this->getShortClassName($this->langClassName);
168 19
        $this->ownerClassName = get_class($this->owner);
169 19
        $this->ownerClassShortName = $this->getShortClassName($this->ownerClassName);
170
171
        /** @var ActiveRecord $className */
172 19
        $className = $this->ownerClassName;
173 19
        if (!isset($this->ownerPrimaryKey)) {
174 19
            $this->ownerPrimaryKey = $className::primaryKey()[0];
175 19
        }
176
177 19
        if (!isset($this->langForeignKey)) {
178 1
            throw new InvalidConfigException('Please specify langForeignKey for the ' . get_class($this) . ' in the '
179 1
                . get_class($this->owner), 105);
180
        }
181
182 19
        $rules = $owner->rules();
183 19
        $validators = $owner->getValidators();
184
185 19
        foreach ($rules as $rule) {
186 19
            if (in_array($rule[1], $this->excludedValidators))
187 19
                continue;
188
189 19
            $rule_attributes = is_array($rule[0]) ? $rule[0] : [$rule[0]];
190 19
            $attributes = array_intersect($this->attributes, $rule_attributes);
191
192 19
            if (empty($attributes))
193 19
                continue;
194
195 19
            $rule_attributes = [];
196 19
            foreach ($attributes as $key => $attribute) {
197 19
                foreach ($this->languages as $language)
198 19
                    if ($language != $this->defaultLanguage)
199 19
                        $rule_attributes[] = $this->getAttributeName($attribute, $language);
200 19
            }
201
202 19
            if (isset($rule['skipOnEmpty']) && !$rule['skipOnEmpty'])
203 19
                $rule['skipOnEmpty'] = !$this->requireTranslations;
204
205 19
            $params = array_slice($rule, 2);
206
207 19
            if ($rule[1] !== 'required' || $this->requireTranslations) {
208 19
                $validators[] = Validator::createValidator($rule[1], $owner, $rule_attributes, $params);
209 19
            } elseif ($rule[1] === 'required') {
210 18
                $validators[] = Validator::createValidator('safe', $owner, $rule_attributes, $params);
211 18
            }
212 19
        }
213
214 19
        if ($this->dynamicLangClass) {
215 19
            $this->createLangClass();
216 19
        }
217
218 19
        $translation = new $this->langClassName;
219 19 View Code Duplication
        foreach ($this->languages as $lang) {
220 19
            foreach ($this->attributes as $attribute) {
221 19
                $attributeName = $this->localizedPrefix . $attribute;
222 19
                $this->setLangAttribute($this->getAttributeName($attribute, $lang), $translation->{$attributeName});
223 19
                if ($lang == $this->defaultLanguage) {
224 19
                    $this->setLangAttribute($attribute, $translation->{$attributeName});
225 19
                }
226 19
            }
227 19
        }
228 19
    }
229
230 19
    public function createLangClass()
231
    {
232 19
        if (!class_exists($this->langClassName, false)) {
233 3
            $namespace = substr($this->langClassName, 0, strrpos($this->langClassName, '\\'));
234
            eval('
235 3
            namespace ' . $namespace . ';
236
            use yii\db\ActiveRecord;
237 3
            class ' . $this->langClassShortName . ' extends ActiveRecord
238
            {
239
                public static function tableName()
240
                {
241 3
                    return \'' . $this->tableName . '\';
242
                }
243 3
            }');
244 3
        }
245 19
    }
246
247
    /**
248
     * Relation to model translations
249
     * @return ActiveQuery
250
     */
251 8
    public function getTranslations()
252
    {
253 8
        return $this->owner->hasMany($this->langClassName, [$this->langForeignKey => $this->ownerPrimaryKey]);
254
    }
255
256
    /**
257
     * Relation to model translation
258
     * @param $language
259
     * @return ActiveQuery
260
     */
261 8
    public function getTranslation($language = null)
262
    {
263 8
        $language = $language ?: $this->getCurrentLanguage();
264 8
        return $this->owner->hasOne($this->langClassName, [$this->langForeignKey => $this->ownerPrimaryKey])
265 8
            ->where([$this->languageField => $language]);
266
    }
267
268
    /**
269
     * Handle 'beforeValidate' event of the owner.
270
     */
271 11
    public function beforeValidate()
272
    {
273 11
        foreach ($this->attributes as $attribute) {
274 11
            $this->setLangAttribute($this->getAttributeName($attribute, $this->defaultLanguage), $this->getLangAttribute($attribute));
275 11
        }
276 11
    }
277
278
    /**
279
     * Handle 'afterFind' event of the owner.
280
     */
281 13
    public function afterFind()
282
    {
283
        /** @var ActiveRecord $owner */
284 13
        $owner = $this->owner;
285
286 13
        if ($owner->isRelationPopulated('translations') && $related = $owner->getRelatedRecords()['translations']) {
287 6
            $translations = $this->indexByLanguage($related);
288 6
            foreach ($this->languages as $lang) {
289 6
                foreach ($this->attributes as $attribute) {
290 6 View Code Duplication
                    foreach ($translations as $translation) {
291 6
                        if ($this->getLanguageBaseName($translation->{$this->languageField}) == $lang) {
292 6
                            $attributeName = $this->localizedPrefix . $attribute;
293 6
                            $this->setLangAttribute($this->getAttributeName($attribute, $lang), $translation->{$attributeName});
294
295 6
                            if ($lang == $this->defaultLanguage) {
296 6
                                $this->setLangAttribute($attribute, $translation->{$attributeName});
297 6
                            }
298 6
                        }
299 6
                    }
300 6
                }
301 6
            }
302 6
        } else {
303 8
            if (!$owner->isRelationPopulated('translation')) {
304 6
                $owner->translation;
305 6
            }
306
307 8
            $translation = $owner->getRelatedRecords()['translation'];
308 8
            if ($translation) {
309 7
                foreach ($this->attributes as $attribute) {
310 7
                    $attribute_name = $this->localizedPrefix . $attribute;
311 7
                    $owner->setLangAttribute($attribute, $translation->$attribute_name);
312 7
                }
313 7
            }
314
        }
315
316 13
        foreach ($this->attributes as $attribute) {
317 13
            if ($owner->hasAttribute($attribute) && $this->getLangAttribute($attribute)) {
318 1
                $owner->setAttribute($attribute, $this->getLangAttribute($attribute));
319 1
            }
320 13
        }
321 13
    }
322
323
    /**
324
     * Handle 'afterInsert' event of the owner.
325
     */
326 5
    public function afterInsert()
327
    {
328 5
        $translations = [];
329 5
        if ($this->loadTranslationsOnInsert)
330 5
        {
331
            $relations = $this->owner->getRelatedRecords();
332
            if (isset($relations['translations']))
333
            {
334
                $translations = $this->indexByLanguage(['translations']);
335
            }
336
        }
337 5
        $this->saveTranslations($translations);
338 5
    }
339
340
    /**
341
     * Handle 'afterUpdate' event of the owner.
342
     */
343 5
    public function afterUpdate()
344
    {
345
        /** @var ActiveRecord $owner */
346 5
        $owner = $this->owner;
347
348 5
        if ($owner->isRelationPopulated('translations')) {
349 3
            $translations = $this->indexByLanguage($owner->getRelatedRecords()['translations']);
350 3
            $this->saveTranslations($translations);
351 3
        }
352 5
    }
353
354
    /**
355
     * Handle 'afterDelete' event of the owner.
356
     */
357 2
    public function afterDelete()
358
    {
359 2
        if ($this->forceDelete) {
360
            /** @var ActiveRecord $owner */
361 2
            $owner = $this->owner;
362 2
            $owner->unlinkAll('translations', true);
363 2
        }
364 2
    }
365
366
    /**
367
     * @param array $translations
368
     */
369 8
    private function saveTranslations($translations = [])
370
    {
371
        /** @var ActiveRecord $owner */
372 8
        $owner = $this->owner;
373
374 8
        foreach ($this->languages as $lang) {
375 8
            $defaultLanguage = $lang == $this->defaultLanguage;
376
377 8
            if (!isset($translations[$lang])) {
378
                /** @var ActiveRecord $translation */
379 5
                $translation = new $this->langClassName;
380 5
                $translation->{$this->languageField} = $lang;
381 5
                $translation->{$this->langForeignKey} = $owner->getAttribute($this->ownerPrimaryKey);
382 5
            } else {
383 3
                $translation = $translations[$lang];
384
            }
385
386 8
            $save = false;
387 8
            foreach ($this->attributes as $attribute) {
388 8
                $value = $defaultLanguage ? $owner->$attribute : $this->getLangAttribute($this->getAttributeName($attribute, $lang));
389
390 8
                if ($value !== null) {
391 8
                    $field = $this->localizedPrefix . $attribute;
392 8
                    $translation->$field = $value;
393 8
                    $save = true;
394 8
                }
395 8
            }
396
397 8
            if ($translation->isNewRecord && !$save)
398 8
                continue;
399
400 8
            $translation->save();
401 8
        }
402 8
    }
403
404
    /**
405
     * @inheritdoc
406
     */
407 18
    public function canGetProperty($name, $checkVars = true)
408
    {
409 18
        return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)
410 18
        || $this->hasLangAttribute($name);
411
    }
412
413
    /**
414
     * @inheritdoc
415
     */
416 10
    public function canSetProperty($name, $checkVars = true)
417
    {
418 10
        return $this->hasLangAttribute($name);
419
    }
420
421
    /**
422
     * @inheritdoc
423
     */
424 17
    public function __get($name)
425
    {
426
        try {
427 17
            return parent::__get($name);
428 15
        } catch (UnknownPropertyException $e) {
429 15
            if ($this->hasLangAttribute($name)) return $this->getLangAttribute($name);
430
            // @codeCoverageIgnoreStart
431
            else throw $e;
432
            // @codeCoverageIgnoreEnd
433
        }
434
    }
435
436
    /**
437
     * @inheritdoc
438
     */
439 10
    public function __set($name, $value)
440
    {
441
        try {
442 10
            parent::__set($name, $value);
443 10
        } catch (UnknownPropertyException $e) {
444 10
            if ($this->hasLangAttribute($name)) $this->setLangAttribute($name, $value);
445
            // @codeCoverageIgnoreStart
446
            else throw $e;
447
            // @codeCoverageIgnoreEnd
448
        }
449 10
    }
450
451
    /**
452
     * @inheritdoc
453
     * @codeCoverageIgnore
454
     */
455
    public function __isset($name)
456
    {
457
        if (!parent::__isset($name)) {
458
            return $this->hasLangAttribute($name);
459
        } else {
460
            return true;
461
        }
462
    }
463
464
    /**
465
     * Whether an attribute exists
466
     * @param string $name the name of the attribute
467
     * @return boolean
468
     */
469 15
    public function hasLangAttribute($name)
470
    {
471 15
        return array_key_exists($name, $this->langAttributes);
472
    }
473
474
    /**
475
     * @param string $name the name of the attribute
476
     * @return string the attribute value
477
     */
478 15
    public function getLangAttribute($name)
479
    {
480 15
        return $this->hasLangAttribute($name) ? $this->langAttributes[$name] : null;
481
    }
482
483
    /**
484
     * @param string $name the name of the attribute
485
     * @param string $value the value of the attribute
486
     */
487 19
    public function setLangAttribute($name, $value)
488
    {
489 19
        $this->langAttributes[$name] = $value;
490 19
    }
491
492
    /**
493
     * @param $records
494
     * @return array
495
     */
496 6
    protected function indexByLanguage($records)
497
    {
498 6
        $sorted = array();
499 6
        foreach ($records as $record) {
500 6
            $sorted[$record->{$this->languageField}] = $record;
501 6
        }
502 6
        unset($records);
503 6
        return $sorted;
504
    }
505
506
    /**
507
     * @param $language
508
     * @return string
509
     */
510 19
    protected function getLanguageBaseName($language)
511
    {
512 19
        return $this->abridge ? substr($language, 0, 2) : $language;
513
    }
514
515
    /**
516
     * @param string $className
517
     * @return string
518
     */
519 19
    private function getShortClassName($className)
520
    {
521 19
        return substr($className, strrpos($className, '\\') + 1);
522
    }
523
524
    /**
525
     * @return mixed|string
526
     */
527 8
    public function getCurrentLanguage()
528
    {
529 8
        return $this->currentLanguage;
530
    }
531
532
    /**
533
     * @param $attribute
534
     * @param $language
535
     * @return string
536
     */
537 19
    protected function getAttributeName($attribute, $language)
538
    {
539 19
        $language = $this->abridge ? $language : Inflector::camel2id(Inflector::id2camel($language), "_");
540 19
        return $attribute . "_" . $language;
541
    }
542
}
543