Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
nodeattributes.php
1<?php
3
4
15
16final class NodeAttributes extends Stepper
17{
18 const CONTINUE_EXECUTING = true;
19 const STOP_EXECUTING = false;
20 const OPTION_NAME = 'blocks_attrs_update';
21 const OPTION_STATUS_NAME = 'blocks_attrs_update_status';
22 const STEP_PORTION = 1; //count of block CODES to step
23
24 protected static $moduleId = 'landing';
25 protected $dataToUpdate = array();
26 protected $blocksToUpdate = array();
27 protected $sitesToUpdate = array();
28 protected $status = array();
29 protected $codesToStep = array();
30
34 private function loadCurrentStatus()
35 {
36// saved in option
37 $this->status = Option::get('landing', self::OPTION_STATUS_NAME, '');
38 $this->status = ($this->status !== '' ? @unserialize($this->status, ['allowed_classes' => false]) : array());
39 $this->status = (is_array($this->status) ? $this->status : array());
40
41// or default
42 if (empty($this->status))
43 {
44// get codes from all updaters options
45 $count = 0;
46 $params = array();
47 foreach (Option::getForModule('landing') as $key => $option)
48 {
49 if (mb_strpos($key, self::OPTION_NAME) === 0 && $key != self::OPTION_STATUS_NAME)
50 {
51 $option = ($option !== '' ? @unserialize($option, ['allowed_classes' => false]) : array());
52
53 if(!isset($option['BLOCKS']))
54 {
55 Option::delete('landing', array('name' => $key));
56 continue;
57 }
58
59// save params
60 $params[$key] = $option['PARAMS'];
61// count of all blocks - to progress-bar
62 $filter = array(
63 'CODE' => array_keys($option['BLOCKS']),
64 'DELETED' => 'N',
65 );
66 if (
67 isset($option['PARAMS']['UPDATE_PUBLISHED_SITES']) &&
68 $option['PARAMS']['UPDATE_PUBLISHED_SITES'] != 'Y'
69 )
70 {
71 $filter['PUBLIC'] = 'N';
72 }
73
74 $res = BlockTable::getList(array(
75 'select' => array(
77 'CNT', 'COUNT(*)'
78 ),
79 ),
80 'filter' => $filter,
81 ));
82 if ($row = $res->fetch())
83 {
84 $count += $row['CNT'];
85 }
86 }
87 }
88
89 $this->status['COUNT'] = $count;
90 $this->status['STEPS'] = 0;
91 $this->status['SITES_TO_UPDATE'] = array();
92 $this->status['UPDATER_ID'] = '';
93 $this->status['PARAMS'] = $params;
94
95 Option::set('landing', self::OPTION_STATUS_NAME, serialize($this->status));
96 }
97 }
98
104 private function getUpdaterUniqueId()
105 {
106// continue processing current updater
107 if ($this->status['UPDATER_ID'] !== '')
108 {
109 return $this->status['UPDATER_ID'];
110 }
111
112 $updaterOptions = Option::getForModule('landing');
113 $allOptions = preg_grep('/' . self::OPTION_NAME . '.+/', array_keys($updaterOptions));
114 $allOptions = array_diff($allOptions, array(self::OPTION_STATUS_NAME)); // remove status option from list
115 sort($allOptions);
116
117 if (!empty($allOptions))
118 {
119 return str_replace(self::OPTION_NAME, '', $allOptions[0]);
120 }
121 else
122 {
123 return '';
124 }
125 }
126
127
128 public function execute(array &$result)
129 {
130// nothing to update
131 $this->loadCurrentStatus();
132
133 if (!$this->status['COUNT'])
134 {
135 self::finish();
136
138 }
139
140// find option. If nothing - we update all
141 $this->status['UPDATER_ID'] = $this->getUpdaterUniqueId();
142
143 if (!$this->status['UPDATER_ID'])
144 {
145 self::finish();
146
148 }
149
150 $this->processBlocks();
151
152// was processing all data for current option
153 if (!is_array($this->dataToUpdate['BLOCKS']) || empty($this->dataToUpdate['BLOCKS']))
154 {
155 $this->finishOption();
156 }
157
158 $result['count'] = $this->status['COUNT'];
159 $result['steps'] = $this->status['STEPS'];
160
162 }
163
164
168 private static function finish()
169 {
170 self::clearOptions();
172 }
173
174
180 private static function clearOptions()
181 {
182 foreach (Option::getForModule('landing') as $key => $option)
183 {
184 if (mb_strpos($key, self::OPTION_NAME) === 0)
185 {
186 Option::delete('landing', array('name' => $key));
187 }
188 }
189 }
190
191
196 private function getOptionName()
197 {
198 return self::OPTION_NAME . $this->status['UPDATER_ID'];
199 }
200
201
202 private function collectBlocks()
203 {
204 $this->dataToUpdate = Option::get(self::$moduleId, $this->getOptionName());
205 $this->dataToUpdate = ($this->dataToUpdate !== '' ? @unserialize($this->dataToUpdate, ['allowed_classes' => false]) : array());
206 $this->codesToStep = array_unique(array_keys($this->dataToUpdate['BLOCKS']));
207 $this->codesToStep = array_slice($this->codesToStep, 0, self::STEP_PORTION);
208
209// load BLOCKS
210 $filter = array(
211 'CODE' => $this->codesToStep,
212 'DELETED' => 'N',
213 );
214 if (
215 isset($this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES']) &&
216 $this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES'] != 'Y'
217 )
218 {
219 $filter['PUBLIC'] = 'N';
220 }
221
222 $resBlock = BlockTable::getList(array(
223 'filter' => $filter,
224 'select' => array(
225 'ID',
226 'SORT',
227 'CODE',
228 'ACTIVE',
229 'PUBLIC',
230 'DELETED',
231 'CONTENT',
232 'LID',
233 'SITE_ID' => 'LANDING.SITE_ID',
234 ),
235 'order' => array(
236 'CODE' => 'ASC',
237 'ID' => 'ASC',
238 ),
239 ));
240
241 while ($row = $resBlock->fetch())
242 {
243 $this->blocksToUpdate[$row['CODE']][$row['ID']] = new Block($row['ID'], $row);
244 if (count($this->blocksToUpdate) > self::STEP_PORTION)
245 {
246 unset($this->blocksToUpdate[$row['CODE']]);
247 break;
248 }
249
250// save sites ID for current blocks to reset cache later
251 $this->sitesToUpdate[$row['ID']] = $row['SITE_ID'];
252 }
253 }
254
255
256 private function processBlocks()
257 {
258 $this->collectBlocks();
259
260 foreach ($this->blocksToUpdate as $code => $blocks)
261 {
262 foreach ($blocks as $block)
263 {
264 if (is_array($this->dataToUpdate['BLOCKS'][$code]) && !empty($this->dataToUpdate['BLOCKS'][$code]))
265 {
266 $this->updateBlock($block);
267
268// after processing block save site ID to update cache later (only if update needed)
269 if (
270 isset($this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES']) &&
271 $this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES'] == 'Y'
272 )
273 {
274 $this->status['SITES_TO_UPDATE'][] = $this->sitesToUpdate[$block->getId()];
275 }
276 }
277
278 $this->status['STEPS']++;
279 }
280 }
281
282 $this->finishStep();
283 }
284
285 private function updateBlock(Block $block)
286 {
287 $code = $block->getCode();
288 $doc = $block->getDom();
289
290 foreach ($this->dataToUpdate['BLOCKS'][$code]['NODES'] as $selector => $rules)
291 {
292 $resultList = $doc->querySelectorAll($selector);
293
294// prepare ATTRS
295 $nodeAttrs = array();
296 if (is_array($rules['ATTRS_ADD']) && !empty($rules['ATTRS_ADD']))
297 {
298 $nodeAttrs = array_merge($nodeAttrs, $rules['ATTRS_ADD']);
299 }
300 if (is_array($rules['ATTRS_REMOVE']) && !empty($rules['ATTRS_REMOVE']))
301 {
302 $nodeAttrs = array_merge($nodeAttrs, array_fill_keys(array_values($rules['ATTRS_REMOVE']), ''));
303 }
304
305// PROCESS
306 foreach ($resultList as $nth => $resultNode)
307 {
308// FILTER
309// use until cant add some filters in DOM\Parser
310 if (is_array($rules['FILTER']) && !empty($rules['FILTER']))
311 {
312 $notFilterd = false;
313// By content. May have 'NOT' key
314 if (
315 isset($rules['FILTER']['CONTENT']) && is_array($rules['FILTER']['CONTENT']) &&
316 (
317 $rules['FILTER']['CONTENT']['VALUE'] != $resultNode->getInnerHTML() ||
318 (
319 $rules['FILTER']['CONTENT']['NOT'] &&
320 $rules['FILTER']['CONTENT']['VALUE'] == $resultNode->getInnerHTML()
321 )
322 )
323 )
324 {
325 $notFilterd = true;
326 }
327
328// by position in DOM
329 if (
330 isset($rules['FILTER']['NTH']) && is_array($rules['FILTER']['NTH']) &&
331 isset($rules['FILTER']['NTH']['VALUE']) &&
332 $nth + 1 != $rules['FILTER']['NTH']['VALUE']
333 )
334 {
335 $notFilterd = true;
336 }
337
338 if ($notFilterd)
339 {
340 continue;
341 }
342 }
343
344// CLASSES
345 $classesChange = false;
346 $nodeClasses = $resultNode->getClassList();
347 if (is_array($rules['CLASSES_REMOVE']) && !empty($rules['CLASSES_REMOVE']))
348 {
349 $nodeClasses = array_diff($nodeClasses, $rules['CLASSES_REMOVE']);
350 $classesChange = true;
351 }
352
353 if (is_array($rules['CLASSES_ADD']) && !empty($rules['CLASSES_ADD']))
354 {
355 $nodeClasses = array_merge($nodeClasses, $rules['CLASSES_ADD']);
356 $classesChange = true;
357 }
358
359 if (is_array($rules['CLASSES_REPLACE']) &&
360 array_key_exists('PATTERN', $rules['CLASSES_REPLACE']) &&
361 array_key_exists('REPLACE', $rules['CLASSES_REPLACE']))
362 {
363 $nodeClassesStr = implode(' ', $nodeClasses);
364 $nodeClassesReplace = preg_replace(
365 '/' . $rules['CLASSES_REPLACE']['PATTERN'] . '/i',
366 $rules['CLASSES_REPLACE']['REPLACE'],
367 $nodeClassesStr
368 );
369 if ($nodeClassesReplace !== null)
370 {
371 $nodeClasses = explode(' ', $nodeClassesReplace);
372 $classesChange = true;
373 }
374 }
375
376// APPLY changes
377 $nodeClasses = array_unique($nodeClasses);
378 if ($classesChange)
379 {
380 $resultNode->setClassName(implode(' ', $nodeClasses));
381 }
382
383// ID
384 if ($rules['ID_REMOVE'] && $rules['ID_REMOVE'] == 'Y')
385 {
386 $resultNode->removeAttribute('id');
387 }
388
389// ATTRS
390 foreach ($nodeAttrs as $name => $value)
391 {
392// reduce string (in attributes may be a complex data)
393 $value = str_replace(array("\n", "\t"), "", $value);
394
395 if ($value)
396 {
397 $resultNode->setAttribute($name, is_array($value) ? json_encode($value) : $value);
398 }
399 else
400 {
401 $resultNode->removeAttribute($name);
402 }
403 }
404
405// REMOVE NODE
406 if (isset($rules['NODE_REMOVE']) && $rules['NODE_REMOVE'] === true)
407 {
408 $resultNode->getParentNode()->removeChild($resultNode);
409 }
410
411// REPLACE CONTENT by regexp
412// be CAREFUL!
413 if (
414 isset($rules['REPLACE_CONTENT']) && is_array($rules['REPLACE_CONTENT']) &&
415 array_key_exists('regexp', $rules['REPLACE_CONTENT']) &&
416 array_key_exists('replace', $rules['REPLACE_CONTENT'])
417 )
418 {
419 $innerHtml = $resultNode->getInnerHTML();
420 $innerHtml = preg_replace($rules['REPLACE_CONTENT']['regexp'], $rules['REPLACE_CONTENT']['replace'], $innerHtml);
421 if($innerHtml <> '')
422 {
423 $resultNode->setInnerHTML($innerHtml);
424 }
425 }
426 }
427
428
429// add CONTAINER around nodes.
430 if (
431 isset($rules['CONTAINER_ADD']) && is_array($rules['CONTAINER_ADD']) &&
432 isset($rules['CONTAINER_ADD']['CLASSES']) &&
433 !empty($resultList)
434 )
435 {
436 if (!is_array($rules['CONTAINER_ADD']['CLASSES']))
437 {
438 $rules['CONTAINER_ADD']['CLASSES'] = [$rules['CONTAINER_ADD']['CLASSES']];
439 }
440// check if container exist
441 $firstNode = $resultList[0];
442 $parentNode = $firstNode->getParentNode();
443 $parentClasses = $parentNode->getClassList();
444 if (!empty(array_diff($rules['CONTAINER_ADD']['CLASSES'], $parentClasses)))
445 {
446// param TO_EACH - add container to each element. Default (false) - add container once to all nodes
447 if (!isset($rules['CONTAINER_ADD']['TO_EACH']) || $rules['CONTAINER_ADD']['TO_EACH'] !== true)
448 {
449 $containerNode = new Element($rules['CONTAINER_ADD']['TAG'] ? $rules['CONTAINER_ADD']['TAG'] : 'div');
450 $containerNode->setOwnerDocument($doc);
451 $containerNode->setClassName(implode(' ', $rules['CONTAINER_ADD']['CLASSES']));
452 $parentNode->insertBefore($containerNode, $firstNode);
453 foreach ($resultList as $resultNode)
454 {
455 $parentNode->removeChild($resultNode);
456 $containerNode->appendChild($resultNode);
457 }
458 }
459 else
460 {
461 foreach ($resultList as $resultNode)
462 {
463 $containerNode = new Element($rules['CONTAINER_ADD']['TAG'] ? $rules['CONTAINER_ADD']['TAG'] : 'div');
464 $containerNode->setOwnerDocument($doc);
465 $containerNode->setClassName(implode(' ', $rules['CONTAINER_ADD']['CLASSES']));
466 $parentNode->insertBefore($containerNode, $resultNode);
467
468 $parentNode->removeChild($resultNode);
469 $containerNode->appendChild($resultNode);
470 }
471 }
472 }
473 }
474 }
475 $block->saveContent($doc->saveHTML());
476
477// updates COMPONENTS params
478 if (is_array($this->dataToUpdate['BLOCKS'][$code]['UPDATE_COMPONENTS']))
479 {
480 foreach ($this->dataToUpdate['BLOCKS'][$code]['UPDATE_COMPONENTS'] as $selector => $params)
481 {
482 $block->updateNodes(array($selector => $params));
483 }
484 }
485
486// if need remove PHP - we must use block content directly, not DOM parser
487 if (
488 $this->dataToUpdate['BLOCKS'][$code]['CLEAR_PHP'] &&
489 $this->dataToUpdate['BLOCKS'][$code]['CLEAR_PHP'] == 'Y'
490 )
491 {
492 $content = $block->getContent();
493 $content = preg_replace('/<\?.*\?>/s', '', $content);
494 $block->saveContent($content);
495 }
496
497// change block SORT
498 if (
499 $this->dataToUpdate['BLOCKS'][$code]['SET_SORT'] &&
500 is_numeric($this->dataToUpdate['BLOCKS'][$code]['SET_SORT'])
501 )
502 {
503 $block->setSort($this->dataToUpdate['BLOCKS'][$code]['SET_SORT']);
504 }
505
506 $block->save();
507 }
508
509
510 private function finishStep()
511 {
512// processed blocks must be removed from data
513 foreach ($this->codesToStep as $code)
514 {
515 unset($this->dataToUpdate['BLOCKS'][$code]);
516 }
517
518 Option::set('landing', $this->getOptionName(), serialize($this->dataToUpdate));
519 Option::set('landing', self::OPTION_STATUS_NAME, serialize($this->status));
520 }
521
522 private function finishOption()
523 {
524// clean cloud sites cache only if needed
525 $this->updateSites();
526
527// finish current updater id, try next
528 Option::delete('landing', array('name' => $this->getOptionName()));
529 $this->status['SITES_TO_UPDATE'] = array();
530 $this->status['UPDATER_ID'] = '';
531 Option::set('landing', self::OPTION_STATUS_NAME, serialize($this->status));
532 }
533
534
535 private function updateSites()
536 {
537 if (
538 isset($this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES']) &&
539 $this->status['PARAMS'][$this->getOptionName()]['UPDATE_PUBLISHED_SITES'] == 'Y' &&
540 Loader::includeModule('bitrix24')
541
542 && false
543// dbg: need this?
544 )
545 {
546 foreach (array_unique($this->status['SITES_TO_UPDATE']) as $siteId)
547 {
548 if (intval($siteId))
549 {
550// Site::update($siteId, array());
551 }
552 }
553 }
554 }
555
556
562 public static function disableBlockDelete(Entity\Event $event)
563 {
564 if (\Bitrix\Landing\Update\Stepper::checkAgentActivity('\Bitrix\Landing\Update\Block\NodeAttributes'))
565 {
566 $result = new Entity\EventResult();
567 $result->setErrors(array(
569 Loc::getMessage('LANDING_BLOCK_DISABLE_DELETE'),
570 'BLOCK_DISABLE_DELETE'
571 ),
572 ));
573
574 return $result;
575 }
576 else
577 {
579 }
580 }
581
587 public static function disablePublication(\Bitrix\Main\Event $event)
588 {
589 if (\Bitrix\Landing\Update\Stepper::checkAgentActivity('\Bitrix\Landing\Update\Block\NodeAttributes'))
590 {
591 $result = new Entity\EventResult;
592 $result->setErrors(array(
593 new \Bitrix\Main\Entity\EntityError(
594 Loc::getMessage('LANDING_DISABLE_PUBLICATION'),
595 'LANDING_DISABLE_PUBLICATION'
596 ),
597 ));
598
599 return $result;
600 }
601 else
602 {
604 }
605 }
606
607
611 public static function removeCustomEvents()
612 {
613 $eventManager = \Bitrix\Main\EventManager::getInstance();
614 $eventManager->unregisterEventHandler(
615 'landing',
616 '\Bitrix\Landing\Internals\Block::OnBeforeDelete',
617 'landing',
618 '\Bitrix\Landing\Update\Block\NodeAttributes',
619 'disableBlockDelete');
620 $eventManager->unregisterEventHandler(
621 'landing',
622 'onLandingPublication',
623 'landing',
624 '\Bitrix\Landing\Update\Block\NodeAttributes',
625 'disablePublication'
626 );
627 }
628
629
635 public static function updateFormDomainByConnector($event)
636 {
637 trigger_error(
638 "Now using embedded forms, no need domain. You must remove updateFormDomainByConnector() call",
639 E_USER_WARNING
640 );
641 }
642
649 public static function updateFormDomain($domains = array())
650 {
651 trigger_error(
652 "Now using embedded forms, no need domain. You must remove updateFormDomain() call",
653 E_USER_WARNING
654 );
655 }
656
660}
static disablePublication(\Bitrix\Main\Event $event)
static disableBlockDelete(Entity\Event $event)
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29