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() |
|
|
|
|
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
|
|
|
foreach ($this->fields as $code => $value) { |
|
|
|
|
423
|
|
|
if ($saveOnlySelected && !in_array($code, $selectedFields)) { |
424
|
|
|
continue; |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
if (preg_match('/^PROPERTY_(.*)_VALUE$/', $code, $matches) && !empty($matches[1])) { |
428
|
|
|
$propertyValues[$matches[1]] = $value; |
429
|
|
|
} |
430
|
|
|
} |
431
|
|
|
|
432
|
|
|
return $propertyValues; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* Determine whether the field should be stopped from passing to "update". |
437
|
|
|
* |
438
|
|
|
* @param string $field |
439
|
|
|
* @param mixed $value |
440
|
|
|
* @param array $selectedFields |
441
|
|
|
* |
442
|
|
|
* @return bool |
443
|
|
|
*/ |
444
|
|
|
protected function fieldShouldNotBeSaved($field, $value, $selectedFields) |
445
|
|
|
{ |
446
|
|
|
$blacklistedFields = [ |
447
|
|
|
'ID', |
448
|
|
|
'IBLOCK_ID', |
449
|
|
|
'PROPERTIES', |
450
|
|
|
'PROPERTY_VALUES', |
451
|
|
|
]; |
452
|
|
|
|
453
|
|
|
return (!empty($selectedFields) && !in_array($field, $selectedFields)) |
454
|
|
|
|| in_array($field, $blacklistedFields) |
455
|
|
|
|| ($field[0] === '~'); |
456
|
|
|
//|| (substr($field, 0, 9) === 'PROPERTY_'); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* @param $fields |
461
|
|
|
* @param $fieldsSelectedForSave |
462
|
|
|
* @return bool |
463
|
|
|
*/ |
464
|
|
|
protected function internalUpdate($fields, $fieldsSelectedForSave) |
465
|
|
|
{ |
466
|
|
|
$fields = $fields ?: []; |
467
|
|
|
foreach ($fields as $key => $value) { |
468
|
|
|
if (substr($key, 0, 9) === 'PROPERTY_') { |
469
|
|
|
unset($fields[$key]); |
470
|
|
|
} |
471
|
|
|
} |
472
|
|
|
|
473
|
|
|
$result = !empty($fields) ? static::$bxObject->update($this->id, $fields, static::$workFlow, static::$updateSearch, static::$resizePictures) : false; |
474
|
|
|
$savePropsResult = $this->saveProps($fieldsSelectedForSave); |
475
|
|
|
$result = $result || $savePropsResult; |
476
|
|
|
|
477
|
|
|
return $result; |
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
/** |
481
|
|
|
* @param $value |
482
|
|
|
*/ |
483
|
|
|
public static function setWorkflow($value) |
484
|
|
|
{ |
485
|
|
|
static::$workFlow = $value; |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
/** |
489
|
|
|
* @param $value |
490
|
|
|
*/ |
491
|
|
|
public static function setUpdateSearch($value) |
492
|
|
|
{ |
493
|
|
|
static::$updateSearch = $value; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* @param $value |
498
|
|
|
*/ |
499
|
|
|
public static function setResizePictures($value) |
500
|
|
|
{ |
501
|
|
|
static::$resizePictures = $value; |
502
|
|
|
} |
503
|
|
|
} |
504
|
|
|
|
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.