Completed
Push — master ( 2bf726...b8b49a )
by Nekrasov
01:47
created

ElementModel::fieldShouldNotBeSaved()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.7998
c 0
b 0
f 0
cc 4
nc 4
nop 3
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
     * Update search after each create or update.
84
     *
85
     * @var bool
86
     */
87
    protected static $updateSearch = true;
88
89
    /**
90
     * Getter for corresponding iblock id.
91
     *
92
     * @throws LogicException
93
     *
94
     * @return int
95
     */
96
    public static function iblockId()
97
    {
98
        $id = static::IBLOCK_ID;
99
        if (!$id) {
100
            throw new LogicException('You must set IBLOCK_ID constant inside a model or override iblockId() method');
101
        }
102
        
103
        return $id;
104
    }
105
106
    /**
107
     * Create new item in database.
108
     *
109
     * @param $fields
110
     *
111
     * @throws LogicException
112
     *
113
     * @return static|bool
114
     */
115 View Code Duplication
    public static function create($fields)
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...
116
    {
117
        if (!isset($fields['IBLOCK_ID'])) {
118
            $fields['IBLOCK_ID'] = static::iblockId();
119
        }
120
121
        return static::internalCreate($fields);
122
    }
123
124
    public static function internalDirectCreate($bxObject, $fields)
125
    {
126
        return $bxObject->add($fields, false, static::$updateSearch);
127
    }
128
129
    /**
130
     * Corresponding section model full qualified class name.
131
     * MUST be overridden if you are going to use section model for this iblock.
132
     *
133
     * @throws LogicException
134
     *
135
     * @return string
136
     */
137
    public static function sectionModel()
138
    {
139
        throw new LogicException('public static function sectionModel() MUST be overridden');
140
    }
141
142
    /**
143
     * Instantiate a query object for the model.
144
     *
145
     * @return ElementQuery
146
     */
147
    public static function query()
148
    {
149
        return new ElementQuery(static::instantiateObject(), get_called_class());
150
    }
151
152
    /**
153
     * Scope to sort by date.
154
     *
155
     * @param ElementQuery $query
156
     * @param string       $sort
157
     *
158
     * @return ElementQuery
159
     */
160
    public function scopeSortByDate($query, $sort = 'DESC')
161
    {
162
        return $query->sort(['ACTIVE_FROM' => $sort]);
163
    }
164
165
    /**
166
     * Scope to get only items from a given section.
167
     *
168
     * @param ElementQuery $query
169
     * @param mixed        $id
170
     *
171
     * @return ElementQuery
172
     */
173
    public function scopeFromSectionWithId($query, $id)
174
    {
175
        $query->filter['SECTION_ID'] = $id;
176
177
        return $query;
178
    }
179
180
    /**
181
     * Scope to get only items from a given section.
182
     *
183
     * @param ElementQuery $query
184
     * @param string       $code
185
     *
186
     * @return ElementQuery
187
     */
188
    public function scopeFromSectionWithCode($query, $code)
189
    {
190
        $query->filter['SECTION_CODE'] = $code;
191
192
        return $query;
193
    }
194
195
    /**
196
     * Fill extra fields when $this->field is called.
197
     *
198
     * @return null
199
     */
200
    protected function afterFill()
201
    {
202
        $this->normalizePropertyFormat();
203
    }
204
205
    /**
206
     * Load all model attributes from cache or database.
207
     *
208
     * @return $this
209
     */
210
    public function load()
211
    {
212
        $this->getFields();
213
214
        return $this;
215
    }
216
217
    /**
218
     * Get element's sections from cache or database.
219
     *
220
     * @return array
221
     */
222
    public function getSections()
223
    {
224
        if ($this->sectionsAreFetched) {
225
            return $this->fields['IBLOCK_SECTION'];
226
        }
227
228
        return $this->refreshSections();
229
    }
230
231
    /**
232
     * Refresh model from database and place data to $this->fields.
233
     *
234
     * @return array
235
     */
236
    public function refresh()
237
    {
238
        return $this->refreshFields();
239
    }
240
241
    /**
242
     * Refresh element's fields and save them to a class field.
243
     *
244
     * @return array
245
     */
246 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...
247
    {
248
        if ($this->id === null) {
249
            $this->original = [];
250
            return $this->fields = [];
251
        }
252
253
        $sectionsBackup = isset($this->fields['IBLOCK_SECTION']) ? $this->fields['IBLOCK_SECTION'] : null;
254
255
        $this->fields = static::query()->getById($this->id)->fields;
256
257
        if (!empty($sectionsBackup)) {
258
            $this->fields['IBLOCK_SECTION'] = $sectionsBackup;
259
        }
260
261
        $this->fieldsAreFetched = true;
262
263
        $this->original = $this->fields;
264
265
        return $this->fields;
266
    }
267
268
    /**
269
     * Refresh element's sections and save them to a class field.
270
     *
271
     * @return array
272
     */
273
    public function refreshSections()
274
    {
275
        if ($this->id === null) {
276
            return [];
277
        }
278
279
        $this->fields['IBLOCK_SECTION'] = [];
280
        $dbSections = static::$bxObject->getElementGroups($this->id, true);
281
        while ($section = $dbSections->Fetch()) {
282
            $this->fields['IBLOCK_SECTION'][] = $section;
283
        }
284
285
        $this->sectionsAreFetched = true;
286
287
        return $this->fields['IBLOCK_SECTION'];
288
    }
289
290
    /**
291
     * @deprecated in favour of `->section()`
292
     * Get element direct section as ID or array of fields.
293
     *
294
     * @param bool $load
295
     *
296
     * @return false|int|array
297
     */
298
    public function getSection($load = false)
