[TASK] Add CSS Class to special input types in 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 tx_form_Domain_Model_Element_Abstract
38 */
39 protected $model;
40
41 /**
42 * @var string
43 */
44 protected $expectedModelName;
45
46 /**
47 * Wrap for elements
48 *
49 * @var string
50 */
51 protected $elementWrap = '
52 <li>
53 <element />
54 </li>
55 ';
56
57 /**
58 * True if element needs no element wrap
59 * like <li>element</li>
60 *
61 * @var boolean
62 */
63 protected $noWrap = FALSE;
64
65 /**
66 * Constructor
67 *
68 * @param tx_form_Domain_Model_Element_Abstract $model Current elements model
69 * @return void
70 */
71 public function __construct(tx_form_Domain_Model_Element_Abstract $model) {
72 if ($this->isValidModel($model) === FALSE) {
73 throw new RuntimeException('Unexpected model "' . get_class($model) . '".');
74 }
75
76 $this->model = $model;
77 }
78
79 /**
80 * Determines whether the model is expected in this object.
81 *
82 * @param tx_form_Domain_Model_Element_Abstract $model
83 * @return boolean
84 */
85 protected function isValidModel(tx_form_Domain_Model_Element_Abstract $model) {
86 return is_a($model, $this->getExpectedModelName($model));
87 }
88
89 /**
90 * Gets the expected model name.
91 *
92 * @param tx_form_Domain_Model_Element_Abstract $model
93 * @return string
94 */
95 protected function getExpectedModelName(tx_form_Domain_Model_Element_Abstract $model) {
96 if (!isset($this->expectedModelName)) {
97 $specificName = tx_form_Common::getInstance()->getLastPartOfClassName($this);
98 $this->expectedModelName = 'tx_form_Domain_Model_Element_' . $specificName;
99 }
100
101 return $this->expectedModelName;
102 }
103
104 /**
105 * Parse the XML of a view object,
106 * check the node type and name
107 * and add the proper XML part of child tags
108 * to the DOMDocument of the current tag
109 *
110 * @param DOMDocument $dom
111 * @param DOMNode $reference Current XML structure
112 * @return void
113 */
114 protected function parseXML(DOMDocument $dom, DOMNode $reference) {
115 $node = $reference->firstChild;
116
117 while (!is_null($node)) {
118 $deleteNode = FALSE;
119 $nodeType = $node->nodeType;
120 $nodeName = $node->nodeName;
121 switch ($nodeType) {
122 case XML_TEXT_NODE:
123 break;
124 case XML_ELEMENT_NODE:
125 switch($nodeName) {
126 case 'containerWrap':
127 $this->replaceNodeWithFragment($dom, $node, $this->render('containerWrap'));
128 $deleteNode = TRUE;
129 break;
130 case 'elements':
131 $replaceNode = $this->getChildElements($dom);
132 $node->parentNode->replaceChild($replaceNode, $node);
133 break;
134 case 'button':
135 case 'fieldset':
136 case 'form':
137 case 'input':
138 case 'optgroup':
139 case 'select':
140 $this->setAttributes($node);
141 break;
142 case 'label':
143 if (!strstr(get_class($this), '_Additional_')) {
144 if ($this->model->additionalIsSet($nodeName)) {
145 $this->replaceNodeWithFragment($dom, $node, $this->getAdditional('label'));
146 }
147 $deleteNode = TRUE;
148 } else {
149 if ($this->model->additionalIsSet($nodeName)) {
150 $this->setAttributeWithValueofOtherAttribute($node, 'for', 'id');
151 } else {
152 $deleteNode = TRUE;
153 }
154 }
155 break;
156 case 'legend':
157 if (!strstr(get_class($this), '_Additional_')) {
158 if ($this->model->additionalIsSet($nodeName)) {
159 $this->replaceNodeWithFragment($dom, $node, $this->getAdditional('legend'));
160 }
161 $deleteNode = TRUE;
162 }
163 break;
164 case 'textarea':
165 case 'option':
166 $this->setAttributes($node);
167 $appendNode = $dom->createTextNode($this->getElementData());
168 $node->appendChild($appendNode);
169 break;
170 case 'errorvalue':
171 case 'labelvalue':
172 case 'legendvalue':
173 case 'mandatoryvalue':
174 $replaceNode = $dom->createTextNode($this->getAdditionalValue());
175 $node->parentNode->insertBefore($replaceNode, $node);
176 $deleteNode = TRUE;
177 break;
178 case 'mandatory':
179 case 'error':
180 if ($this->model->additionalIsSet($nodeName)) {
181 $this->replaceNodeWithFragment($dom, $node, $this->getAdditional($nodeName));
182 }
183 $deleteNode = TRUE;
184 break;
185 case 'content':
186 case 'header':
187 case 'textblock':
188 $replaceNode = $dom->createTextNode($this->getElementData(FALSE));
189 $node->parentNode->insertBefore($replaceNode, $node);
190 $deleteNode = TRUE;
191 break;
192 }
193 break;
194 }
195
196 // Parse the child nodes of this node if available
197 if ($node->hasChildNodes()) {
198 $this->parseXML($dom, $node);
199 }
200
201 // Get the current node for deletion if replaced. We need this because nextSibling can be empty
202 $oldNode = $node;
203
204 // Go to next sibling to parse
205 $node = $node->nextSibling;
206
207 // Delete the old node. This can only be done after going to the next sibling
208 if ($deleteNode) {
209 $oldNode->parentNode->removeChild($oldNode);
210 }
211 }
212 }
213
214 /**
215 * Get the content for the current object as DOMDocument
216 *
217 * @param string $type Type of element for layout
218 * @param boolean $returnFirstChild If TRUE, the first child will be returned instead of the DOMDocument
219 * @return mixed DOMDocument/DOMNode XML part of the view object
220 */
221 public function render($type = 'element', $returnFirstChild = TRUE) {
222 $useLayout = $this->getLayout((string) $type);
223
224 $dom = new DOMDocument('1.0', 'utf-8');
225 $dom->formatOutput = TRUE;
226 $dom->preserveWhiteSpace = FALSE;
227 $dom->loadXML($useLayout);
228
229 $this->parseXML($dom, $dom);
230
231 if ($returnFirstChild) {
232 return $dom->firstChild;
233 } else {
234 return $dom;
235 }
236 }
237
238 /**
239 * Ask the layoutHandler to get the layout for this object
240 *
241 * @param string $type Layout type
242 * @return string HTML string of the layout to use for this element
243 */
244 public function getLayout($type) {
245 /** @var $layoutHandler tx_form_System_Layout */
246 $layoutHandler = t3lib_div::makeInstance('tx_form_System_Layout');
247
248 switch($type) {
249 case 'element':
250 $layoutDefault = $this->layout;
251 $objectClass = get_class($this);
252 $type = tx_form_Common::getInstance()->getLastPartOfClassName($this, TRUE);
253
254 if (strstr($objectClass, '_Additional_')) {
255 $additionalModel = $this->model->getAdditionalObjectByKey($type);
256 $layoutOverride = $additionalModel->getLayout();
257 } else {
258 $layoutOverride = $this->model->getLayout();
259 }
260
261 $layout = $layoutHandler->getLayoutByObject($type, $layoutDefault, $layoutOverride);
262 break;
263 case 'elementWrap':
264 $layoutDefault = $this->elementWrap;
265 $elementWrap = $layoutHandler->getLayoutByObject($type, $layoutDefault, $layoutOverride);
266
267 $layout = str_replace('<element />', $this->getLayout('element'), $elementWrap);
268 break;
269 case 'containerWrap':
270 $layoutDefault = $this->containerWrap;
271 $layout = $layoutHandler->getLayoutByObject($type, $layoutDefault, $layoutOverride);
272 break;
273 }
274
275 return $layout;
276 }
277
278 /**
279 * Replace the current node with a document fragment
280 *
281 * @param DOMDocument $dom
282 * @param DOMNode $node Current Node
283 * @param DOMNode $value Value to import
284 * @return void
285 */
286 public function replaceNodeWithFragment(DOMDocument $dom, DOMNode $node, DOMNode $value) {
287 $replaceNode = $dom->createDocumentFragment();
288 $domNode = $dom->importNode($value, TRUE);
289 $replaceNode->appendChild($domNode);
290 $node->parentNode->insertBefore($replaceNode, $node);
291 }
292
293 /**
294 * Set the attributes on the html tags according to the attributes that are
295 * assigned in the model for a certain element
296 *
297 * @param DOMElement $domElement DOM element of the specific HTML tag
298 * @return void
299 */
300 public function setAttributes(DOMElement $domElement) {
301 $attributes = $this->model->getAttributes();
302 foreach ($attributes as $key => $attribute) {
303 if (!empty($attribute)) {
304 $value = htmlspecialchars($attribute->getValue(), ENT_QUOTES);
305 if (!empty($value)) {
306 $domElement->setAttribute($key, $value);
307 }
308 }
309 }
310 }
311
312 /**
313 * Set a single attribute of a HTML tag specified by key
314 *
315 * @param DOMElement $domElement DOM element of the specific HTML tag
316 * @param string $key Attribute key
317 * @return void
318 */
319 public function setAttribute(DOMElement $domElement, $key) {
320 $value = htmlspecialchars($this->model->getAttributeValue((string) $key), ENT_QUOTES);
321
322 if (!empty($value)) {
323 $domElement->setAttribute($key, $value);
324 }
325 }
326
327 /**
328 * Sets the value of an attribute with the value of another attribute,
329 * for instance equalizing the name and id attributes for the form tag
330 *
331 * @param DOMElement $domElement DOM element of the specific HTML tag
332 * @param string $key Key of the attribute which needs to be changed
333 * @param string $other Key of the attribute to take the value from
334 * @return unknown_type
335 */
336 public function setAttributeWithValueofOtherAttribute(DOMElement $domElement, $key, $other) {
337 $value = htmlspecialchars($this->model->getAttributeValue((string) $other), ENT_QUOTES);
338
339 if (!empty($value)) {
340 $domElement->setAttribute($key, $value);
341 }
342 }
343
344 /**
345 * Load and instantiate an additional object
346 *
347 * @param string $class Type of additional
348 * @return object
349 */
350 protected function createAdditional($class) {
351 $class = strtolower((string) $class);
352 $className = 'tx_form_View_Form_Additional_' . ucfirst($class);
353
354 return t3lib_div::makeInstance($className, $this->model);
355 }
356
357 /**
358 * Create additional object by key and render the content
359 *
360 * @param string $key Type of additional
361 * @return DOMNode
362 */
363 public function getAdditional($key) {
364 $additional = $this->createAdditional($key);
365 return $additional->render();
366 }
367
368 /**
369 * Get the content of tags
370 * like <option>content</option>
371 * or <textarea>content</textarea>
372 *
373 * @param boolean $encodeSpecialCharacters Whether to encode the data
374 * @return string
375 */
376 public function getElementData($encodeSpecialCharacters = TRUE) {
377 $elementData = $this->model->getData();
378
379 if ($encodeSpecialCharacters) {
380 $elementData = htmlspecialchars($elementData, ENT_QUOTES);
381 }
382
383 return $elementData;
384 }
385
386 /**
387 * Return the id for the element wraps,
388 * like <li class="csc-form-2 tx_form-hidden"> ... </li>
389 *
390 * @return string
391 */
392 public function getElementWrapId() {
393 $elementId = (integer) $this->model->getElementId();
394 $fieldType = $this->model->getAttributes();
395 $wrapId = 'csc-form-' . $elementId;
396 if (gettype($fieldType['type']) === 'object') {
397 $wrapId .= ' tx_form-' . htmlspecialchars($fieldType['type']->getValue());
398 }
399 return $wrapId;
400 }
401
402 /**
403 * Read the noWrap value of an element
404 * if TRUE the element does not need a element wrap
405 * like <li>element</li>
406 *
407 * @return boolean
408 */
409 public function noWrap() {
410 return $this->noWrap;
411 }
412 }
413 ?>