This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace DigitalWand\AdminHelper; |
||
4 | |||
5 | use Bitrix\Main\Localization\Loc; |
||
6 | use Bitrix\Main\ArgumentException; |
||
7 | use Bitrix\Main\Entity\DataManager; |
||
8 | use Bitrix\Main\Entity; |
||
9 | use DigitalWand\AdminHelper\Helper\AdminBaseHelper; |
||
10 | use DigitalWand\AdminHelper\Widget\HelperWidget; |
||
11 | |||
12 | /** |
||
13 | * Менеджер для управления моделью, способный анализировать её связи и сохранять данные в связанные сущности на |
||
14 | * основании полученных данных от виджетов. |
||
15 | * |
||
16 | * Пример создания сущности: |
||
17 | * ``` |
||
18 | * $filmManager = new EntityManager('\Vendor\Module\FilmTable', array( |
||
19 | * // Данные сущности |
||
20 | * 'TITLE' => 'Монстры на каникулах 2', |
||
21 | * 'YEAR' => 2015, |
||
22 | * // У сущности FilmTable есть связь с RelatedLinksTable через поле RELATED_LINKS. |
||
23 | * // Если передать ей данные, то они будут обработаны |
||
24 | * // Представим, что у сущности RelatedLinksTable есть поля ID и VALUE (в этом поле хранится ссылка), FILM_ID |
||
25 | * // В большинстве случаев, данные передаваемые связям генерируются множественными виджетами |
||
26 | * 'RELATED_LINKS' => array( |
||
27 | * // Переданный ниже массив будет обработан аналогично коду RelatedLinksTable::add(array('VALUE' => |
||
28 | * 'yandex.ru')); array('VALUE' => 'yandex.ru'), |
||
29 | * // Если в массив добавить ID, то запись обновится: RelatedLinksTable::update(3, array('ID' => 3, 'VALUE' |
||
30 | * => 'google.com')); array('ID' => 3, 'VALUE' => 'google.com'), |
||
31 | * // ВНИМАНИЕ: данный класс реководствуется принципом: что передано для связи, то сохранится или обновится, |
||
32 | * что не передано, будет удалено |
||
33 | * // То есть, если в поле связи RELATED_LINKS передать пустой массив, то все значения связи будут удалены |
||
34 | * ) |
||
35 | * )); |
||
36 | * $filmManager->save(); |
||
37 | * ``` |
||
38 | * |
||
39 | * Пример удаления сущности |
||
40 | * ``` |
||
41 | * $articleManager = new EntityManager('\Vendor\Module\ArticlesTable', array(), 7, $adminHelper); |
||
42 | * $articleManager->delete(); |
||
43 | * ``` |
||
44 | * |
||
45 | * Как работает сохранение данных ? Дополнительный пример |
||
46 | * Допустим, что есть модели NewsTable (новости) и NewsLinksTable (ссылки на дополнительную информацию о новости) |
||
47 | * |
||
48 | * У модели NewsTable есть связь с моделью NewsLinksTable через поле NEWS_LINKS: |
||
49 | * ``` |
||
50 | * DataManager::getMap() { |
||
51 | * ... |
||
52 | * new Entity\ReferenceField( |
||
53 | * 'NEWS_LINKS', |
||
54 | * '\Vendor\Module\NewsLinksTable', |
||
55 | * array('=this.ID' => 'ref.NEWS_ID'), |
||
56 | * 'ref.FIELD' => new DB\SqlExpression('?s', 'NEWS_LINKS'), |
||
57 | * 'ref.ENTITY' => new DB\SqlExpression('?s', 'news'), |
||
58 | * ), |
||
59 | * ... |
||
60 | * } |
||
61 | * ``` |
||
62 | * |
||
63 | * Попробуем сохранить |
||
64 | * ``` |
||
65 | * $newsManager = new EntityManager( |
||
66 | * '\Vendor\Module\NewsTable', |
||
67 | * array( |
||
68 | * 'TITLE' => 'News title', |
||
69 | * 'CONTENT' => 'News content', |
||
70 | * 'NEWS_LINKS' => array( |
||
71 | * array('LINK' => 'test.ru'), |
||
72 | * array('LINK' => 'test2.ru'), |
||
73 | * array('ID' => 'id ссылки', 'LINK' => 'test3.ru'), |
||
74 | * ) |
||
75 | * ), |
||
76 | * null, |
||
77 | * $adminHelper |
||
78 | * ); |
||
79 | * $newsManager->save(); |
||
80 | * ``` |
||
81 | * |
||
82 | * В данном примере передаются данные для новости (заголовок и содержимое) и данные для поля-связи NEWS_LINKS. |
||
83 | * |
||
84 | * Далее EntityManager: |
||
85 | * 1. Вырезает данные, которые предназначены связям |
||
86 | * 2. Подставляет в них данные из основной модели на основе условий связи |
||
87 | * Например для связи с полем NEWS_LINKS подставятся данные: |
||
88 | * |
||
89 | * ``` |
||
90 | * NewsLinksTable::ENTITY_ID => NewsTable::ID, |
||
91 | * NewsLinksTable::FIELD => 'NEWS_LINKS', |
||
92 | * NewsLinksTable::ENTITY => 'news' |
||
93 | * ``` |
||
94 | * |
||
95 | * 3. После подстановки данных они будут переданы модели связи подобно коду ниже: |
||
96 | * |
||
97 | * ``` |
||
98 | * NewsLinksTable::add(array('ENTITY' => 'news', 'FIELD' => 'NEWS_LINKS', 'ENTITY_ID' => 'id сущности, например |
||
99 | * новости', 'LINK' => 'test.ru')); NewsLinksTable::add(array('ENTITY' => 'news', 'FIELD' => 'NEWS_LINKS', 'ENTITY_ID' |
||
100 | * => 'id сущности', 'LINK' => 'test2.ru')); NewsLinksTable::update('id ссылки', array('ENTITY' => 'news', 'FIELD' => |
||
101 | * 'NEWS_LINKS', 'ENTITY_ID' => 'id сущности', 'LINK' => 'test3.ru')); |
||
102 | * ``` |
||
103 | * |
||
104 | * Обратите внимание, что в метод EntityManager::save() были изначально передано только поле LINK, поля ENTITY, |
||
105 | * ENTITY_ID и FIELD были подставлены классом EntityManager автоматически (предыдущий пункт) А так же важно, что для |
||
106 | * третьей ссылки был передан идентификатор, поэтому выполнился NewsLinksTable::update, а не NewsLinksTable::add |
||
107 | * |
||
108 | * 4. Далее `EntityManager` удаляет данные связанной модели `NewsLinksTable`, которые не были добавлены или обновлены. |
||
109 | * |
||
110 | * <b>Как работает удаление?</b> |
||
111 | * |
||
112 | * 1. EntityManager получает из `NewsTable::getMap()` поля-связи |
||
113 | * 2. Получает поля описанные в интерфейсе генератора админки |
||
114 | * 3. Удаляет значения для полей-связей, которые объявлены в интерфейсе |
||
115 | * |
||
116 | * <i>Примечание.</i> |
||
117 | * EntityManager управляет только данными, которые получает при помощи связи стандартными средставами битрикса. |
||
118 | * Например, при удалении NewsTable будут удалены только NewsLinksTable, где: |
||
119 | * |
||
120 | * ``` |
||
121 | * NewsLinksTable::ENTITY_ID => NewsTable::ID, |
||
122 | * NewsLinksTable::FIELD => 'NEWS_LINKS', |
||
123 | * NewsLinksTable::ENTITY => 'news' |
||
124 | * ``` |
||
125 | * |
||
126 | * @author Nik Samokhvalov <[email protected]> |
||
127 | * @author Dmitriy Baibuhtin <[email protected]> |
||
128 | */ |
||
129 | class EntityManager |
||
130 | { |
||
131 | /** |
||
132 | * @var string Класс модели. |
||
133 | */ |
||
134 | protected $modelClass; |
||
135 | /** |
||
136 | * @var Entity\Base Сущность модели. |
||
137 | */ |
||
138 | protected $model; |
||
139 | /** |
||
140 | * @var array Данные для обработки. |
||
141 | */ |
||
142 | protected $data; |
||
143 | /** |
||
144 | * @var integer Идентификатор записи. |
||
145 | */ |
||
146 | protected $itemId = null; |
||
147 | /** |
||
148 | * @var string Поле модели, в котором хранится идентификатор записи. |
||
149 | */ |
||
150 | protected $modelPk = null; |
||
151 | /** |
||
152 | * @var array Данные для связей. |
||
153 | */ |
||
154 | protected $referencesData = array(); |
||
155 | /** |
||
156 | * @var AdminBaseHelper Хелпер. |
||
157 | */ |
||
158 | protected $helper; |
||
159 | /** |
||
160 | * @var array Предупреждения. |
||
161 | */ |
||
162 | protected $notes = array(); |
||
163 | |||
164 | /** |
||
165 | * @param string $modelClass Класс основной модели, наследника DataManager. |
||
166 | * @param array $data Массив с сохраняемыми данными. |
||
167 | * @param integer $itemId Идентификатор сохраняемой записи. |
||
168 | * @param AdminBaseHelper $helper Хелпер, инициирующий сохранение записи. |
||
169 | */ |
||
170 | public function __construct($modelClass, array $data = array(), $itemId = null, AdminBaseHelper $helper) |
||
171 | { |
||
172 | Loc::loadMessages(__FILE__); |
||
173 | |||
174 | $this->modelClass = $modelClass; |
||
175 | $this->model = $modelClass::getEntity(); |
||
176 | $this->data = $data; |
||
177 | $this->modelPk = $this->model->getPrimary(); |
||
178 | $this->helper = $helper; |
||
179 | |||
180 | if (!empty($itemId)) { |
||
181 | $this->setItemId($itemId); |
||
182 | } |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * Сохранить запись и данные связей. |
||
187 | * |
||
188 | * @return Entity\AddResult|Entity\UpdateResult |
||
189 | */ |
||
190 | public function save() |
||
191 | { |
||
192 | $this->collectReferencesData(); |
||
193 | |||
194 | /** |
||
195 | * @var DataManager $modelClass |
||
196 | */ |
||
197 | $modelClass = $this->modelClass; |
||
198 | $db = $this->model->getConnection(); |
||
199 | $db->startTransaction(); // начало транзакции |
||
200 | |||
201 | if (empty($this->itemId)) { |
||
202 | $result = $modelClass::add($this->data); |
||
203 | |||
204 | if ($result->isSuccess()) { |
||
205 | $this->setItemId($result->getId()); |
||
206 | } |
||
207 | } |
||
208 | else { |
||
209 | $result = $modelClass::update($this->itemId, $this->data); |
||
210 | } |
||
211 | |||
212 | if ($result->isSuccess()) { |
||
213 | $referencesDataResult = $this->processReferencesData(); |
||
214 | if($referencesDataResult->isSuccess()){ |
||
215 | $db->commitTransaction(); // ошибок нет - применяем изменения |
||
216 | }else{ |
||
217 | $result = $referencesDataResult; // возвращаем ReferencesResult что бы вернуть ошибку |
||
218 | $db->rollbackTransaction(); // что-то пошло не так - возвращаем все как было |
||
219 | } |
||
220 | } else { |
||
221 | $db->rollbackTransaction(); |
||
222 | } |
||
223 | |||
224 | return $result; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Удаление запись и данные связей. |
||
229 | * |
||
230 | * @return Entity\DeleteResult |
||
231 | */ |
||
232 | public function delete() |
||
233 | { |
||
234 | // Удаление данных зависимостей |
||
235 | $db = $this->model->getConnection(); |
||
236 | $db->startTransaction(); // начало транзакции |
||
237 | |||
238 | $result = $this->deleteReferencesData(); // удаляем зависимые сущности |
||
239 | |||
240 | if(!$result->isSuccess()){ // если хотя бы одна из них не удалилась |
||
241 | $db->rollbackTransaction(); // то восстанавливаем все |
||
242 | return $result; // возвращаем ошибку |
||
243 | } |
||
244 | |||
245 | $model = $this->modelClass; |
||
246 | |||
247 | //Если передается массив, то получаем ИД записи |
||
248 | if (!is_null($this->itemId)) { |
||
249 | $result = $model::delete($this->itemId); // удаляем основную сущность |
||
250 | } elseif (!is_array($this->helper->getPk())) { |
||
251 | $result = $model::delete($this->helper->getPk()); // удаляем основную сущность |
||
252 | } else { |
||
253 | $result = new Entity\DeleteResult(); |
||
254 | $error = new Entity\EntityError('Can\'t find element\'s ID'); |
||
255 | $result->addError($error); |
||
256 | } |
||
257 | |||
258 | if(!$result->isSuccess()){ // если не удалилась |
||
259 | $db->rollbackTransaction(); // то восстанавливаем зависимые сущности |
||
260 | return $result; // возвращаем ошибку |
||
261 | } |
||
262 | |||
263 | $db->commitTransaction(); // все прошло без ошибок применяем изменения |
||
264 | return $result; |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * Получить список предупреждений |
||
269 | * @return array |
||
270 | */ |
||
271 | public function getNotes() |
||
272 | { |
||
273 | return $this->notes; |
||
274 | } |
||
275 | |||
276 | /** |
||
277 | * Добавить предупреждение |
||
278 | * |
||
279 | * @param $note |
||
280 | * @param string $key Ключ для избежания дублирования сообщений |
||
281 | * |
||
282 | * @return bool |
||
283 | */ |
||
284 | protected function addNote($note, $key = null) |
||
285 | { |
||
286 | if ($key) { |
||
0 ignored issues
–
show
|
|||
287 | $this->notes[$key] = $note; |
||
288 | } |
||
289 | else { |
||
290 | $this->notes[] = $note; |
||
291 | } |
||
292 | |||
293 | return true; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * Установка текущего идентификатора модели. |
||
298 | * |
||
299 | * @param integer $itemId Идентификатор записи. |
||
300 | */ |
||
301 | protected function setItemId($itemId) |
||
302 | { |
||
303 | $this->itemId = $itemId; |
||
304 | $this->data[$this->modelPk] = $this->itemId; |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Получение связей |
||
309 | * |
||
310 | * @return array |
||
311 | */ |
||
312 | protected function getReferences() |
||
313 | { |
||
314 | /** |
||
315 | * @var DataManager $modelClass |
||
316 | */ |
||
317 | $modelClass = $this->modelClass; |
||
318 | $entity = $modelClass::getEntity(); |
||
319 | $fields = $entity->getFields(); |
||
320 | $references = array(); |
||
321 | |||
322 | foreach ($fields as $fieldName => $field) { |
||
323 | if ($field instanceof Entity\ReferenceField) { |
||
324 | $references[$fieldName] = $field; |
||
325 | } |
||
326 | } |
||
327 | |||
328 | return $references; |
||
329 | } |
||
330 | |||
331 | /** |
||
332 | * Извлечение данных для связей |
||
333 | */ |
||
334 | protected function collectReferencesData() |
||
335 | { |
||
336 | $result = new Entity\Result(); |
||
337 | $references = $this->getReferences(); |
||
338 | // Извлечение данных управляемых связей |
||
339 | foreach ($references as $fieldName => $reference) { |
||
340 | if (array_key_exists($fieldName, $this->data)) { |
||
341 | if (!is_array($this->data[$fieldName])) { |
||
342 | $result->addError(new Entity\EntityError(Loc::getMessage('DIGITALWAND_AH_RELATION_SHOULD_BE_MULTIPLE_FIELD'))); |
||
343 | |||
344 | return $result; |
||
345 | } |
||
346 | // Извлечение данных для связи |
||
347 | $this->referencesData[$fieldName] = $this->data[$fieldName]; |
||
348 | unset($this->data[$fieldName]); |
||
349 | } |
||
350 | } |
||
351 | |||
352 | return $result; |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Обработка данных для связей. |
||
357 | * |
||
358 | * @throws ArgumentException |
||
359 | */ |
||
360 | protected function processReferencesData() |
||
361 | { |
||
362 | /** |
||
363 | * @var DataManager $modelClass |
||
364 | */ |
||
365 | $modelClass = $this->modelClass; |
||
366 | $entity = $modelClass::getEntity(); |
||
367 | $fields = $entity->getFields(); |
||
368 | $result = new Entity\Result(); // пустой Result у которого isSuccess вернет true |
||
369 | |||
370 | foreach ($this->referencesData as $fieldName => $referenceDataSet) { |
||
371 | if (!is_array($referenceDataSet)) { |
||
372 | continue; |
||
373 | } |
||
374 | |||
375 | /** |
||
376 | * @var Entity\ReferenceField $reference |
||
377 | */ |
||
378 | $reference = $fields[$fieldName]; |
||
379 | $referenceDataSet = $this->linkDataSet($reference, $referenceDataSet); |
||
380 | $referenceStaleDataSet = $this->getReferenceDataSet($reference); |
||
381 | $fieldWidget = $this->getFieldWidget($fieldName); |
||
382 | |||
383 | // Создание и обновление привязанных данных |
||
384 | $processedDataIds = array(); |
||
385 | foreach ($referenceDataSet as $referenceData) { |
||
386 | if (empty($referenceData[$fieldWidget->getMultipleField('ID')])) { |
||
387 | // Создание связанных данных |
||
388 | if (!empty($referenceData[$fieldWidget->getMultipleField('VALUE')])) { |
||
389 | $result = $this->createReferenceData($reference, $referenceData); |
||
390 | |||
391 | if ($result->isSuccess()) { |
||
392 | $processedDataIds[] = $result->getId(); |
||
393 | } else { |
||
394 | break; // ошибка, прерываем обработку данных |
||
395 | } |
||
396 | } |
||
397 | } else { |
||
398 | // Обновление связанных данных |
||
399 | $result = $this->updateReferenceData($reference, $referenceData, $referenceStaleDataSet); |
||
400 | |||
401 | if ($result->isSuccess()) { |
||
402 | $processedDataIds[] = $referenceData[$fieldWidget->getMultipleField('ID')]; |
||
403 | } else { |
||
404 | break; // ошибка, прерываем обработку данных |
||
405 | } |
||
406 | } |
||
407 | } |
||
408 | |||
409 | if($result->isSuccess()){ // Удаление записей, которые не были созданы или обновлены |
||
410 | foreach ($referenceStaleDataSet as $referenceData) { |
||
411 | if (!in_array($referenceData[$fieldWidget->getMultipleField('ID')], $processedDataIds)) { |
||
412 | $result = $this->deleteReferenceData($reference, |
||
413 | $referenceData[$fieldWidget->getMultipleField('ID')]); |
||
414 | if(!$result->isSuccess()) { |
||
415 | break; // ошибка, прерываем удаление данных |
||
416 | } |
||
417 | } |
||
418 | } |
||
419 | } |
||
420 | } |
||
421 | |||
422 | $this->referencesData = array(); |
||
423 | return $result; |
||
424 | } |
||
425 | |||
426 | /** |
||
427 | * Удаление данных всех связей, которые указаны в полях интерфейса раздела. |
||
428 | */ |
||
429 | protected function deleteReferencesData() |
||
430 | { |
||
431 | $references = $this->getReferences(); |
||
432 | $fields = $this->helper->getFields(); |
||
433 | $result = new Entity\Result(); |
||
434 | /** |
||
435 | * @var string $fieldName |
||
436 | * @var Entity\ReferenceField $reference |
||
437 | */ |
||
438 | foreach ($references as $fieldName => $reference) { |
||
439 | // Удаляются только данные связей, которые объявлены в интерфейсе |
||
440 | if (!isset($fields[$fieldName])) { |
||
441 | continue; |
||
442 | } |
||
443 | |||
444 | $fieldWidget = $this->getFieldWidget($reference->getName()); |
||
445 | $referenceStaleDataSet = $this->getReferenceDataSet($reference); |
||
446 | |||
447 | foreach ($referenceStaleDataSet as $referenceData) { |
||
448 | $result = $this->deleteReferenceData($reference, $referenceData[$fieldWidget->getMultipleField('ID')]); |
||
449 | if(!$result->isSuccess()){ |
||
450 | return $result; |
||
451 | } |
||
452 | } |
||
453 | } |
||
454 | return $result; |
||
455 | } |
||
456 | |||
457 | /** |
||
458 | * Создание связанной записи. |
||
459 | * |
||
460 | * @param Entity\ReferenceField $reference |
||
461 | * @param array $referenceData |
||
462 | * |
||
463 | * @return \Bitrix\Main\Entity\AddResult |
||
464 | * @throws ArgumentException |
||
465 | */ |
||
466 | protected function createReferenceData(Entity\ReferenceField $reference, array $referenceData) |
||
467 | { |
||
468 | $referenceName = $reference->getName(); |
||
469 | $fieldParams = $this->getFieldParams($referenceName); |
||
470 | $fieldWidget = $this->getFieldWidget($referenceName); |
||
471 | |||
472 | View Code Duplication | if (!empty($referenceData[$fieldWidget->getMultipleField('ID')])) { |
|
0 ignored issues
–
show
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. ![]() |
|||
473 | throw new ArgumentException(Loc::getMessage('DIGITALWAND_AH_ARGUMENT_CANT_CONTAIN_ID', array('%A%' => 'referenceData')), 'referenceData'); |
||
474 | } |
||
475 | |||
476 | $refClass = $reference->getRefEntity()->getDataClass(); |
||
477 | |||
478 | $createResult = $refClass::add($referenceData); |
||
479 | |||
480 | View Code Duplication | if (!$createResult->isSuccess()) { |
|
0 ignored issues
–
show
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. ![]() |
|||
481 | $this->addNote(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_RELATION_SAVE_ERROR', |
||
482 | array('#FIELD#' => $fieldParams['TITLE'])), 'CREATE_' . $referenceName); |
||
483 | } |
||
484 | |||
485 | return $createResult; |
||
486 | } |
||
487 | |||
488 | /** |
||
489 | * Обновление связанной записи |
||
490 | * |
||
491 | * @param Entity\ReferenceField $reference |
||
492 | * @param array $referenceData |
||
493 | * @param array $referenceStaleDataSet |
||
494 | * |
||
495 | * @return Entity\UpdateResult|null |
||
496 | * @throws ArgumentException |
||
497 | */ |
||
498 | protected function updateReferenceData( |
||
499 | Entity\ReferenceField $reference, |
||
500 | array $referenceData, |
||
501 | array $referenceStaleDataSet |
||
502 | ) |
||
503 | { |
||
504 | $referenceName = $reference->getName(); |
||
505 | $fieldParams = $this->getFieldParams($referenceName); |
||
506 | $fieldWidget = $this->getFieldWidget($referenceName); |
||
507 | |||
508 | View Code Duplication | if (empty($referenceData[$fieldWidget->getMultipleField('ID')])) { |
|
0 ignored issues
–
show
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. ![]() |
|||
509 | throw new ArgumentException(Loc::getMessage('DIGITALWAND_AH_ARGUMENT_SHOULD_CONTAIN_ID', array('%A%' => 'referenceData')), 'referenceData'); |
||
510 | } |
||
511 | |||
512 | // Сравнение старых данных и новых, обновляется только при различиях |
||
513 | if ($this->isDifferentData($referenceStaleDataSet[$referenceData[$fieldWidget->getMultipleField('ID')]], |
||
514 | $referenceData) |
||
515 | ) { |
||
516 | $refClass = $reference->getRefEntity()->getDataClass(); |
||
517 | $updateResult = $refClass::update($referenceData[$fieldWidget->getMultipleField('ID')], $referenceData); |
||
518 | |||
519 | View Code Duplication | if (!$updateResult->isSuccess()) { |
|
0 ignored issues
–
show
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. ![]() |
|||
520 | $this->addNote(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_RELATION_SAVE_ERROR', |
||
521 | array('#FIELD#' => $fieldParams['TITLE'])), 'UPDATE_' . $referenceName); |
||
522 | } |
||
523 | |||
524 | return $updateResult; |
||
525 | } else { |
||
526 | return new Entity\Result(); // пустой Result у которого isSuccess() вернет true |
||
527 | } |
||
528 | } |
||
529 | |||
530 | /** |
||
531 | * Удаление данных связи. |
||
532 | * |
||
533 | * @param Entity\ReferenceField $reference |
||
534 | * @param $referenceId |
||
535 | * |
||
536 | * @return \Bitrix\Main\Entity\Result |
||
537 | * @throws ArgumentException |
||
538 | */ |
||
539 | protected function deleteReferenceData(Entity\ReferenceField $reference, $referenceId) |
||
540 | { |
||
541 | $fieldParams = $this->getFieldParams($reference->getName()); |
||
542 | $refClass = $reference->getRefEntity()->getDataClass(); |
||
543 | $deleteResult = $refClass::delete($referenceId); |
||
544 | |||
545 | if (!$deleteResult->isSuccess()) { |
||
546 | $this->addNote(Loc::getMessage('DIGITALWAND_ADMIN_HELPER_RELATION_DELETE_ERROR', |
||
547 | array('#FIELD#' => $fieldParams['TITLE'])), 'DELETE_' . $reference->getName()); |
||
548 | } |
||
549 | |||
550 | return $deleteResult; |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * Получение данных связи. |
||
555 | * |
||
556 | * @param $reference |
||
557 | * |
||
558 | * @return array |
||
559 | */ |
||
560 | protected function getReferenceDataSet(Entity\ReferenceField $reference) |
||
561 | { |
||
562 | /** |
||
563 | * @var DataManager $modelClass |
||
564 | */ |
||
565 | $modelClass = $this->modelClass; |
||
566 | $dataSet = array(); |
||
567 | $fieldWidget = $this->getFieldWidget($reference->getName()); |
||
568 | |||
569 | $rsData = $modelClass::getList(array( |
||
570 | 'select' => array('REF_' => $reference->getName() . '.*'), |
||
571 | 'filter' => array('=' . $this->modelPk => $this->itemId) |
||
572 | )); |
||
573 | |||
574 | while ($data = $rsData->fetch()) { |
||
575 | if (empty($data['REF_' . $fieldWidget->getMultipleField('ID')])) { |
||
576 | continue; |
||
577 | } |
||
578 | |||
579 | $row = array(); |
||
580 | foreach ($data as $key => $value) { |
||
581 | $row[str_replace('REF_', '', $key)] = $value; |
||
582 | } |
||
583 | |||
584 | $dataSet[$data['REF_' . $fieldWidget->getMultipleField('ID')]] = $row; |
||
585 | } |
||
586 | |||
587 | return $dataSet; |
||
588 | } |
||
589 | |||
590 | /** |
||
591 | * В данные связи подставляются данные основной модели используя условия связи моделей из getMap(). |
||
592 | * |
||
593 | * @param Entity\ReferenceField $reference |
||
594 | * @param array $referenceData Данные привязанной модели |
||
595 | * |
||
596 | * @return array |
||
597 | */ |
||
598 | protected function linkData(Entity\ReferenceField $reference, array $referenceData) |
||
599 | { |
||
600 | // Парсим условия связи двух моделей |
||
601 | $referenceConditions = $this->getReferenceConditions($reference); |
||
602 | |||
603 | foreach ($referenceConditions as $refField => $refValue) { |
||
604 | // Так как в условиях связи между моделями в основном отношения типа this.field => ref.field или |
||
605 | // ref.field => SqlExpression, мы можем использовать это для подстановки данных |
||
606 | // this.field - это поле основной модели |
||
607 | // ref.field - поле модели из связи |
||
608 | // customValue - это строка полученная из new SqlExpression('%s', ...) |
||
609 | if (empty($refValue['thisField'])) { |
||
610 | $referenceData[$refField] = $refValue['customValue']; |
||
611 | } |
||
612 | else { |
||
613 | $referenceData[$refField] = $this->data[$refValue['thisField']]; |
||
614 | } |
||
615 | } |
||
616 | |||
617 | return $referenceData; |
||
618 | } |
||
619 | |||
620 | /** |
||
621 | * Связывает набор связанных данных с основной моделю. |
||
622 | * |
||
623 | * @param Entity\ReferenceField $reference |
||
624 | * @param array $referenceDataSet |
||
625 | * |
||
626 | * @return array |
||
627 | */ |
||
628 | protected function linkDataSet(Entity\ReferenceField $reference, array $referenceDataSet) |
||
629 | { |
||
630 | foreach ($referenceDataSet as $key => $referenceData) { |
||
631 | $referenceDataSet[$key] = $this->linkData($reference, $referenceData); |
||
632 | } |
||
633 | |||
634 | return $referenceDataSet; |
||
635 | } |
||
636 | |||
637 | /** |
||
638 | * Парсинг условий связи между моделями. |
||
639 | * |
||
640 | * Ничего сложного нет, просто определяются соответствия полей основной модели и модели из связи. Например: |
||
641 | * |
||
642 | * `FilmLinksTable::FILM_ID => FilmTable::ID (ref.FILM_ID => this.ID)` |
||
643 | * |
||
644 | * Или, например: |
||
645 | * |
||
646 | * `MediaTable::TYPE => 'FILM' (ref.TYPE => new DB\SqlExpression('?s', 'FILM'))` |
||
647 | * |
||
648 | * @param Entity\ReferenceField $reference Данные поля из getMap(). |
||
649 | * |
||
650 | * @return array Условия связи преобразованные в массив вида $conditions[$refField]['thisField' => $thisField, |
||
651 | * 'customValue' => $customValue]. |
||
652 | * $customValue - это результат парсинга SqlExpression. |
||
653 | * Если шаблон SqlExpression не равен %s, то условие исключается из результата. |
||
654 | */ |
||
655 | protected function getReferenceConditions(Entity\ReferenceField $reference) |
||
656 | { |
||
657 | $conditionsFields = array(); |
||
658 | |||
659 | foreach ($reference->getReference() as $leftCondition => $rightCondition) { |
||
660 | $thisField = null; |
||
661 | $refField = null; |
||
662 | $customValue = null; |
||
663 | |||
664 | // Поиск this.... в левом условии |
||
665 | $thisFieldMatch = array(); |
||
666 | $refFieldMatch = array(); |
||
667 | if (preg_match('/=this\.([A-z]+)/', $leftCondition, $thisFieldMatch) == 1) { |
||
668 | $thisField = $thisFieldMatch[1]; |
||
669 | } // Поиск ref.... в левом условии |
||
670 | else { |
||
671 | if (preg_match('/ref\.([A-z]+)/', $leftCondition, $refFieldMatch) == 1) { |
||
672 | $refField = $refFieldMatch[1]; |
||
673 | } |
||
674 | } |
||
675 | |||
676 | // Поиск expression value... в правом условии |
||
677 | $refFieldMatch = array(); |
||
678 | if ($rightCondition instanceof \Bitrix\Main\DB\SqlExpression) { |
||
679 | $customValueDirty = $rightCondition->compile(); |
||
680 | $customValue = preg_replace('/^([\'"])(.+)\1$/', '$2', $customValueDirty); |
||
681 | if ($customValueDirty == $customValue) { |
||
682 | // Если значение выражения не обрамлено кавычками, значит оно не нужно нам |
||
683 | $customValue = null; |
||
684 | } |
||
685 | } // Поиск ref.... в правом условии |
||
686 | else { |
||
687 | if (preg_match('/ref\.([A-z]+)/', $rightCondition, $refFieldMatch) > 0) { |
||
688 | $refField = $refFieldMatch[1]; |
||
689 | } |
||
690 | } |
||
691 | |||
692 | // Если не указано поле, которое нужно заполнить или не найдено содержимое для него, то исключаем условие |
||
693 | if (empty($refField) || (empty($thisField) && empty($customValue))) { |
||
694 | continue; |
||
695 | } |
||
696 | else { |
||
697 | $conditionsFields[$refField] = array( |
||
698 | 'thisField' => $thisField, |
||
699 | 'customValue' => $customValue, |
||
700 | ); |
||
701 | } |
||
702 | } |
||
703 | |||
704 | return $conditionsFields; |
||
705 | } |
||
706 | |||
707 | /** |
||
708 | * Обнаружение отличий массивов |
||
709 | * Метод не сранивает наличие аргументов, сравниваются только значения общих параметров |
||
710 | * |
||
711 | * @param array $data1 |
||
712 | * @param array $data2 |
||
713 | * |
||
714 | * @return bool |
||
715 | */ |
||
716 | protected function isDifferentData(array $data1 = null, array $data2 = null) |
||
717 | { |
||
718 | foreach ($data1 as $key => $value) { |
||
0 ignored issues
–
show
The expression
$data1 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.
![]() |
|||
719 | if (isset($data2[$key]) && $data2[$key] != $value) { |
||
720 | return true; |
||
721 | } |
||
722 | } |
||
723 | |||
724 | return false; |
||
725 | } |
||
726 | |||
727 | /** |
||
728 | * @param $fieldName |
||
729 | * |
||
730 | * @return array|bool |
||
731 | */ |
||
732 | protected function getFieldParams($fieldName) |
||
733 | { |
||
734 | $fields = $this->helper->getFields(); |
||
735 | |||
736 | if (isset($fields[$fieldName]) && isset($fields[$fieldName]['WIDGET'])) { |
||
737 | return $fields[$fieldName]; |
||
738 | } |
||
739 | else { |
||
740 | return false; |
||
741 | } |
||
742 | } |
||
743 | |||
744 | /** |
||
745 | * Получение виджета привязанного к полю. |
||
746 | * |
||
747 | * @param $fieldName |
||
748 | * |
||
749 | * @return HelperWidget|bool |
||
750 | */ |
||
751 | protected function getFieldWidget($fieldName) |
||
752 | { |
||
753 | $field = $this->getFieldParams($fieldName); |
||
754 | |||
755 | return isset($field['WIDGET']) ? $field['WIDGET'] : null; |
||
756 | } |
||
757 | } |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: