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
05:09
created

MultilingualBehavior::hasLangAttribute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
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 $checkOnInsert = 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 9
    public function events()
116
    {
117
        return [
118 9
            ActiveRecord::EVENT_AFTER_FIND => 'afterFind',
119 9
            ActiveRecord::EVENT_AFTER_UPDATE => 'afterUpdate',
120 9
            ActiveRecord::EVENT_AFTER_INSERT => 'afterInsert',
121 9
            ActiveRecord::EVENT_AFTER_DELETE => 'afterDelete',
122 9
            ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
123
        ];
124
    }
125
126
    /**
127
     * @inheritdoc
128
     */
129 9
    public function attach($owner)
130
    {
131
        /** @var ActiveRecord $owner */
132
        parent::attach($owner);
133
134 View Code Duplication
        if (empty($this->languages) || !is_array($this->languages)) {
135
            throw new InvalidConfigException('Please specify array of available languages for the ' . get_class($this) . ' in the '
136
                . get_class($this->owner) . ' or in the application parameters', 101);
137
        }
138
139
        if (array_values($this->languages) !== $this->languages) { //associative array
140
            $this->languages = array_keys($this->languages);
141
        }
142
143
        $this->languages = array_unique(array_map(function ($language) {
144
            return $this->getLanguageBaseName($language);
145
        }, $this->languages));
146
147 9
        if (!$this->defaultLanguage) {
148
            $this->defaultLanguage = isset(Yii::$app->params['defaultLanguage']) && Yii::$app->params['defaultLanguage'] ?
149
                Yii::$app->params['defaultLanguage'] : Yii::$app->language;
150
        }
151
152
        $this->defaultLanguage = $this->getLanguageBaseName($this->defaultLanguage);
153
154 9
        if (!$this->currentLanguage) {
155
            $this->currentLanguage = $this->getLanguageBaseName(Yii::$app->language);
156
        }
157
158 View Code Duplication
        if (empty($this->attributes) || !is_array($this->attributes)) {
159
            throw new InvalidConfigException('Please specify multilingual attributes for the ' . get_class($this) . ' in the '
160
                . get_class($this->owner), 103);
161
        }
162
163 9
        if (!$this->langClassName) {
164
            $this->langClassName = get_class($this->owner) . 'Lang';
165
        }
166
167
        $this->langClassShortName = $this->getShortClassName($this->langClassName);
168
        $this->ownerClassName = get_class($this->owner);
169
        $this->ownerClassShortName = $this->getShortClassName($this->ownerClassName);
170
171
        /** @var ActiveRecord $className */
172 9
        $className = $this->ownerClassName;
173 9
        if (!isset($this->ownerPrimaryKey)) {
174
            $this->ownerPrimaryKey = $className::primaryKey()[0];
175
        }
176
177 9
        if (!isset($this->langForeignKey)) {
178
            throw new InvalidConfigException('Please specify langForeignKey for the ' . get_class($this) . ' in the '
179
                . get_class($this->owner), 105);
180
        }
181
182
        $rules = $owner->rules();
183
        $validators = $owner->getValidators();
184
185
        foreach ($rules as $rule) {
186
            if (in_array($rule[1], $this->excludedValidators))
187
                continue;
188
189
            $rule_attributes = is_array($rule[0]) ? $rule[0] : [$rule[0]];
190
            $attributes = array_intersect($this->attributes, $rule_attributes);
191
192
            if (empty($attributes))
193
                continue;
194
195
            $rule_attributes = [];
196
            foreach ($attributes as $key => $attribute) {
197
                foreach ($this->languages as $language)
198
                    if ($language != $this->defaultLanguage)
199
                        $rule_attributes[] = $this->getAttributeName($attribute, $language);
200
            }
201
202
            if (isset($rule['skipOnEmpty']) && !$rule['skipOnEmpty'])
203
                $rule['skipOnEmpty'] = !$this->requireTranslations;
204
205
            $params = array_slice($rule, 2);
206
207
            if ($rule[1] !== 'required' || $this->requireTranslations) {
208
                $validators[] = Validator::createValidator($rule[1], $owner, $rule_attributes, $params);
209 8
            } elseif ($rule[1] === 'required') {
210
                $validators[] = Validator::createValidator('safe', $owner, $rule_attributes, $params);
211 8
            }
212
        }
213
214 9
        if ($this->dynamicLangClass) {
215
            $this->createLangClass();
216
        }
217
218
        $translation = new $this->langClassName;
219 9 View Code Duplication
        foreach ($this->languages as $lang) {
220
            foreach ($this->attributes as $attribute) {
221
                $attributeName = $this->localizedPrefix . $attribute;
222
                $this->setLangAttribute($this->getAttributeName($attribute, $lang), $translation->{$attributeName});
223
                if ($lang == $this->defaultLanguage) {
224
                    $this->setLangAttribute($attribute, $translation->{$attributeName});
225
                }
226
            }
227
        }
228
    }
