Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Structure.php
Go to the documentation of this file.
1 <?php
7 namespace Magento\Framework\Data;
8 
10 
14 class Structure
15 {
19  const PARENT = 'parent';
20 
21  const CHILDREN = 'children';
22 
23  const GROUPS = 'groups';
24 
28  protected $_elements = [];
29 
35  public function __construct(array $elements = null)
36  {
37  if (null !== $elements) {
38  $this->importElements($elements);
39  }
40  }
41 
49  public function importElements(array $elements)
50  {
51  $this->_elements = $elements;
52  foreach ($elements as $elementId => $element) {
53  if (is_numeric($elementId)) {
54  throw new LocalizedException(
55  new \Magento\Framework\Phrase("Element ID must not be numeric: '%1'.", [$elementId])
56  );
57  }
58  $this->_assertParentRelation($elementId);
59  if (isset($element[self::GROUPS])) {
60  $groups = $element[self::GROUPS];
61  $this->_assertArray($groups);
62  foreach ($groups as $groupName => $group) {
63  $this->_assertArray($group);
64  if ($group !== array_flip($group)) {
65  throw new LocalizedException(
66  new \Magento\Framework\Phrase(
67  '"%2" is an invalid format of "%1" group. Verify the format and try again.',
68  [$groupName, var_export($group, 1)]
69  )
70  );
71  }
72  foreach ($group as $groupElementId) {
73  $this->_assertElementExists($groupElementId);
74  }
75  }
76  }
77  }
78  }
79 
87  protected function _assertParentRelation($elementId)
88  {
89  $element = $this->_elements[$elementId];
90 
91  // element presence in its parent's nested set
92  if (isset($element[self::PARENT])) {
93  $parentId = $element[self::PARENT];
94  $this->_assertElementExists($parentId);
95  if (empty($this->_elements[$parentId][self::CHILDREN][$elementId])) {
96  throw new LocalizedException(
97  new \Magento\Framework\Phrase(
98  'The "%1" is not in the nested set of "%2", causing the parent-child relation to break. '
99  . 'Verify and try again.',
100  [$elementId, $parentId]
101  )
102  );
103  }
104  }
105 
106  // element presence in its children
107  if (isset($element[self::CHILDREN])) {
109  $this->_assertArray($children);
110  if ($children !== array_flip(array_flip($children))) {
111  throw new LocalizedException(
112  new \Magento\Framework\Phrase(
113  'The "%1" format of children is invalid. Verify and try again.',
114  [var_export($children, 1)]
115  )
116  );
117  }
118  foreach (array_keys($children) as $childId) {
119  $this->_assertElementExists($childId);
120  if (!isset(
121  $this->_elements[$childId][self::PARENT]
122  ) || $elementId !== $this->_elements[$childId][self::PARENT]
123  ) {
124  throw new LocalizedException(
125  new \Magento\Framework\Phrase(
126  'The "%1" doesn\'t have "%2" as parent, causing the parent-child relation to break. '
127  . 'Verify and try again.',
128  [$childId, $elementId]
129  )
130  );
131  }
132  }
133  }
134  }
135 
141  public function exportElements()
142  {
143  return $this->_elements;
144  }
145 
154  public function createElement($elementId, array $data)
155  {
156  if (isset($this->_elements[$elementId])) {
157  throw new LocalizedException(
158  new \Magento\Framework\Phrase('An element with a "%1" ID already exists.', [$elementId])
159  );
160  }
161  $this->_elements[$elementId] = [];
162  foreach ($data as $key => $value) {
163  $this->setAttribute($elementId, $key, $value);
164  }
165  }
166 
173  public function getElement($elementId)
174  {
175  return $this->_elements[$elementId] ?? false;
176  }
177 
184  public function hasElement($elementId)
185  {
186  return isset($this->_elements[$elementId]);
187  }
188 
199  public function unsetElement($elementId, $recursive = true)
200  {
201  if (isset($this->_elements[$elementId][self::CHILDREN])) {
202  foreach (array_keys($this->_elements[$elementId][self::CHILDREN]) as $childId) {
203  $this->_assertElementExists($childId);
204  if ($recursive) {
205  $this->unsetElement($childId, $recursive);
206  } else {
207  unset($this->_elements[$childId][self::PARENT]);
208  }
209  }
210  }
211  $this->unsetChild($elementId);
212  $wasFound = isset($this->_elements[$elementId]);
213  unset($this->_elements[$elementId]);
214  return $wasFound;
215  }
216 
226  public function setAttribute($elementId, $attribute, $value)
227  {
228  $this->_assertElementExists($elementId);
229  switch ($attribute) {
230  case self::PARENT:
231  // break is intentionally omitted
232  case self::CHILDREN:
233  case self::GROUPS:
234  throw new \InvalidArgumentException("The '{$attribute}' attribute is reserved and can't be set.");
235  default:
236  $this->_elements[$elementId][$attribute] = $value;
237  }
238  return $this;
239  }
240 
248  public function getAttribute($elementId, $attribute)
249  {
250  $this->_assertElementExists($elementId);
251  if (isset($this->_elements[$elementId][$attribute])) {
252  return $this->_elements[$elementId][$attribute];
253  }
254  return false;
255  }
256 
265  public function renameElement($oldId, $newId)
266  {
267  $this->_assertElementExists($oldId);
268  if (!$newId || isset($this->_elements[$newId])) {
269  throw new LocalizedException(
270  new \Magento\Framework\Phrase('An element with a "%1" ID is already defined.', [$newId])
271  );
272  }
273 
274  // rename in registry
275  $this->_elements[$newId] = $this->_elements[$oldId];
276 
277  // rename references in children
278  if (isset($this->_elements[$oldId][self::CHILDREN])) {
279  foreach (array_keys($this->_elements[$oldId][self::CHILDREN]) as $childId) {
280  $this->_assertElementExists($childId);
281  $this->_elements[$childId][self::PARENT] = $newId;
282  }
283  }
284 
285  // rename key in its parent's children array
286  if (isset($this->_elements[$oldId][self::PARENT]) && ($parentId = $this->_elements[$oldId][self::PARENT])) {
287  $alias = $this->_elements[$parentId][self::CHILDREN][$oldId];
288  $offset = $this->_getChildOffset($parentId, $oldId);
289  unset($this->_elements[$parentId][self::CHILDREN][$oldId]);
290  $this->setAsChild($newId, $parentId, $alias, $offset);
291  }
292 
293  unset($this->_elements[$oldId]);
294  return $this;
295  }
296 
308  public function setAsChild($elementId, $parentId, $alias = '', $position = null)
309  {
310  if ($elementId == $parentId) {
311  throw new LocalizedException(
312  new \Magento\Framework\Phrase(
313  'The "%1" was incorrectly set as a child to itself. Resolve the issue and try again.',
314  [$elementId]
315  )
316  );
317  }
318  if ($this->_isParentRecursively($elementId, $parentId)) {
319  throw new LocalizedException(
320  new \Magento\Framework\Phrase(
321  'The "%3" cannot be set as child to "%1" because "%1" is a parent of "%2" recursively. '
322  . 'Resolve the issue and try again.',
323  [$elementId, $parentId, $elementId]
324  )
325  );
326  }
327  $this->unsetChild($elementId);
328  unset($this->_elements[$parentId][self::CHILDREN][$elementId]);
329  $this->_insertChild($parentId, $elementId, $position, $alias);
330  }
331 
344  public function unsetChild($elementId, $alias = null)
345  {
346  if (null === $alias) {
347  $childId = $elementId;
348  } else {
349  $childId = $this->getChildId($elementId, $alias);
350  }
351  $parentId = $this->getParentId($childId);
352  unset($this->_elements[$childId][self::PARENT]);
353  if ($parentId) {
354  unset($this->_elements[$parentId][self::CHILDREN][$childId]);
355  if (empty($this->_elements[$parentId][self::CHILDREN])) {
356  unset($this->_elements[$parentId][self::CHILDREN]);
357  }
358  }
359  return $this;
360  }
361 
373  public function reorderChild($parentId, $childId, $position)
374  {
375  $alias = $this->getChildAlias($parentId, $childId);
376  $currentOffset = $this->_getChildOffset($parentId, $childId);
377  $offset = $position;
378  if ($position > 0) {
379  if ($position >= $currentOffset + 1) {
380  $offset -= 1;
381  }
382  } elseif ($position < 0) {
383  if ($position < $currentOffset + 1 - count($this->_elements[$parentId][self::CHILDREN])) {
384  if ($position === -1) {
385  $offset = null;
386  } else {
387  $offset += 1;
388  }
389  }
390  }
391  $this->unsetChild($childId)->_insertChild($parentId, $childId, $offset, $alias);
392  return $this->_getChildOffset($parentId, $childId) + 1;
393  }
394 
411  public function reorderToSibling($parentId, $childId, $siblingId, $offset)
412  {
413  $this->_getChildOffset($parentId, $childId);
414  if ($childId === $siblingId) {
415  $newOffset = $this->_getRelativeOffset($parentId, $siblingId, $offset);
416  return $this->reorderChild($parentId, $childId, $newOffset);
417  }
418  $alias = $this->getChildAlias($parentId, $childId);
419  $newOffset = $this->unsetChild($childId)->_getRelativeOffset($parentId, $siblingId, $offset);
420  $this->_insertChild($parentId, $childId, $newOffset, $alias);
421  return $this->_getChildOffset($parentId, $childId) + 1;
422  }
423 
432  private function _getRelativeOffset($parentId, $siblingId, $delta)
433  {
434  $newOffset = $this->_getChildOffset($parentId, $siblingId) + $delta;
435  if ($delta < 0) {
436  $newOffset += 1;
437  }
438  if ($newOffset < 0) {
439  $newOffset = 0;
440  }
441  return $newOffset;
442  }
443 
451  public function getChildId($parentId, $alias)
452  {
453  if (isset($this->_elements[$parentId][self::CHILDREN])) {
454  return array_search($alias, $this->_elements[$parentId][self::CHILDREN]);
455  }
456  return false;
457  }
458 
467  public function getChildren($parentId)
468  {
469  return $this->_elements[$parentId][self::CHILDREN] ?? [];
470  }
471 
478  public function getParentId($childId)
479  {
480  return $this->_elements[$childId][self::PARENT] ?? false;
481  }
482 
490  public function getChildAlias($parentId, $childId)
491  {
492  if (isset($this->_elements[$parentId][self::CHILDREN][$childId])) {
493  return $this->_elements[$parentId][self::CHILDREN][$childId];
494  }
495  return false;
496  }
497 
505  public function addToParentGroup($childId, $groupName)
506  {
507  $parentId = $this->getParentId($childId);
508  if ($parentId) {
509  $this->_assertElementExists($parentId);
510  $this->_elements[$parentId][self::GROUPS][$groupName][$childId] = $childId;
511  return true;
512  }
513  return false;
514  }
515 
527  public function getGroupChildNames($parentId, $groupName)
528  {
529  $result = [];
530  if (isset($this->_elements[$parentId][self::GROUPS][$groupName])) {
531  foreach ($this->_elements[$parentId][self::GROUPS][$groupName] as $childId) {
532  if (isset($this->_elements[$parentId][self::CHILDREN][$childId])) {
533  $result[] = $childId;
534  }
535  }
536  }
537  return $result;
538  }
539 
548  protected function _getChildOffset($parentId, $childId)
549  {
550  $index = array_search($childId, array_keys($this->getChildren($parentId)));
551  if (false === $index) {
552  throw new LocalizedException(
553  new \Magento\Framework\Phrase(
554  'The "%1" is not a child of "%2". Resolve the issue and try again.',
555  [$childId, $parentId]
556  )
557  );
558  }
559  return $index;
560  }
561 
569  private function _isParentRecursively($childId, $potentialParentId)
570  {
571  $parentId = $this->getParentId($potentialParentId);
572  if (!$parentId) {
573  return false;
574  }
575  if ($parentId === $childId) {
576  return true;
577  }
578  return $this->_isParentRecursively($childId, $parentId);
579  }
580 
600  protected function _insertChild($targetParentId, $elementId, $offset, $alias)
601  {
602  $alias = $alias ?: $elementId;
603 
604  // validate
605  $this->_assertElementExists($elementId);
606  if (!empty($this->_elements[$elementId][self::PARENT])) {
607  throw new LocalizedException(
608  new \Magento\Framework\Phrase(
609  'The element "%1" can\'t have a parent because "%2" is already the parent of "%1".',
610  [$elementId, $this->_elements[$elementId][self::PARENT]]
611  )
612  );
613  }
614  $this->_assertElementExists($targetParentId);
615  $children = $this->getChildren($targetParentId);
616  if (isset($children[$elementId])) {
617  throw new LocalizedException(
618  new \Magento\Framework\Phrase(
619  'The element "%1" is already a child of "%2".',
620  [$elementId, $targetParentId]
621  )
622  );
623  }
624  if (false !== array_search($alias, $children)) {
625  throw new LocalizedException(
626  new \Magento\Framework\Phrase(
627  'The element "%1" can\'t have a child because "%1" already has a child with alias "%2".',
628  [$targetParentId, $alias]
629  )
630  );
631  }
632 
633  // insert
634  if (null === $offset) {
635  $offset = count($children);
636  }
637  $this->_elements[$targetParentId][self::CHILDREN] = array_merge(
638  array_slice($children, 0, $offset),
639  [$elementId => $alias],
640  array_slice($children, $offset)
641  );
642  $this->_elements[$elementId][self::PARENT] = $targetParentId;
643  }
644 
652  private function _assertElementExists($elementId)
653  {
654  if (!isset($this->_elements[$elementId])) {
655  throw new \OutOfBoundsException(
656  'The element with the "' . $elementId . '" ID wasn\'t found. Verify the ID and try again.'
657  );
658  }
659  }
660 
668  private function _assertArray($value)
669  {
670  if (!is_array($value)) {
671  throw new LocalizedException(
672  new \Magento\Framework\Phrase("An array expected: %1", [var_export($value, 1)])
673  );
674  }
675  }
676 }
getGroupChildNames($parentId, $groupName)
Definition: Structure.php:527
getChildAlias($parentId, $childId)
Definition: Structure.php:490
setAttribute($elementId, $attribute, $value)
Definition: Structure.php:226
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
reorderToSibling($parentId, $childId, $siblingId, $offset)
Definition: Structure.php:411
__construct(array $elements=null)
Definition: Structure.php:35
getChildId($parentId, $alias)
Definition: Structure.php:451
$group
Definition: sections.phtml:16
unsetChild($elementId, $alias=null)
Definition: Structure.php:344
getAttribute($elementId, $attribute)
Definition: Structure.php:248
$value
Definition: gender.phtml:16
addToParentGroup($childId, $groupName)
Definition: Structure.php:505
reorderChild($parentId, $childId, $position)
Definition: Structure.php:373
unsetElement($elementId, $recursive=true)
Definition: Structure.php:199
setAsChild($elementId, $parentId, $alias='', $position=null)
Definition: Structure.php:308
_getChildOffset($parentId, $childId)
Definition: Structure.php:548
createElement($elementId, array $data)
Definition: Structure.php:154
$children
Definition: actions.phtml:11
if(!trim($html)) $alias
Definition: details.phtml:20
importElements(array $elements)
Definition: Structure.php:49
$index
Definition: list.phtml:44
_insertChild($targetParentId, $elementId, $offset, $alias)
Definition: Structure.php:600
$element
Definition: element.phtml:12