ExtBase:
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / View / TemplateView.php
1 <?php
2 declare(ENCODING = 'utf-8');
3 /***************************************************************
4 * Copyright notice
5 *
6 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
7 * All rights reserved
8 *
9 * This script is part of the TYPO3 project. The TYPO3 project is
10 * free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * The GNU General Public License can be found at
16 * http://www.gnu.org/copyleft/gpl.html.
17 *
18 * This script is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * This copyright notice MUST APPEAR in all copies of the script!
24 ***************************************************************/
25
26 require_once(PATH_tslib . 'class.tslib_content.php');
27 require_once(PATH_t3lib . 'class.t3lib_parsehtml.php');
28
29 /**
30 * A basic Template View
31 *
32 * @package TYPO3
33 * @subpackage extbase
34 * @version $ID:$
35 * @scope prototype
36 */
37 class Tx_ExtBase_View_TemplateView extends Tx_ExtBase_View_AbstractView {
38
39 /**
40 * Pattern for fetching information from controller object name
41 * @var string
42 */
43 const PATTERN_CONTROLLER = '/^Tx_\w*_Controller_(?P<ControllerName>\w*)Controller$/sm';
44
45 const SCAN_PATTERN_SUBPARTS = '/<!--\s*###(?P<SubpartName>[^#]*)###.*?-->(?P<SubpartTemplateSource>.*?)<!--\s*###(?P=SubpartName)###.*?-->/sm';
46 const SCAN_PATTERN_MARKER = '/###(?P<MarkerName>.*?)###/sm';
47
48 const SPLIT_PATTERN_STATEMENT = '/(?:\s*(?P<ViewHelperName>[a-zA-Z0-9_]+):)?(?P<ContextVariable>(?:\s*[a-zA-Z0-9_]+)(?=(\s|$)))?(?P<ObjectAndProperty>(?:\s*[a-zA-Z0-9_]+\.(?:[a-zA-Z0-9_]+)(?=(\s|$))))?(?P<Attributes>(?:\s*[a-zA-Z0-9_]+=(?:"(?:[^"])*"|\'(?:[^\'])*\'|\{(?:[^\{])*\}|[a-zA-Z0-9_\.]+)\s*)*)/';
49 const SPLIT_PATTERN_ARGUMENTS = '/(?P<ArgumentKey>[a-zA-Z][a-zA-Z0-9_]*)=(?:(?:"(?P<ValueDoubleQuoted>[^"]+)")|(?:\'(?P<ValueSingleQuoted>[^\']+)\')|(?:\{(?P<ValueObject>[^\'\s]+)\})|(?:(?P<ValueUnquoted>[^"\'\s]*)))/';
50
51 /**
52 * File pattern for resolving the template file
53 * @var string
54 */
55 protected $templateFilePattern = 'Resources/Private/Templates/@controller/@action.html';
56
57 /**
58 * @var array Marker uids and their replacement content
59 */
60 protected $markers = array();
61
62 /**
63 * @var array Subparts
64 */
65 protected $subparts = array();
66
67 /**
68 * @var array Wrapped subparts
69 */
70 protected $wrappedSubparts = array();
71
72 /**
73 * Context variables
74 * @var array of context variables
75 */
76 protected $contextVariables = array();
77
78 /**
79 * Template file path. If set, overrides the templateFilePattern
80 * @var string
81 */
82 protected $templateFile = NULL;
83
84 /**
85 * @var string
86 */
87 protected $templateSource = '';
88
89 /**
90 * Name of current action to render
91 * @var string
92 */
93 protected $actionName;
94
95 /**
96 * The content object
97 *
98 * @var tslib_cObj
99 **/
100 private $cObj;
101
102 public function __construct() {
103 $this->initializeView();
104 }
105
106 /**
107 * Initialize view
108 *
109 * @return void
110 */
111 protected function initializeView() {
112 $this->cObj = t3lib_div::makeInstance('tslib_cObj');
113 }
114
115 /**
116 * Sets the template file. Effectively overrides the dynamic resolving of a template file.
117 *
118 * @param string $templateFile Template file path
119 * @return void
120 */
121 public function setTemplateFile($templateFile) {
122 $this->templateFile = $templateFile;
123 }
124
125 /**
126 * Sets the text source which contains the markers of this template view
127 * is going to fill in.
128 *
129 * @param string $templateSource The template source
130 * @return void
131 */
132 public function setTemplateSource($templateSource) {
133 $this->templateSource = $templateSource;
134 }
135
136 /**
137 * Resolve the template file path, based on $this->templateFilePath and $this->templatePathPattern.
138 * In case a template has been set with $this->setTemplateFile, it just uses the given template file.
139 * Otherwise, it resolves the $this->templatePathPattern
140 *
141 * @param string $action Name of action. Optional. Defaults to current action.
142 * @return string File name of template file
143 */
144 protected function resolveTemplateFile() {
145 if ($this->templateFile) {
146 return $this->templateFile;
147 } else {
148 $action = ($this->actionName ? $this->actionName : $this->request->getControllerActionName());
149 preg_match(self::PATTERN_CONTROLLER, $this->request->getControllerObjectName(), $matches);
150 $controllerName = $matches['ControllerName'];
151 $templateFile = $this->templateFilePattern;
152 $templateFile = str_replace('@controller', $controllerName, $templateFile);
153 $templateFile = str_replace('@action', strtolower($action), $templateFile);
154 return $templateFile;
155 }
156 }
157
158 /**
159 * Load the given template file.
160 *
161 * @param string $templateFilePath Full path to template file to load
162 * @return string the contents of the template file
163 */
164 protected function loadTemplateFile($templateFilePath) {
165 $templateSource = file_get_contents(t3lib_extMgm::extPath(strtolower($this->request->getExtensionName())) . $templateFilePath, FILE_TEXT);
166 if (!$templateSource) throw new RuntimeException('The template file "' . $templateFilePath . '" was not found.', 1225709595);
167 return $templateSource;
168 }
169
170 /**
171 * Find the XHTML template according to $this->templatePathPattern and render the template.
172 *
173 * @return string Rendered Template
174 */
175 public function render() {
176 if ($this->templateSource == '') {
177 $templateFileName = $this->resolveTemplateFile();
178 $templateSource = $this->loadTemplateFile($templateFileName);
179 } else {
180 $templateSource = $this->templateSource;
181 }
182 $content = $this->renderTemplate($templateSource, $this->contextVariables);
183 $this->removeUnfilledMarkers($content);
184 return $content;
185 }
186
187 /**
188 * Recursive rendering of a given template source.
189 *
190 * @param string $templateSource The template source
191 * @return void
192 */
193 public function renderTemplate($templateSource, $variables) {
194 $content = '';
195 $subpartArray = array();
196 $subparts = $this->getSubparts($templateSource);
197 foreach ($subparts as $subpartMarker => $subpartSource) {
198 $subpartArray['###' . $subpartMarker . '###'] = $this->getMarkerContent($subpartMarker, $variables, $subpartSource);
199 }
200
201 $markerArray = array();
202 $markers = $this->getMarkers($templateSource);
203 foreach ($markers as $marker => $foo) {
204 $markerArray['###' . $marker . '###'] = $this->getMarkerContent($marker, $variables);
205 }
206
207 $content = $this->cObj->substituteMarkerArrayCached($templateSource, $markerArray, $subpartArray);
208
209 return $content;
210 }
211
212 /**
213 * Returns the markers and their values
214 *
215 * @param string $templateSource The template source code with markers
216 * @param string $value The value (only useful if the method is called recursively)
217 * @return array The array with marker-value pairs
218 */
219 public function getMarkerArray($templateSource, $value = NULL) {
220 $markers = $this->getMarkers($templateSource);
221 $markerArray = array();
222 foreach ($markers as $marker => $foo) {
223 $markerArray['###' . $marker . '###'] = $this->getMarkerContent($marker, $value);
224 }
225 return $markerArray;
226 }
227
228 /**
229 * Returns the value of a marker
230 *
231 * @param string $marker The marker as string (without ###|###)
232 * @param array $variables The variables of the context (these may be assigned by the controller or determined inside this view)
233 * @param string $templateSource The template source code
234 * @return string The value
235 */
236 public function getMarkerContent($marker, $variables = NULL, $templateSource = NULL) {
237 $explodedMarker = t3lib_div::trimExplode('|', $marker);
238 foreach ($explodedMarker as $key => $statement) {
239 preg_match(self::SPLIT_PATTERN_STATEMENT, $statement, $explodedStatement);
240 $viewHelperName = Tx_ExtBase_Utility_Strings::underscoredToUpperCamelCase($explodedStatement['ViewHelperName']);
241 $contextVariableName = Tx_ExtBase_Utility_Strings::underscoredToUpperCamelCase($explodedStatement['ContextVariable']);
242 $explodedObjectAndProperty = explode('.', $explodedStatement['ObjectAndProperty']);
243 $objectName = Tx_ExtBase_Utility_Strings::underscoredToUpperCamelCase($explodedObjectAndProperty[0]);
244 $property = Tx_ExtBase_Utility_Strings::underscoredToLowerCamelCase($explodedObjectAndProperty[1]);
245 if (!empty($explodedStatement['Attributes'])) {
246 $arguments = $this->getArguments($explodedStatement['Attributes'], $variables);
247 }
248
249 if ($variables[$objectName] instanceof Tx_ExtBase_DomainObject_AbstractDomainObject) {
250 $object = $variables[$objectName];
251 $possibleMethodName = 'get' . ucfirst($property);
252 if (method_exists($object, $possibleMethodName)) {
253 $content = $object->$possibleMethodName(); // Properties should be already secure (XSS)
254 }
255 } elseif (!empty($variables[$contextVariableName])) {
256 $content = filter_var($variables[$contextVariableName], FILTER_SANITIZE_STRING);
257 }
258
259 if (!empty($viewHelperName)) {
260 $viewHelperClassName = 'Tx_ExtBase_View_Helper_' . $viewHelperName . 'Helper';
261 $viewHelper = $this->getViewHelper($viewHelperClassName);
262 $content = $viewHelper->render($this, $arguments, $templateSource, $variables);
263 }
264 }
265 return $content;
266 }
267
268 /**
269 * Parses the attributes string and returns the arguments
270 *
271 * @param string $attributes The attributes string
272 * @param array $variables The variables of the context (these may be assigned by the controller or determined inside this view)
273 * @return array The arguments as key => value
274 */
275 protected function getArguments($attributes, $variables) {
276 preg_match_all(self::SPLIT_PATTERN_ARGUMENTS, $attributes, $explodedAttributes, PREG_SET_ORDER);
277 $arguments = array();
278 foreach ($explodedAttributes as $explodedAttribute) {
279 if (!empty($explodedAttribute['ValueDoubleQuoted'])) {
280 $argumentValue = $explodedAttribute['ValueDoubleQuoted'];
281 } elseif (!empty($explodedAttribute['ValueSingleQuoted'])) {
282 $argumentValue = $explodedAttribute['ValueSingleQuoted'];
283 } elseif (!empty($explodedAttribute['ValueObject'])) {
284 $argumentValue = $this->getValueForVariableAndKey($explodedAttribute['ValueObject'], $variables);
285 } elseif (!empty($explodedAttribute['ValueUnquoted'])) {
286 $argumentValue = $this->getValueForVariableAndKey($explodedAttribute['ValueUnquoted'], $variables);
287 } else {
288 $argumentValue = NULL;
289 }
290 $arguments[Tx_ExtBase_Utility_Strings::underscoredToLowerCamelCase($explodedAttribute['ArgumentKey'])] = $argumentValue;
291 }
292 return $arguments;
293 }
294
295 /**
296 * Replaces references (e.g. "{OBJECT.PROPERTY}") with the corresponding values
297 *
298 * @param string $theString The string perhaps containing references
299 * @param array $variables The variables of the context (these may be assigned by the controller or determined inside this view)
300 * @return void
301 */
302 public function replaceReferencesWithValues(&$theString, $variables) {
303 preg_match_all('/(?:\{([^\s]*?)\})?/', $theString, $matches, PREG_SET_ORDER);
304 foreach ($matches as $match) {
305 if (count($match) > 1) {
306 $reference = $match[0];
307 $value = $this->getValueForVariableAndKey($match[1], $variables);
308 }
309 $theString = str_replace($reference, $value, $theString);
310 }
311 }
312
313 /**
314 * Returns the value for a given string with a (context) variable and a key (e.g. "PERSON.NAME" invokes $variables['person']->getName())
315 *
316 * @param string $variableAndKey The variable (mainly an object) and key (mainly a property of an object) linked with a dot (e.g. "PERSON.NAME")
317 * @param array $variables The variables of the context (these may be assigned by the controller or determined inside this view)
318 * @return mixed The value
319 */
320 public function getValueForVariableAndKey($variableAndKey, $variables) {
321 $explodedVariableAndKey = explode('.', $variableAndKey);
322 $variable = $variables[Tx_ExtBase_Utility_Strings::underscoredToUpperCamelCase($explodedVariableAndKey[0])];
323 if (!empty($variable)) {
324 if (count($explodedVariableAndKey) > 1) {
325 $key = $explodedVariableAndKey[1];
326 if (is_object($variable)) {
327 $possibleMethodName = 'get' . Tx_ExtBase_Utility_Strings::underscoredToUpperCamelCase($key);
328 if (method_exists($variable, $possibleMethodName)) {
329 $value = $variable->$possibleMethodName();
330 }
331 } elseif (is_array($variable)) {
332 $value = $variable[Tx_ExtBase_Utility_Strings::underscoredToLowerCamelCase($key)];
333 }
334 } else {
335 if (is_object($variable)) {
336 $value = $variable->__toString();
337 } else {
338 $value = $variable;
339 }
340 }
341 }
342 return $value;
343 }
344
345 /**
346 * Returns the subparts and their values
347 *
348 * @param string $templateSource The template source
349 * @return array The subparts
350 */
351 protected function getSubpartArray($templateSource) {
352 $subpartArray = array();
353 if (count($subparts) > 0) {
354 foreach ($subparts as $subpartMarker => $subpartTemplateSource) {
355 $value = $this->getMarkerContent($subpartMarker);
356 $subpartArray['###' . $subpartMarker . '###'] .= $this->renderTemplate($subpartTemplateSource, $value);
357 }
358 }
359 return $subpartArray;
360 }
361
362 /**
363 * Fetches all subparts from the template source
364 *
365 * @param string $templateSource The template source
366 * @return array An array of subpartName => subpartTemplateSource pairs
367 */
368 protected function getSubparts($templateSource) {
369 preg_match_all(self::SCAN_PATTERN_SUBPARTS, $templateSource, $matches, PREG_SET_ORDER);
370 $subparts = array();
371 if (is_array($matches)) {
372 foreach ($matches as $key => $match) {
373 $subparts[$match['SubpartName']] = $match['SubpartTemplateSource'];
374 }
375 }
376 return $subparts;
377 }
378
379 /**
380 * Fetches all markers from the template source
381 *
382 * @param string $templateSource The template source
383 * @return array An array of markerNames as keys
384 */
385 protected function getMarkers($templateSource) {
386 preg_match_all(self::SCAN_PATTERN_MARKER, $templateSource, $matches, PREG_SET_ORDER);
387 $markers = array();
388 if (is_array($matches)) {
389 foreach ($matches as $key => $match) {
390 $markers[$match['MarkerName']] = NULL;
391 }
392 }
393 return $markers;
394 }
395
396 /**
397 * Removes unfilled markers from the rendered content
398 *
399 * @param string $content The content
400 * @return string The cleaned content
401 */
402 protected function removeUnfilledMarkers(&$content) {
403 $content = preg_replace('/###.*###|<!--[^>]*###.*###[^<]*-->(.*)/msU', '', $content);
404 }
405
406 /**
407 * Assigns domain models (single objects or aggregates) or values to the view
408 *
409 * @param string $valueName The name of the value
410 * @param mixed $value the value to assign
411 * @return void
412 */
413 public function assign($key, $value) {
414 $this->contextVariables[$key] = $value;
415 return $this;
416 }
417 }
418 ?>