299
    {
300
        $fields = $this->getFields();
301
        if (!$load) {
302
            return $fields['IBLOCK_SECTION_ID'];
303
        }
304
305
        /** @var SectionModel $sectionModel */
306
        $sectionModel = static::sectionModel();
307
        if (!$fields['IBLOCK_SECTION_ID']) {
308
            return false;
309
        }
310
311
        return $sectionModel::query()->getById($fields['IBLOCK_SECTION_ID'])->toArray();
312
    }
313
314
    /**
315
     * Get element direct section as model object.
316
     *
317
     * @param bool $load
318
     *
319
     * @return false|SectionModel
320
     */
321
    public function section($load = false)
322
    {
323
        $fields = $this->getFields();
324
325
        /** @var SectionModel $sectionModel */
326
        $sectionModel = static::sectionModel();
327
328
        return $load
329
            ? $sectionModel::query()->getById($fields['IBLOCK_SECTION_ID'])
330
            : new $sectionModel($fields['IBLOCK_SECTION_ID']);
331
    }
332
333
    /**
334
     * Proxy for GetPanelButtons
335
     *
336
     * @param array $options
337
     * @return array
338
     */
339
    public function getPanelButtons($options = [])
340
    {
341
        return CIBlock::GetPanelButtons(
342
            static::iblockId(),
343
            $this->id,
344
            0,
345
            $options
346
        );
347
    }
348
349
    /**
350
     * Save props to database.
351
     * If selected is not empty then only props from it are saved.
352
     *
353
     * @param array $selected
354
     *
355
     * @return bool
356
     */
357
    public function saveProps($selected = [])
358
    {
359
        $propertyValues = $this->constructPropertyValuesForSave($selected);
360
        if (empty($propertyValues)) {
361
            return false;
362
        }
363
364
        $bxMethod = empty($selected) ? 'setPropertyValues' : 'setPropertyValuesEx';
365
        static::$bxObject->$bxMethod(
366
            $this->id,
367
            static::iblockId(),
368
            $propertyValues
369
        );
370
371
        return true;
372
    }
373
374
    /**
375
     * Normalize properties's format converting it to 'PROPERTY_"CODE"_VALUE'.
376
     *
377
     * @return null
378
     */
379
    protected function normalizePropertyFormat()
380
    {
381
        if (empty($this->fields['PROPERTIES'])) {
382
            return;
383
        }
384
385
        foreach ($this->fields['PROPERTIES'] as $code => $prop) {
386
            $this->fields['PROPERTY_'.$code.'_VALUE'] = $prop['VALUE'];
387
            $this->fields['~PROPERTY_'.$code.'_VALUE'] = $prop['~VALUE'];
388
            $this->fields['PROPERTY_'.$code.'_DESCRIPTION'] = $prop['DESCRIPTION'];
389
            $this->fields['~PROPERTY_'.$code.'_DESCRIPTION'] = $prop['~DESCRIPTION'];
390
            $this->fields['PROPERTY_'.$code.'_VALUE_ID'] = $prop['PROPERTY_VALUE_ID'];
391
        }
392
    }
393
394
    /**
395
     * Construct 'PROPERTY_VALUES' => [...] from flat fields array.
396
     * This is used in save.
397
     * If $selectedFields are specified only those are saved.
398
     *
399
     * @param $selectedFields
400
     *
401
     * @return array
402
     */
403
    protected function constructPropertyValuesForSave($selectedFields = [])
404
    {
405
        $propertyValues = [];
406
        $saveOnlySelected = !empty($selectedFields);
407
        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...
408
            if ($saveOnlySelected && !in_array($code, $selectedFields)) {
409
                continue;
410
            }
411
412
            if (preg_match('/^PROPERTY_(.*)_VALUE$/', $code, $matches) && !empty($matches[1])) {
413
                $propertyValues[$matches[1]] = $value;
414
            }
415
        }
416
417
        return $propertyValues;
418
    }
419
420
    /**
421
     * Determine whether the field should be stopped from passing to "update".
422
     *
423
     * @param string $field
424
     * @param mixed  $value
425
     * @param array  $selectedFields
426
     *
427
     * @return bool
428
     */
429
    protected function fieldShouldNotBeSaved($field, $value, $selectedFields)
430
    {
431
        $blacklistedFields = [
432
            'ID',
433
            'IBLOCK_ID',
434
            'PROPERTIES',
435
            'PROPERTY_VALUES',
436
        ];
437
438
        return (!empty($selectedFields) && !in_array($field, $selectedFields))
439
            || in_array($field, $blacklistedFields)
440
            || ($field[0] === '~');
441
            //|| (substr($field, 0, 9) === 'PROPERTY_');
0 ignored issues
show
Unused Code Comprehensibility introduced by
64% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
442
    }
443
444
    /**
445
     * @param $fields
446
     * @param $fieldsSelectedForSave
447
     * @return bool
448
     */
449
    protected function internalUpdate($fields, $fieldsSelectedForSave)
450
    {
451
        $fields = $fields ?: [];
452
        foreach ($fields as $key => $value) {
453
            if (substr($key, 0, 9) === 'PROPERTY_') {
454
                unset($fields[$key]);
455
            }
456
        }
457
458
        $result = !empty($fields) ? static::$bxObject->update($this->id, $fields, false, static::$updateSearch) : false;
459
        $savePropsResult = $this->saveProps($fieldsSelectedForSave);
460
        $result = $result || $savePropsResult;
461
462
        return $result;
463
    }
464
465
    /**
466
     * @param $value
467
     */
468
    public static function setUpdateSearch($value)
469
    {
470
        static::$updateSearch = $value;
471
    }
472
}
473