229
230 9
    public function createLangClass()
231
    {
232
        if (!class_exists($this->langClassName, false)) {
233
            $namespace = substr($this->langClassName, 0, strrpos($this->langClassName, '\\'));
234
            eval('
235
            namespace ' . $namespace . ';
236
            use yii\db\ActiveRecord;
237
            class ' . $this->langClassShortName . ' extends ActiveRecord
238
            {
239
                public static function tableName()
240
                {
241
                    return \'' . $this->tableName . '\';
242
                }
243
            }');
244
        }
245 9
    }
246
247
    /**
248
     * Relation to model translations
249
     * @return ActiveQuery
250
     */
251
    public function getTranslations()
252
    {
253
        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 5
    public function getTranslation($language = null)
262
    {
263
        $language = $language ?: $this->getCurrentLanguage();
264 5
        return $this->owner->hasOne($this->langClassName, [$this->langForeignKey => $this->ownerPrimaryKey])
265
            ->where([$this->languageField => $language]);
266 5
    }
267
268
    /**
269
     * Handle 'beforeValidate' event of the owner.
270
     */
271 10
    public function beforeValidate()
272
    {
273 10
        foreach ($this->attributes as $attribute) {
274
            $this->setLangAttribute($this->getAttributeName($attribute, $this->defaultLanguage), $this->getLangAttribute($attribute));
275
        }
276 10
    }
277
278
    /**
279
     * Handle 'afterFind' event of the owner.
280
     */
281 8
    public function afterFind()
282
    {
283
        /** @var ActiveRecord $owner */
284 8
        $owner = $this->owner;
285
286
        if ($owner->isRelationPopulated('translations') && $related = $owner->getRelatedRecords()['translations']) {
287
            $translations = $this->indexByLanguage($related);
288 4
            foreach ($this->languages as $lang) {
289
                foreach ($this->attributes as $attribute) {
290 View Code Duplication
                    foreach ($translations as $translation) {
291
                        if ($this->getLanguageBaseName($translation->{$this->languageField}) == $lang) {
292
                            $attributeName = $this->localizedPrefix . $attribute;
293
                            $this->setLangAttribute($this->getAttributeName($attribute, $lang), $translation->{$attributeName});
294
295
                            if ($lang == $this->defaultLanguage) {
296
                                $this->setLangAttribute($attribute, $translation->{$attributeName});
297
                            }
298
                        }
299
                    }
300
                }
301
            }
302
        } else {
303
            if (!$owner->isRelationPopulated('translation')) {
304
                $owner->translation;
305
            }
306
307
            $translation = $owner->getRelatedRecords()['translation'];
308 5
            if ($translation) {
309 4
                foreach ($this->attributes as $attribute) {
310
                    $attribute_name = $this->localizedPrefix . $attribute;
311
                    $owner->setLangAttribute($attribute, $translation->$attribute_name);
312
                }
313
            }
314 4
        }
315
316 8
        foreach ($this->attributes as $attribute) {
317
            if ($owner->hasAttribute($attribute) && $this->getLangAttribute($attribute)) {
318
                $owner->setAttribute($attribute, $this->getLangAttribute($attribute));
319
            }
320
        }
321 8
    }
322
323
    /**
324
     * Handle 'afterInsert' event of the owner.
325
     */
326 4 View Code Duplication
    public function afterInsert()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
327
    {
328 4
        $translations = [];
329 4
        if ($this->checkOnInsert)
330
        {
331
            $translations = $this->indexByLanguage($this->owner->getRelatedRecords()['translations']);
332
        }
333
        $this->saveTranslations($translations);
334 4
    }
335
336
    /**
337
     * Handle 'afterUpdate' event of the owner.
338
     */
339 5 View Code Duplication
    public function afterUpdate()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
340
    {
341
        /** @var ActiveRecord $owner */
342 5
        $owner = $this->owner;
343
344
        if ($owner->isRelationPopulated('translations')) {
345
            $translations = $this->indexByLanguage($owner->getRelatedRecords()['translations']);
346
            $this->saveTranslations($translations);
347
        }
348 5
    }
349
350
    /**
351
     * Handle 'afterDelete' event of the owner.
352
     */
353 2
    public function afterDelete()
354
    {
355 2
        if ($this->forceDelete) {
356
            /** @var ActiveRecord $owner */
357 2
            $owner = $this->owner;
358
            $owner->unlinkAll('translations', true);
359
        }
360 2
    }
361
362
    /**
363
     * @param array $translations
364
     */
365 7
    private function saveTranslations($translations = [])
366
    {
367
        /** @var ActiveRecord $owner */
368 7
        $owner = $this->owner;
369
370 7
        foreach ($this->languages as $lang) {
371
            $defaultLanguage = $lang == $this->defaultLanguage;
372
373
            if (!isset($translations[$lang])) {
374
                /** @var ActiveRecord $translation */
375
                $translation = new $this->langClassName;
376
                $translation->{$this->languageField} = $lang;
377
                $translation->{$this->langForeignKey} = $owner->getAttribute($this->ownerPrimaryKey);
378
            } else {
379
                $translation = $translations[$lang];
380
            }
381
382
            $save = false;
383
            foreach ($this->attributes as $attribute) {
384
                $value = $defaultLanguage ? $owner->$attribute : $this->getLangAttribute($this->getAttributeName($attribute, $lang));
385
386
                if ($value !== null) {
387
                    $field = $this->localizedPrefix . $attribute;
388
                    $translation->$field = $value;
389
                    $save = true;
390
                }
391
            }
392
393
            if ($translation->isNewRecord && !$save)
394 3
                continue;
395
396
            $translation->save();
397
        }
398 7
    }
399
400
    /**
401
     * @inheritdoc
402
     */
403 3
    public function canGetProperty($name, $checkVars = true)
404
    {
405
        return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)
406 3
        || $this->hasLangAttribute($name);
407 3
    }
408
409
    /**
410
     * @inheritdoc
411
     */
412
    public function canSetProperty($name, $checkVars = true)
413
    {
414
        return $this->hasLangAttribute($name);
415
    }
416
417
    /**
418
     * @inheritdoc
419
     */
420
    public function __get($name)
421
    {
422
        try {
423
            return parent::__get($name);
424
        } catch (UnknownPropertyException $e) {
425
            if ($this->hasLangAttribute($name)) return $this->getLangAttribute($name);
426
            // @codeCoverageIgnoreStart
427
            else throw $e;
428
            // @codeCoverageIgnoreEnd
429
        }
430
    }
431
432
    /**
433
     * @inheritdoc
434
     */
435
    public function __set($name, $value)
436
    {
437
        try {
438
            parent::__set($name, $value);
439
        } catch (UnknownPropertyException $e) {
440
            if ($this->hasLangAttribute($name)) $this->setLangAttribute($name, $value);
441
            // @codeCoverageIgnoreStart
442
            else throw $e;
443
            // @codeCoverageIgnoreEnd
444
        }
445
    }
446
447
    /**
448
     * @inheritdoc
449
     * @codeCoverageIgnore
450
     */
451
    public function __isset($name)
452
    {
453
        if (!parent::__isset($name)) {
454
            return $this->hasLangAttribute($name);
455
        } else {
456
            return true;
457
        }
458
    }
459
460
    /**
461
     * Whether an attribute exists
462
     * @param string $name the name of the attribute
463
     * @return boolean
464
     */
465
    public function hasLangAttribute($name)
466
    {
467
        return array_key_exists($name, $this->langAttributes);
468
    }
469
470
    /**
471
     * @param string $name the name of the attribute
472
     * @return string the attribute value
473
     */
474
    public function getLangAttribute($name)
475
    {
476
        return $this->hasLangAttribute($name) ? $this->langAttributes[$name] : null;
477
    }
478
479
    /**
480
     * @param string $name the name of the attribute
481
     * @param string $value the value of the attribute
482
     */
483
    public function setLangAttribute($name, $value)
484
    {
485
        $this->langAttributes[$name] = $value;
486
    }
487
488
    /**
489
     * @param $records
490
     * @return array
491
     */
492 1
    protected function indexByLanguage($records)
493
    {
494 1
        $sorted = array();
495
        foreach ($records as $record) {
496
            $sorted[$record->{$this->languageField}] = $record;
497
        }
498 1
        unset($records);
499 1
        return $sorted;
500
    }
501
502
    /**
503
     * @param $language
504
     * @return string
505
     */
506
    protected function getLanguageBaseName($language)
507
    {
508
        return $this->abridge ? substr($language, 0, 2) : $language;
509
    }
510
511
    /**
512
     * @param string $className
513
     * @return string
514
     */
515
    private function getShortClassName($className)
516
    {
517
        return substr($className, strrpos($className, '\\') + 1);
518
    }
519
520
    /**
521
     * @return mixed|string
522
     */
523 5
    public function getCurrentLanguage()
524
    {
525 5
        return $this->currentLanguage;
526
    }
527
528
    /**
529
     * @param $attribute
530
     * @param $language
531
     * @return string
532
     */
533
    protected function getAttributeName($attribute, $language)
534
    {
535
        $language = $this->abridge ? $language : Inflector::camel2id(Inflector::id2camel($language), "_");
536
        return $attribute . "_" . $language;
537
    }
538
}
539