EXTMVC:
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / View / TX_EXTMVC_View_TemplateView.php
1 <?php
2 declare(ENCODING = 'utf-8');
3
4 /* *
5 * This script belongs to the FLOW3 framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License as published by the *
9 * Free Software Foundation, either version 3 of the License, or (at your *
10 * option) any later version. *
11 * *
12 * This script is distributed in the hope that it will be useful, but *
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
14 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
15 * General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU Lesser General Public *
18 * License along with the script. *
19 * If not, see http://www.gnu.org/licenses/lgpl.html *
20 * *
21 * The TYPO3 project - inspiring people to share! *
22 * */
23
24 require_once(PATH_t3lib . 'class.t3lib_parsehtml.php');
25 require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/Utility/TX_EXTMVC_Utility_Strings.php');
26 require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/View/Helper/TX_EXTMVC_View_Helper_ForHelper.php');
27
28 /**
29 * A basic Template View
30 *
31 * @version $Id:$
32 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
33 * @scope prototype
34 */
35 class TX_EXTMVC_View_TemplateView extends TX_EXTMVC_View_AbstractView {
36
37 /**
38 * Pattern for fetching information from controller object name
39 * @var string
40 */
41 const PATTERN_CONTROLLER = '/^TX_\w*_Controller_(?P<ControllerName>\w*)Controller$/sm';
42
43 const SCAN_PATTERN_SUBPARTS = '/<!--\s*###(?P<SubpartName>[^#]*)###.*?-->(?P<SubpartTemplateSource>.*?)<!--\s*###(?P=SubpartName)###.*?-->/sm';
44 const SCAN_PATTERN_MARKER = '/###(?P<MarkerName>.*?)###/sm';
45
46 const SPLIT_PATTERN_MARKER = '/^(?:(?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*)*)\s*$/';
47 const SPLIT_PATTERN_ARGUMENTS = '/(?P<ArgumentKey>[a-zA-Z][a-zA-Z0-9_]*)=(?:(?:"(?P<ValueDoubleQuoted>[^"]+)")|(?:\'(?P<ValueSingleQuoted>[^\']+)\')|(?:\{(?P<ValueObject>[^\'\s]+)\})|(?:(?P<ValueUnquoted>[^"\'\s]*)))/';
48
49 /**
50 * File pattern for resolving the template file
51 * @var string
52 */
53 protected $templateFilePattern = 'Resources/Template/@controller/@action.xhtml';
54
55 /**
56 * @var array Marker uids and their replacement content
57 */
58 protected $markers = array();
59
60 /**
61 * @var array Subparts
62 */
63 protected $subparts = array();
64
65 /**
66 * @var array Wrapped subparts
67 */
68 protected $wrappedSubparts = array();
69
70 /**
71 * Context variables
72 * @var array of context variables
73 */
74 protected $contextVariables = array();
75
76 /**
77 * Template file path. If set, overrides the templateFilePattern
78 * @var string
79 */
80 protected $templateFile = NULL;
81
82 /**
83 * @var string
84 */
85 protected $templateSource = '';
86
87 /**
88 * Name of current action to render
89 * @var string
90 */
91 protected $actionName;
92
93 /**
94 * The content object
95 *
96 * @var tslib_cObj
97 **/
98 private $cObj;
99
100 public function __construct() {
101 $this->initializeView();
102 }
103
104 /**
105 * Initialize view
106 *
107 * @return void
108 */
109 protected function initializeView() {
110 $this->cObj = t3lib_div::makeInstance('tslib_cObj');
111 }
112
113 /**
114 * Sets the template file. Effectively overrides the dynamic resolving of a template file.
115 *
116 * @param string $templateFile Template file path
117 * @return void
118 */
119 public function setTemplateFile($templateFile) {
120 $this->templateFile = $templateFile;
121 }
122
123 /**
124 * Sets the text source which contains the markers of this template view
125 * is going to fill in.
126 *
127 * @param string $templateSource The template source
128 * @return void
129 */
130 public function setTemplateSource($templateSource) {
131 $this->templateSource = $templateSource;
132 }
133
134 /**
135 * Resolve the template file path, based on $this->templateFilePath and $this->templatePathPattern.
136 * In case a template has been set with $this->setTemplateFile, it just uses the given template file.
137 * Otherwise, it resolves the $this->templatePathPattern
138 *
139 * @param string $action Name of action. Optional. Defaults to current action.
140 * @return string File name of template file
141 */
142 protected function resolveTemplateFile() {
143 if ($this->templateFile) {
144 return $this->templateFile;
145 } else {
146 $action = ($this->actionName ? $this->actionName : $this->request->getControllerActionName());
147 preg_match(self::PATTERN_CONTROLLER, $this->request->getControllerObjectName(), $matches);
148 $controllerName = $matches['ControllerName'];
149 $templateFile = $this->templateFilePattern;
150 $templateFile = str_replace('@controller', $controllerName, $templateFile);
151 $templateFile = str_replace('@action', strtolower($action), $templateFile);
152 return $templateFile;
153 }
154 }
155
156 /**
157 * Load the given template file.
158 *
159 * @param string $templateFilePath Full path to template file to load
160 * @return string the contents of the template file
161 */
162 protected function loadTemplateFile($templateFilePath) {
163 $templateSource = file_get_contents(t3lib_extMgm::extPath(strtolower($this->request->getControllerExtensionKey())) . $templateFilePath, FILE_TEXT);
164 if (!$templateSource) throw new RuntimeException('The template file "' . $templateFilePath . '" was not found.', 1225709595); // TODO Specific exception
165 return $templateSource;
166 }
167
168 /**
169 * Find the XHTML template according to $this->templatePathPattern and render the template.
170 *
171 * @return string Rendered Template
172 */
173 public function render() {
174 if ($this->templateSource == '') {
175 $this->actionName = $action;
176 $templateFileName = $this->resolveTemplateFile();
177 $templateSource = $this->loadTemplateFile($templateFileName);
178 } else {
179 $templateSource = $this->templateSource;
180 }
181 // TODO exception if a template was not defined
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 $subpartArray = array();
195 $subparts = $this->getSubparts($templateSource);
196 foreach ($subparts as $subpartMarker => $subpartSource) {
197 $subpartArray['###' . $subpartMarker . '###'] = $this->getMarkerContent($subpartMarker, $variables, $subpartSource);
198 }
199 // $content = $this->cObj->substituteMarkerArrayCached($templateSource, $markerArray, $subpartArray, $wrappedSubpartArray);
200 $markerArray = array();
201 $markers = $this->getMarkers($templateSource);
202 foreach ($markers as $marker => $foo) {
203 $markerArray['###' . $marker . '###'] = $this->getMarkerContent($marker, $variables);
204 }
205 $content = $this->cObj->substituteMarkerArrayCached($templateSource, $markerArray, $subpartArray, $wrappedSubpartArray);
206
207 return $content;
208 }
209
210 public function getMarkerArray($templateSource, $value = NULL) {
211 $markers = $this->getMarkers($templateSource);
212 $markerArray = array();
213 foreach ($markers as $marker => $foo) {
214 $markerArray['###' . $marker . '###'] = $this->getMarkerContent($marker, $value);
215 }
216 return $markerArray;
217 }
218
219 protected function getMarkerContent($marker, $variables = NULL, $templateSource = NULL) {
220 preg_match(self::SPLIT_PATTERN_MARKER, $marker, $explodedMarker);
221 $viewHelperName = TX_EXTMVC_Utility_Strings::underscoredToUpperCamelCase($explodedMarker['ViewHelperName']);
222 $contextVariable = TX_EXTMVC_Utility_Strings::underscoredToUpperCamelCase($explodedMarker['ContextVariable']);
223 $explodedObjectAndProperty = explode('.', $explodedMarker['ObjectAndProperty']);
224 $objectName = TX_EXTMVC_Utility_Strings::underscoredToUpperCamelCase($explodedObjectAndProperty[0]);
225 $property = TX_EXTMVC_Utility_Strings::underscoredToLowerCamelCase($explodedObjectAndProperty[1]);
226 if (!empty($explodedMarker['Attributes'])) {
227 $arguments = $this->getArguments($explodedMarker['Attributes'], $variables);
228 }
229 if ($variables[$objectName] instanceof TX_EXTMVC_DomainObject_AbstractDomainObject) {
230 $object = $variables[$objectName];
231 $possibleMethodName = 'get' . $property;
232 if (method_exists($object, $possibleMethodName)) {
233 $content = $object->$possibleMethodName();
234 }
235 }
236
237 if (!empty($viewHelperName)) {
238 $viewHelperClassName = 'TX_EXTMVC_View_Helper_' . $viewHelperName . 'Helper';
239 $viewHelper = $this->getViewHelper($viewHelperClassName);
240 $content = $viewHelper->render($this, $content, $arguments, $templateSource, $variables);
241 }
242 return $content;
243 }
244
245 protected function getArguments($attributes, $variables) {
246 preg_match_all(self::SPLIT_PATTERN_ARGUMENTS, $attributes, $explodedAttributes, PREG_SET_ORDER);
247 $arguments = array();
248 foreach ($explodedAttributes as $explodedAttribute) {
249 if (!empty($explodedAttribute['ValueDoubleQuoted'])) {
250 $argumentValue = $explodedAttribute['ValueDoubleQuoted'];
251 } elseif (!empty($explodedAttribute['ValueSingleQuoted'])) {
252 $argumentValue = $explodedAttribute['ValueSingleQuoted'];
253 } elseif (!empty($explodedAttribute['ValueObject'])) {
254 $argumentValue = $this->getValueForVariableAndKey($explodedAttribute['ValueObject'], $variables);
255 } elseif (!empty($explodedAttribute['ValueUnquoted'])) {
256 $argumentValue = $this->getValueForVariableAndKey($explodedAttribute['ValueUnquoted'], $variables);
257 } else {
258 $argumentValue = NULL;
259 }
260 $arguments[TX_EXTMVC_Utility_Strings::underscoredToLowerCamelCase($explodedAttribute['ArgumentKey'])] = $argumentValue;
261 }
262 return $arguments;
263 }
264
265 public function replaceReferencesWithValues(&$theString, $variables) {
266 preg_match_all('/(?:\{([^\s]*?)\})?/', $theString, $matches, PREG_SET_ORDER);
267 foreach ($matches as $match) {
268 if (count($match) > 1) {
269 $reference = $match[0];
270 $value = $this->getValueForVariableAndKey($match[1], $variables);
271 }
272 $theString = str_replace($reference, $value, $theString);
273 }
274 }
275
276 public function getValueForVariableAndKey($variableAndKey, $variables) {
277 $explodedVariableAndKey = explode('.', $variableAndKey);
278 $variable = $variables[TX_EXTMVC_Utility_Strings::underscoredToUpperCamelCase($explodedVariableAndKey[0])];
279 if (!empty($variable)) {
280 if (count($explodedVariableAndKey) > 1) {
281 $key = $explodedVariableAndKey[1];
282 if (is_object($variable)) {
283 $possibleMethodName = 'get' . TX_EXTMVC_Utility_Strings::underscoredToUpperCamelCase($key);
284 if (method_exists($variable, $possibleMethodName)) {
285 $value = $variable->$possibleMethodName();
286 }
287 } elseif (is_array($variable)) {
288 $value = $variable[TX_EXTMVC_Utility_Strings::underscoredToLowerCamelCase($key)];
289 }
290 } else {
291 if (is_object($variable)) {
292 $value = $variable->__toString();
293 } else {
294 $value = $variable;
295 }
296 }
297 }
298 return $value;
299 }
300
301 protected function getSubpartArray($templateSource) {
302 $subpartArray = array();
303 if (count($subparts) > 0) {
304 foreach ($subparts as $subpartMarker => $subpartTemplateSource) {
305 $value = $this->getMarkerContent($subpartMarker);
306 $subpartArray['###' . $subpartMarker . '###'] .= $this->renderTemplate($subpartTemplateSource, $value);
307 }
308 }
309 return $subpartArray;
310 }
311
312 protected function getSubparts($templateSource) {
313 preg_match_all(self::SCAN_PATTERN_SUBPARTS, $templateSource, $matches, PREG_SET_ORDER);
314 $subparts = array();
315 if (is_array($matches)) {
316 foreach ($matches as $key => $match) {
317 $subparts[$match['SubpartName']] = $match['SubpartTemplateSource'];
318 }
319 }
320 return $subparts;
321 }
322
323 protected function getMarkers($templateSource) {
324 preg_match_all(self::SCAN_PATTERN_MARKER, $templateSource, $matches, PREG_SET_ORDER);
325 $markers = array();
326 if (is_array($matches)) {
327 foreach ($matches as $key => $match) {
328 $markers[$match['MarkerName']] = NULL;
329 }
330 }
331 return $markers;
332 }
333
334 protected function removeUnfilledMarkers(&$content) {
335 // TODO remove also comments
336 $content = preg_replace('/###.*###|<!--[^>]*###.*###[^<]*-->(.*)/msU', '', $content);
337 }
338
339 /**
340 * Assigns domain models (single objects or aggregates) or values to the view
341 *
342 * @param string $valueName The name of the value
343 * @param mixed $value the value to assign
344 * @return void
345 */
346 public function assign($key, $value) {
347 $this->contextVariables[$key] = $value;
348 return $this;
349 }
350 }
351 ?>