1e9f0cafd2810583264f45cae7e13d2893e4c124
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Domain / Model / Renderable / AbstractCompositeRenderable.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Domain\Model\Renderable;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It originated from the Neos.Form package (www.neos.io)
9 *
10 * It is free software; you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License, either version 2
12 * of the License, or any later version.
13 *
14 * For the full copyright and license information, please read the
15 * LICENSE.txt file that was distributed with this source code.
16 *
17 * The TYPO3 project - inspiring people to share!
18 */
19
20 use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
21
22 /**
23 * Convenience base class which implements common functionality for most
24 * classes which implement CompositeRenderableInterface, i.e. have **child renderable elements**.
25 *
26 * Scope: frontend
27 * **This class is NOT meant to be sub classed by developers.**
28 */
29 abstract class AbstractCompositeRenderable extends AbstractRenderable implements CompositeRenderableInterface
30 {
31
32 /**
33 * array of child renderables
34 *
35 * @var \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface[]
36 */
37 protected $renderables = [];
38
39 /**
40 * Add a renderable to the list of child renderables.
41 *
42 * This function will be wrapped by the subclasses, f.e. with an "addPage"
43 * or "addElement" method with the correct type hint.
44 *
45 * @param RenderableInterface $renderable
46 * @throws FormDefinitionConsistencyException
47 * @internal
48 */
49 protected function addRenderable(RenderableInterface $renderable)
50 {
51 if ($renderable->getParentRenderable() !== null) {
52 throw new FormDefinitionConsistencyException(sprintf('The renderable with identifier "%s" is already added to another element (element identifier: "%s").', $renderable->getIdentifier(), $renderable->getParentRenderable()->getIdentifier()), 1325665144);
53 }
54 $renderable->setIndex(count($this->renderables));
55 $renderable->setParentRenderable($this);
56 $this->renderables[] = $renderable;
57 }
58
59 /**
60 * Move $renderableToMove before $referenceRenderable
61 *
62 * This function will be wrapped by the subclasses, f.e. with an "movePageBefore"
63 * or "moveElementBefore" method with the correct type hint.
64 *
65 * @param RenderableInterface $renderableToMove
66 * @param RenderableInterface $referenceRenderable
67 * @throws FormDefinitionConsistencyException
68 * @internal
69 */
70 protected function moveRenderableBefore(RenderableInterface $renderableToMove, RenderableInterface $referenceRenderable)
71 {
72 if ($renderableToMove->getParentRenderable() !== $referenceRenderable->getParentRenderable() || $renderableToMove->getParentRenderable() !== $this) {
73 throw new FormDefinitionConsistencyException('Moved renderables need to be part of the same parent element.', 1326089744);
74 }
75
76 $reorderedRenderables = [];
77 $i = 0;
78 foreach ($this->renderables as $renderable) {
79 if ($renderable === $renderableToMove) {
80 continue;
81 }
82
83 if ($renderable === $referenceRenderable) {
84 $reorderedRenderables[] = $renderableToMove;
85 $renderableToMove->setIndex($i);
86 $i++;
87 }
88 $reorderedRenderables[] = $renderable;
89 $renderable->setIndex($i);
90 $i++;
91 }
92 $this->renderables = $reorderedRenderables;
93 }
94
95 /**
96 * Move $renderableToMove after $referenceRenderable
97 *
98 * This function will be wrapped by the subclasses, f.e. with an "movePageAfter"
99 * or "moveElementAfter" method with the correct type hint.
100 *
101 * @param RenderableInterface $renderableToMove
102 * @param RenderableInterface $referenceRenderable
103 * @throws FormDefinitionConsistencyException
104 * @internal
105 */
106 protected function moveRenderableAfter(RenderableInterface $renderableToMove, RenderableInterface $referenceRenderable)
107 {
108 if ($renderableToMove->getParentRenderable() !== $referenceRenderable->getParentRenderable() || $renderableToMove->getParentRenderable() !== $this) {
109 throw new FormDefinitionConsistencyException('Moved renderables need to be part of the same parent element.', 1477083145);
110 }
111
112 $reorderedRenderables = [];
113 $i = 0;
114 foreach ($this->renderables as $renderable) {
115 if ($renderable === $renderableToMove) {
116 continue;
117 }
118
119 $reorderedRenderables[] = $renderable;
120 $renderable->setIndex($i);
121 $i++;
122
123 if ($renderable === $referenceRenderable) {
124 $reorderedRenderables[] = $renderableToMove;
125 $renderableToMove->setIndex($i);
126 $i++;
127 }
128 }
129 $this->renderables = $reorderedRenderables;
130 }
131
132 /**
133 * Returns all RenderableInterface instances of this composite renderable recursively
134 *
135 * @return RenderableInterface[]
136 * @internal
137 */
138 public function getRenderablesRecursively(): array
139 {
140 $renderables = [[]];
141 foreach ($this->renderables as $renderable) {
142 $renderables[] = $renderable;
143 if ($renderable instanceof CompositeRenderableInterface) {
144 $renderables[] = $renderable->getRenderablesRecursively();
145 }
146 }
147 return array_merge(...$renderables);
148 }
149
150 /**
151 * Remove a renderable from this renderable.
152 *
153 * This function will be wrapped by the subclasses, f.e. with an "removePage"
154 * or "removeElement" method with the correct type hint.
155 *
156 * @param RenderableInterface $renderableToRemove
157 * @throws FormDefinitionConsistencyException
158 * @internal
159 */
160 protected function removeRenderable(RenderableInterface $renderableToRemove)
161 {
162 if ($renderableToRemove->getParentRenderable() !== $this) {
163 throw new FormDefinitionConsistencyException('The renderable to be removed must be part of the calling parent renderable.', 1326090127);
164 }
165
166 $updatedRenderables = [];
167 foreach ($this->renderables as $renderable) {
168 if ($renderable === $renderableToRemove) {
169 continue;
170 }
171
172 $updatedRenderables[] = $renderable;
173 }
174 $this->renderables = $updatedRenderables;
175
176 $renderableToRemove->onRemoveFromParentRenderable();
177 }
178
179 /**
180 * Register this element at the parent form, if there is a connection to the parent form.
181 *
182 * @internal
183 */
184 public function registerInFormIfPossible()
185 {
186 parent::registerInFormIfPossible();
187 foreach ($this->renderables as $renderable) {
188 $renderable->registerInFormIfPossible();
189 }
190 }
191
192 /**
193 * This function is called after a renderable has been removed from its parent
194 * renderable.
195 * This just passes the event down to all child renderables of this composite renderable.
196 *
197 * @internal
198 */
199 public function onRemoveFromParentRenderable()
200 {
201 foreach ($this->renderables as $renderable) {
202 $renderable->onRemoveFromParentRenderable();
203 }
204 parent::onRemoveFromParentRenderable();
205 }
206 }