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