Completed
Push — master ( 0c59ec...7bdd51 )
by Nekrasov
02:20
created

ElementModel::constructPropertyValuesForSave()   D

Complexity

Conditions 23
Paths 39

Size

Total Lines 69

Duplication

Lines 14
Ratio 20.29 %

Importance

Changes 0
Metric Value
dl 14
loc 69
rs 4.1666
c 0
b 0
f 0
cc 23
nc 39
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Arrilot\BitrixModels\Models;
4
5
use Arrilot\BitrixModels\Exceptions\ExceptionFromBitrix;
6
use Arrilot\BitrixModels\Queries\ElementQuery;
7
use CIBlock;
8
use Illuminate\Support\Collection;
9
use LogicException;
10
11
/**
12
 * ElementQuery methods
13
 * @method static ElementQuery groupBy($value)
14
 * @method static static getByCode(string $code)
15
 * @method static static getByExternalId(string $id)
16
 *
17
 * Base Query methods
18
 * @method static Collection|static[] getList()
19
 * @method static static first()
20
 * @method static static getById(int $id)
21
 * @method static ElementQuery sort(string|array $by, string $order='ASC')
22
 * @method static ElementQuery order(string|array $by, string $order='ASC') // same as sort()
23
 * @method static ElementQuery filter(array $filter)
24
 * @method static ElementQuery addFilter(array $filters)
25
 * @method static ElementQuery resetFilter()
26
 * @method static ElementQuery navigation(array $filter)
27
 * @method static ElementQuery select($value)
28
 * @method static ElementQuery keyBy(string $value)
29
 * @method static ElementQuery limit(int $value)
30
 * @method static ElementQuery offset(int $value)
31
 * @method static ElementQuery page(int $num)
32
 * @method static ElementQuery take(int $value) // same as limit()
33
 * @method static ElementQuery forPage(int $page, int $perPage=15)
34
 * @method static \Illuminate\Pagination\LengthAwarePaginator paginate(int $perPage = 15, string $pageName = 'page')
35
 * @method static \Illuminate\Pagination\Paginator simplePaginate(int $perPage = 15, string $pageName = 'page')
36
 * @method static ElementQuery stopQuery()
37
 * @method static ElementQuery cache(float|int $minutes)
38
 *
39
 * Scopes
40
 * @method static ElementQuery active()
41
 * @method static ElementQuery sortByDate(string $sort = 'DESC')
42
 * @method static ElementQuery fromSectionWithId(int $id)
43
 * @method static ElementQuery fromSectionWithCode(string $code)
44
 */
