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.

MultilingualBehavior::afterDelete()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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