86284a057e43dbe3f887fe5f3b6c8041ff87b093
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / View / Mail / Html / Element / AbstractElementView.php
1 <?php
2 namespace TYPO3\CMS\Form\View\Mail\Html\Element;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Form\Domain\Model\Element\AbstractElement;
19 use TYPO3\CMS\Form\ObjectFactory;
20 use TYPO3\CMS\Form\View\Mail\Html\Additional\AdditionalElementView;
21
22 /**
23 * Abstract class for the form elements view
24 */
25 abstract class AbstractElementView {
26
27 /**
28 * The model for the current object
29 *
30 * @var \TYPO3\CMS\Form\Domain\Model\Element\AbstractElement
31 */
32 protected $model;
33
34 /**
35 * Wrap for elements
36 *
37 * @var string
38 */
39 protected $elementWrap = '
40 <tr>
41 <element />
42 </tr>
43 ';
44
45 /**
46 * True if element needs no element wrap
47 * like <li>element</li>
48 *
49 * @var bool
50 */
51 protected $noWrap = FALSE;
52
53 /**
54 * Constructor
55 *
56 * @param AbstractElement $model Current elements model
57 */
58 public function __construct($model) {
59 $this->model = $model;
60 }
61
62 /**
63 * Parse the XML of a view object,
64 * check the node type and name
65 * and add the proper XML part of child tags
66 * to the \DOMDocument of the current tag
67 *
68 * @param \DOMDocument $dom
69 * @param \DOMNode $reference Current XML structure
70 * @param bool $emptyElement
71 * @return bool
72 */
73 protected function parseXML(\DOMDocument $dom, \DOMNode $reference, $emptyElement = FALSE) {
74 $node = $reference->firstChild;
75 while (!is_null($node)) {
76 $deleteNode = FALSE;
77 $nodeType = $node->nodeType;
78 $nodeName = $node->nodeName;
79 switch ($nodeType) {
80 case XML_TEXT_NODE:
81 break;
82 case XML_ELEMENT_NODE:
83 switch ($nodeName) {
84 case 'containerWrap':
85 $containerWrap = $this->render('containerWrap');
86 if ($containerWrap) {
87 $this->replaceNodeWithFragment($dom, $node, $containerWrap);
88 } else {
89 $emptyElement = TRUE;
90 }
91 $deleteNode = TRUE;
92 break;
93 case 'elements':
94 $replaceNode = $this->getChildElements($dom);
95 if ($replaceNode) {
96 $node->parentNode->replaceChild($replaceNode, $node);
97 } else {
98 $emptyElement = TRUE;
99 }
100 break;
101 case 'label':
102 if (!strrchr(get_class($this), 'AdditionalElement')) {
103 if ($this->model->additionalIsSet($nodeName)) {
104 $this->replaceNodeWithFragment($dom, $node, $this->getAdditional('label'));
105 } else {
106 $replaceNode = $dom->createTextNode($this->model->getName());
107 $node->parentNode->insertBefore($replaceNode, $node);
108 }
109 }
110 $deleteNode = TRUE;
111 break;
112 case 'legend':
113 if (!strrchr(get_class($this), 'AdditionalElement')) {
114 if ($this->model->additionalIsSet($nodeName)) {
115 $this->replaceNodeWithFragment($dom, $node, $this->getAdditional('legend'));
116 }
117 $deleteNode = TRUE;
118 }
119 break;
120 case 'inputvalue':
121 if (array_key_exists('checked', $this->model->getAllowedAttributes())) {
122 if (!$this->model->hasAttribute('checked')) {
123 $emptyElement = TRUE;
124 }
125 } elseif (array_key_exists('selected', $this->model->getAllowedAttributes()) && !$this->model->hasAttribute('selected')) {
126 $emptyElement = TRUE;
127 } else {
128 $inputValue = $this->getInputValue();
129 if ($inputValue != '') {
130 $replaceNode = $dom->createTextNode($this->getInputValue());
131 $node->parentNode->insertBefore($replaceNode, $node);
132 }
133 }
134 $deleteNode = TRUE;
135 break;
136 case 'labelvalue':
137
138 case 'legendvalue':
139 $replaceNode = $dom->createTextNode($this->getAdditionalValue());
140 $node->parentNode->insertBefore($replaceNode, $node);
141 $deleteNode = TRUE;
142 break;
143 }
144 break;
145 }
146 // Parse the child nodes of this node if available
147 if ($node->hasChildNodes()) {
148 $emptyElement = $this->parseXML($dom, $node, $emptyElement);
149 }
150 // Get the current node for deletion if replaced. We need this because nextSibling can be empty
151 $oldNode = $node;
152 // Go to next sibling to parse
153 $node = $node->nextSibling;
154 // Delete the old node. This can only be done after going to the next sibling
155 if ($deleteNode) {
156 $oldNode->parentNode->removeChild($oldNode);
157 }
158 }
159 return $emptyElement;
160 }
161
162 /**
163 * Get the content for the current object as \DOMDocument
164 *
165 * @param string $type Type of element for layout
166 * @param bool $returnFirstChild If TRUE, the first child will be returned instead of the \DOMDocument
167 * @return \DOMDocument|\DOMNode XML part of the view object
168 */
169 public function render($type = 'element', $returnFirstChild = TRUE) {
170 $useLayout = $this->getLayout((string)$type);
171 $dom = new \DOMDocument('1.0', 'utf-8');
172 $dom->formatOutput = TRUE;
173 $dom->preserveWhiteSpace = FALSE;
174 $dom->loadXML($useLayout);
175 $emptyElement = $this->parseXML($dom, $dom);
176 if ($emptyElement) {
177 return NULL;
178 } elseif ($returnFirstChild) {
179 return $dom->firstChild;
180 } else {
181 return $dom;
182 }
183 }
184
185 /**
186 * Ask the layoutHandler to get the layout for this object
187 *
188 * @param string $type Layout type
189 * @return string HTML string of the layout to use for this element
190 */
191 public function getLayout($type) {
192 /** @var $layoutHandler \TYPO3\CMS\Form\Layout */
193 $layoutHandler = GeneralUtility::makeInstance(\TYPO3\CMS\Form\Layout::class);
194 $layout = '';
195 switch ($type) {
196 case 'element':
197 $layoutDefault = $this->layout;
198 $layout = $layoutHandler->getLayoutByObject(\TYPO3\CMS\Form\Utility\FormUtility::getInstance()->getLastPartOfClassName($this, TRUE), $layoutDefault);
199 break;
200 case 'elementWrap':
201 $layoutDefault = $this->elementWrap;
202 $elementWrap = $layoutHandler->getLayoutByObject($type, $layoutDefault);
203 $layout = str_replace('<element />', $this->getLayout('element'), $elementWrap);
204 break;
205 case 'containerWrap':
206 $layoutDefault = $this->containerWrap;
207 $layout = $layoutHandler->getLayoutByObject($type, $layoutDefault);
208 break;
209 }
210 return $layout;
211 }
212
213 /**
214 * Replace the current node with a document fragment
215 *
216 * @param \DOMDocument $dom
217 * @param \DOMNode $node Current Node
218 * @param \DOMNode $value Value to import
219 * @return void
220 */
221 public function replaceNodeWithFragment(\DOMDocument $dom, \DOMNode $node, \DOMNode $value) {
222 $replaceNode = $dom->createDocumentFragment();
223 $domNode = $dom->importNode($value, TRUE);
224 $replaceNode->appendChild($domNode);
225 $node->parentNode->insertBefore($replaceNode, $node);
226 }
227
228 /**
229 * Set the attributes on the html tags according to the attributes that are
230 * assigned in the model for a certain element
231 *
232 * @param \DOMElement $domElement DOM element of the specific HTML tag
233 * @return void
234 */
235 public function setAttributes(\DOMElement $domElement) {
236 $attributes = $this->model->getAttributes();
237 foreach ($attributes as $key => $attribute) {
238 if (!empty($attribute)) {
239 $value = htmlspecialchars($attribute->getValue(), ENT_QUOTES);
240 if ($value !== '') {
241 $domElement->setAttribute($key, $value);
242 }
243 }
244 }
245 }
246
247 /**
248 * Set a single attribute of a HTML tag specified by key
249 *
250 * @param \DOMElement $domElement DOM element of the specific HTML tag
251 * @param string $key Attribute key
252 * @return void
253 */
254 public function setAttribute(\DOMElement $domElement, $key) {
255 $value = htmlspecialchars($this->model->getAttributeValue((string)$key), ENT_QUOTES);
256 if ($value !== '') {
257 $domElement->setAttribute($key, $value);
258 }
259 }
260
261 /**
262 * Sets the value of an attribute with the value of another attribute,
263 * for instance equalizing the name and id attributes for the form tag
264 *
265 * @param \DOMElement $domElement DOM element of the specific HTML tag
266 * @param string $key Key of the attribute which needs to be changed
267 * @param string $other Key of the attribute to take the value from
268 * @return void
269 */
270 public function setAttributeWithValueofOtherAttribute(\DOMElement $domElement, $key, $other) {
271 $value = htmlspecialchars($this->model->getAttributeValue((string)$other), ENT_QUOTES);
272 if ($value !== '') {
273 $domElement->setAttribute($key, $value);
274 }
275 }
276
277 /**
278 * Load and instantiate an additional object
279 *
280 * @param string $class Type of additional
281 * @return AdditionalElementView
282 */
283 protected function createAdditional($class) {
284 $class = strtolower((string)$class);
285 $className = 'TYPO3\\CMS\\Form\\View\\Mail\\Html\\Additional\\' . ucfirst($class) . 'AdditionalElementView';
286 return ObjectFactory::createFormObject($className, $this->model);
287 }
288
289 /**
290 * Create additional object by key and render the content
291 *
292 * @param string $key Type of additional
293 * @return \DOMNode
294 */
295 public function getAdditional($key) {
296 $additional = $this->createAdditional($key);
297 return $additional->render();
298 }
299
300 /**
301 * @return string
302 */
303 public function getInputValue() {
304 if (method_exists($this->model, 'getData')) {
305 $inputValue = $this->model->getData();
306 } else {
307 $inputValue = $this->model->getAttributeValue('value');
308 }
309
310 $inputValue = htmlspecialchars($inputValue, ENT_QUOTES);
311 $inputValue = nl2br($inputValue, TRUE);
312 return $inputValue;
313 }
314
315 /**
316 * Return the id for the element wraps,
317 * like <li id="csc-form-"> ... </li>
318 *
319 * @return string
320 */
321 public function getElementWrapId() {
322 $elementId = (int)$this->model->getElementId();
323 $wrapId = 'csc-form-' . $elementId;
324 return $wrapId;
325 }
326
327 /**
328 * Returns the type for the element wraps,
329 * like <li class="csc-form-element csc-form-element-abstract">...</li>
330 *
331 * @return string
332 */
333 public function getElementWrapType() {
334 $elementType = strtolower(\TYPO3\CMS\Form\Utility\FormUtility::getInstance()->getLastPartOfClassName($this->model));
335 $wrapType = 'csc-form-element csc-form-element-' . $elementType;
336 return $wrapType;
337 }
338
339 /**
340 * Returns all element wraps.
341 *
342 * @return string
343 */
344 public function getElementWraps() {
345 $wraps = array(
346 $this->getElementWrapId(),
347 $this->getElementWrapType()
348 );
349 return implode(' ', $wraps);
350 }
351
352 /**
353 * Read the noWrap value of an element
354 * if TRUE the element does not need an element wrap
355 * like <li>element</li>
356 *
357 * @return bool
358 */
359 public function noWrap() {
360 return $this->noWrap;
361 }
362
363 }