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