45
class ElementModel extends BitrixModel
46
{
47
    /**
48
     * Corresponding IBLOCK_ID
49
     *
50
     * @var int
51
     */
52
    const IBLOCK_ID = null;
53
54
    /**
55
     * IBLOCK version (1 or 2)
56
     *
57
     * @var int
58
     */
59
    const IBLOCK_VERSION = 2;
60
61
    /**
62
     * Bitrix entity object.
63
     *
64
     * @var object
65
     */
66
    public static $bxObject;
67
68
    /**
69
     * Corresponding object class name.
70
     *
71
     * @var string
72
     */
73
    protected static $objectClass = 'CIBlockElement';
74
75
    /**
76
     * Have sections been already fetched from DB?
77
     *
78
     * @var bool
79
     */
80
    protected $sectionsAreFetched = false;
81
82
    /**
83
     * Log in Bitrix workflow ($bWorkFlow for CIBlockElement::Add/Update).
84
     *
85
     * @var bool
86
     */
87
    protected static $workFlow = false;
88
89
    /**
90
     * Update search after each create or update ($bUpdateSearch for CIBlockElement::Add/Update).
91
     *
92
     * @var bool
93
     */
94
    protected static $updateSearch = true;
95
96
    /**
97
     * Resize pictures during add/update ($bResizePictures for CIBlockElement::Add/Update).
98
     *
99
     * @var bool
100
     */
101
    protected static $resizePictures = false;
102
103
    /**
104
     * Getter for corresponding iblock id.
105
     *
106
     * @throws LogicException
107
     *
108
     * @return int
109
     */
110
    public static function iblockId()
111
    {
112
        $id = static::IBLOCK_ID;
113
        if (!$id) {
114
            throw new LogicException('You must set IBLOCK_ID constant inside a model or override iblockId() method');
115
        }
116
        
117
        return $id;
118
    }
119
    
120
    /**
121
     * Create new item in database.
122
     *
123
     * @param $fields
124
     *
125
     * @throws LogicException
126
     *
127
     * @return static|bool
128
     * @throws ExceptionFromBitrix
129
     */
130
    public static function create($fields)
131
    {
132
        if (!isset($fields['IBLOCK_ID'])) {
133
            $fields['IBLOCK_ID'] = static::iblockId();
134
        }
135
136
        return static::internalCreate($fields);
137
    }
138
139
    public static function internalDirectCreate($bxObject, $fields)
140
    {
141
        return $bxObject->add($fields, static::$workFlow, static::$updateSearch, static::$resizePictures);
142
    }
143
144
    /**
145
     * Corresponding section model full qualified class name.
146
     * MUST be overridden if you are going to use section model for this iblock.
147
     *
148
     * @throws LogicException
149
     *
150
     * @return string
151
     */
152
    public static function sectionModel()
153
    {
154
        throw new LogicException('public static function sectionModel() MUST be overridden');
155
    }
156
157
    /**
158
     * Instantiate a query object for the model.
159
     *
160
     * @return ElementQuery
161
     */
162
    public static function query()
163
    {
164
        return new ElementQuery(static::instantiateObject(), get_called_class());
165
    }
166
167
    /**
168
     * Scope to sort by date.
169
     *
170
     * @param ElementQuery $query
171
     * @param string       $sort
172
     *
173
     * @return ElementQuery
174
     */
175
    public function scopeSortByDate($query, $sort = 'DESC')
176
    {
177
        return $query->sort(['ACTIVE_FROM' => $sort]);
178
    }
179
180
    /**
181
     * Scope to get only items from a given section.
182
     *
183
     * @param ElementQuery $query
184
     * @param mixed        $id
185
     *
186
     * @return ElementQuery
187
     */
188
    public function scopeFromSectionWithId($query, $id)
189
    {
190
        $query->filter['SECTION_ID'] = $id;
191
192
        return $query;
193
    }
194
195
    /**
196
     * Scope to get only items from a given section.
197
     *
198
     * @param ElementQuery $query
199
     * @param string       $code
200
     *
201
     * @return ElementQuery
202
     */
203
    public function scopeFromSectionWithCode($query, $code)
204
    {
205
        $query->filter['SECTION_CODE'] = $code;
206
207
        return $query;
208
    }
209
210
    /**
211
     * Fill extra fields when $this->field is called.
212
     *
213
     * @return null
214
     */
215
    protected function afterFill()
216
    {
217
        $this->normalizePropertyFormat();
218
    }
219
220
    /**
221
     * Load all model attributes from cache or database.
222
     *
223
     * @return $this
224
     */
225
    public function load()
226
    {
227
        $this->getFields();
228
229
        return $this;
230
    }
231
232
    /**
233
     * Get element's sections from cache or database.
234
     *
235
     * @return array
236
     */
237
    public function getSections()
238
    {
239
        if ($this->sectionsAreFetched) {
240
            return $this->fields['IBLOCK_SECTION'];
241
        }
242
243
        return $this->refreshSections();
244
    }
245
246
    /**
247
     * Refresh model from database and place data to $this->fields.
248
     *
249
     * @return array
250
     */
251
    public function refresh()
252
    {
253
        return $this->refreshFields();
254
    }
255
256
    /**
257
     * Refresh element's fields and save them to a class field.
258
     *
259
     * @return array
260
     */
261 View Code Duplication
    public function refreshFields()
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...
262
    {
263
        if ($this->id === null) {
264
            $this->original = [];
265
            return $this->fields = [];
266
        }
267
268
        $sectionsBackup = isset($this->fields['IBLOCK_SECTION']) ? $this->fields['IBLOCK_SECTION'] : null;
269
270
        $this->fields = static::query()->getById($this->id)->fields;
271
272
        if (!empty($sectionsBackup)) {
273
            $this->fields['IBLOCK_SECTION'] = $sectionsBackup;
274
        }
275
276
        $this->fieldsAreFetched = true;
277
278
        $this->original = $this->fields;
279
280
        return $this->fields;
281
    }
282
283
    /**
284
     * Refresh element's sections and save them to a class field.
285
     *
286
     * @return array
287
     */
288
    public function refreshSections()
289
    {
290
        if ($this->id === null) {
291
            return [];
292
        }
293
294
        $this->fields['IBLOCK_SECTION'] = [];
295
        $dbSections = static::$bxObject->getElementGroups($this->id, true);
296
        while ($section = $dbSections->Fetch()) {
297
            $this->fields['IBLOCK_SECTION'][] = $section;
298
        }
299
300
        $this->sectionsAreFetched = true;
301
302
        return $this->fields['IBLOCK_SECTION'];
303
    }
304
305
    /**
306
     * @deprecated in favour of `->section()`
307
     * Get element direct section as ID or array of fields.
308
     *
309
     * @param bool $load
310
     *
311
     * @return false|int|array
312
     */
313
    public function getSection($load = false)
314
    {
315
        $fields = $this->getFields();
316
        if (!$load) {
317
            return $fields['IBLOCK_SECTION_ID'];
318
        }
319
320
        /** @var SectionModel $sectionModel */
321
        $sectionModel = static::sectionModel();
322
        if (!$fields['IBLOCK_SECTION_ID']) {
323
            return false;
324
        }
325
326
        return $sectionModel::query()->getById($fields['IBLOCK_SECTION_ID'])->toArray();
327
    }
328
329
    /**
330
     * Get element direct section as model object.
331
     *
332
     * @param bool $load
333
     *
334
     * @return false|SectionModel
335
     */
336
    public function section($load = false)
337
    {
338
        $fields = $this->getFields();
339
340
        /** @var SectionModel $sectionModel */
341
        $sectionModel = static::sectionModel();
342
343
        return $load
344
            ? $sectionModel::query()->getById($fields['IBLOCK_SECTION_ID'])
345
            : new $sectionModel($fields['IBLOCK_SECTION_ID']);
346
    }
347
348
    /**
349
     * Proxy for GetPanelButtons
350
     *
351
     * @param array $options
352
     * @return array
353
     */
354
    public function getPanelButtons($options = [])
355
    {
356
        return CIBlock::GetPanelButtons(
357
            static::iblockId(),
358
            $this->id,
359
            0,
360
            $options
361
        );
362
    }
363
364
    /**
365
     * Save props to database.
366
     * If selected is not empty then only props from it are saved.
367
     *
368
     * @param array $selected
369
     *
370
     * @return bool
371
     */
372
    public function saveProps($selected = [])
373
    {
374
        $propertyValues = $this->constructPropertyValuesForSave($selected);
375
        if (empty($propertyValues)) {
376
            return false;
377
        }
378
379
        $bxMethod = empty($selected) ? 'setPropertyValues' : 'setPropertyValuesEx';
380
        static::$bxObject->$bxMethod(
381
            $this->id,
382
            static::iblockId(),
383
            $propertyValues
384
        );
385
386
        return true;
387
    }
388
389
    /**
390
     * Normalize properties's format converting it to 'PROPERTY_"CODE"_VALUE'.
391
     *
392
     * @return null
393
     */
394
    protected function normalizePropertyFormat()
395
    {
396
        if (empty($this->fields['PROPERTIES'])) {
397
            return;
398
        }
399
400
        foreach ($this->fields['PROPERTIES'] as $code => $prop) {
401
            $this->fields['PROPERTY_'.$code.'_VALUE'] = $prop['VALUE'];
402
            $this->fields['~PROPERTY_'.$code.'_VALUE'] = $prop['~VALUE'];
403
            $this->fields['PROPERTY_'.$code.'_DESCRIPTION'] = $prop['DESCRIPTION'];
404
            $this->fields['~PROPERTY_'.$code.'_DESCRIPTION'] = $prop['~DESCRIPTION'];
405
            $this->fields['PROPERTY_'.$code.'_VALUE_ID'] = $prop['PROPERTY_VALUE_ID'];
406
        }
407
    }
408
409
    /**
410
     * Construct 'PROPERTY_VALUES' => [...] from flat fields array.
411
     * This is used in save.
412
     * If $selectedFields are specified only those are saved.
413
     *
414
     * @param $selectedFields
415
     *
416
     * @return array
417
     */
418
    protected function constructPropertyValuesForSave($selectedFields = [])
419
    {
420
        $propertyValues = [];
421
        $saveOnlySelected = !empty($selectedFields);
422
423
        if ($saveOnlySelected) {
424
            foreach ($selectedFields as $code) {
425
                // if we pass PROPERTY_X_DESCRIPTION as selected field, we need to add PROPERTY_X_VALUE as well.
426 View Code Duplication
                if (preg_match('/^PROPERTY_(.*)_DESCRIPTION$/', $code, $matches) && !empty($matches[1])) {
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...
427
                    $propertyCode = $matches[1];
428
                    $propertyValueKey = "PROPERTY_{$propertyCode}_VALUE";
429
                    if (!in_array($propertyValueKey, $selectedFields)) {
430
                        $selectedFields[] = $propertyValueKey;
431
                    }
432
                }
433
434
                // if we pass PROPERTY_X_ENUM_ID as selected field, we need to add PROPERTY_X_VALUE as well.
435 View Code Duplication
                if (preg_match('/^PROPERTY_(.*)_ENUM_ID$/', $code, $matches) && !empty($matches[1])) {
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...
436
                    $propertyCode = $matches[1];
437
                    $propertyValueKey = "PROPERTY_{$propertyCode}_VALUE";
438
                    if (!in_array($propertyValueKey, $selectedFields)) {
439
                        $selectedFields[] = $propertyValueKey;
440
                    }
441
                }
442
            }
443
        }
444
445
        foreach ($this->fields as $code => $value) {
0 ignored issues
show
Bug introduced by
The expression $this->fields of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
446
            if ($saveOnlySelected && !in_array($code, $selectedFields)) {
447
                continue;
448
            }
449
450
            if (preg_match('/^PROPERTY_(.*)_VALUE$/', $code, $matches) && !empty($matches[1])) {
451
                $propertyCode = $matches[1];
452
    
453
                // if property type is a list we need to use enum ID/IDs as value/values
454
                if (array_key_exists("PROPERTY_{$propertyCode}_ENUM_ID", $this->fields)) {
455
                    $value = $this->fields["PROPERTY_{$propertyCode}_ENUM_ID"];
456
                } else {
457
                    // if we suspect multiple list and PROPERTY_{$propertyCode}_ENUM_ID is not explicitly set.
458
                    if (is_array($value) && (count($value) == 0 || array_keys($value)[0] != 0)) {
459
                        $propertyData = CIBlock::GetProperties(static::iblockId(), [], ["CODE" => $propertyCode, "PROPERTY_TYPE" => "L", "MULTIPLE" => "Y"])->Fetch();
460
                        if ($propertyData) {
461
                            $value = array_keys($value);
462
                        }
463
                    }
464
                }
465
466
                // if property values have descriptions
467
                if (array_key_exists("PROPERTY_{$propertyCode}_DESCRIPTION", $this->fields)) {
468
                    $description = $this->fields["PROPERTY_{$propertyCode}_DESCRIPTION"];
469
470
                    if (is_array($value) && is_array($description)) {
471
                        // for multiple property
472
                        foreach ($value as $rowIndex => $rowValue) {
473
                            $propertyValues[$propertyCode][] = ['VALUE' => $rowValue, 'DESCRIPTION' => $description[$rowIndex]];
474
                        }
475
                    } else {
476
                        // for single property
477
                        $propertyValues[$propertyCode] = ['VALUE' => $value, 'DESCRIPTION' => $description];
478
                    }
479
                } else {
480
                    $propertyValues[$propertyCode] = $value;
481
                }
482
            }
483
        }
484
485
        return $propertyValues;
486
    }
487
488
    /**
489
     * Determine whether the field should be stopped from passing to "update".
490
     *
491
     * @param string $field
492
     * @param mixed  $value
493
     * @param array  $selectedFields
494
     *
495
     * @return bool
496
     */
497
    protected function fieldShouldNotBeSaved($field, $value, $selectedFields)
498
    {
499
        $blacklistedFields = [
500
            'ID',
501
            'IBLOCK_ID',
502
            'PROPERTIES',
503
            'PROPERTY_VALUES',
504
        ];
505
506
        return (!empty($selectedFields) && !in_array($field, $selectedFields))
507
            || in_array($field, $blacklistedFields)
508
            || ($field[0] === '~');
509
            //|| (substr($field, 0, 9) === 'PROPERTY_');
510
    }
511
512
    /**
513
     * @param $fields
514
     * @param $fieldsSelectedForSave
515
     * @return bool
516
     */
517
    protected function internalUpdate($fields, $fieldsSelectedForSave)
518
    {
519
        $fields = $fields ?: [];
520
        foreach ($fields as $key => $value) {
521
            if (substr($key, 0, 9) === 'PROPERTY_') {
522
                unset($fields[$key]);
523
            }
524
        }
525
526
        $result = !empty($fields) ? static::$bxObject->update($this->id, $fields, static::$workFlow, static::$updateSearch, static::$resizePictures) : false;
527
        $savePropsResult = $this->saveProps($fieldsSelectedForSave);
528
        $result = $result || $savePropsResult;
529
530
        return $result;
531
    }
532
533
    /**
534
     * Get value from language field according to current language.
535
     *
536
     * @param $field
537
     * @return mixed
538
     */
539
    protected function getValueFromLanguageField($field)
540
    {
541
        $key = $field . '_' . $this->getCurrentLanguage() . '_VALUE';
542
543
        return isset($this->fields[$key]) ? $this->fields[$key] : null;
544
    }
545
546
    /**
547
     * @param $value
548
     */
549
    public static function setWorkflow($value)
550
    {
551
        static::$workFlow = $value;
552
    }
553
554
    /**
555
     * @param $value
556
     */
557
    public static function setUpdateSearch($value)
558
    {
559
        static::$updateSearch = $value;
560
    }
561
562
    /**
563
     * @param $value
564
     */
565
    public static function setResizePictures($value)
566
    {
567
        static::$resizePictures = $value;
568
    }
569
}
570