[BUGFIX] Prevent XSS in ViewHelpers
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / ViewHelpers / CObjectViewHelper.php
1 <?php
2 namespace TYPO3\CMS\Fluid\ViewHelpers;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18
19 /**
20 * This ViewHelper renders CObjects from the global TypoScript configuration.
21 * NOTE: You have to ensure proper escaping (htmlspecialchars/intval/etc.) on your own!
22 *
23 * = Examples =
24 *
25 * <code title="Render lib object">
26 * <f:cObject typoscriptObjectPath="lib.someLibObject" />
27 * </code>
28 * <output>
29 * rendered lib.someLibObject
30 * </output>
31 *
32 * <code title="Specify cObject data & current value">
33 * <f:cObject typoscriptObjectPath="lib.customHeader" data="{article}" currentValueKey="title" />
34 * </code>
35 * <output>
36 * rendered lib.customHeader. data and current value will be available in TypoScript
37 * </output>
38 *
39 * <code title="inline notation">
40 * {article -> f:cObject(typoscriptObjectPath: 'lib.customHeader')}
41 * </code>
42 * <output>
43 * rendered lib.customHeader. data will be available in TypoScript
44 * </output>
45 */
46 class CObjectViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper
47 {
48 /**
49 * Disable escaping of child nodes' output
50 *
51 * @var bool
52 */
53 protected $escapeChildren = false;
54
55 /**
56 * Disable escaping of this node's output
57 *
58 * @var bool
59 */
60 protected $escapeOutput = false;
61
62 /**
63 * @var array
64 */
65 protected $typoScriptSetup;
66
67 /**
68 * @var \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController contains a backup of the current $GLOBALS['TSFE'] if used in BE mode
69 */
70 protected $tsfeBackup;
71
72 /**
73 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
74 */
75 protected $configurationManager;
76
77 /**
78 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
79 * @return void
80 */
81 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
82 {
83 $this->configurationManager = $configurationManager;
84 $this->typoScriptSetup = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
85 }
86
87 /**
88 * Renders the TypoScript object in the given TypoScript setup path.
89 *
90 * @param string $typoscriptObjectPath the TypoScript setup path of the TypoScript object to render
91 * @param mixed $data the data to be used for rendering the cObject. Can be an object, array or string. If this argument is not set, child nodes will be used
92 * @param string $currentValueKey
93 * @param string $table
94 * @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
95 * @return string the content of the rendered TypoScript object
96 */
97 public function render($typoscriptObjectPath, $data = null, $currentValueKey = null, $table = '')
98 {
99 if (TYPO3_MODE === 'BE') {
100 $this->simulateFrontendEnvironment();
101 }
102 if ($data === null) {
103 $data = $this->renderChildren();
104 }
105 $currentValue = null;
106 if (is_object($data)) {
107 $data = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getGettableProperties($data);
108 } elseif (is_string($data) || is_numeric($data)) {
109 $currentValue = (string)$data;
110 $data = array($data);
111 }
112 /** @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObject */
113 $contentObject = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class);
114 $contentObject->start($data, $table);
115 if ($currentValue !== null) {
116 $contentObject->setCurrentVal($currentValue);
117 } elseif ($currentValueKey !== null && isset($data[$currentValueKey])) {
118 $contentObject->setCurrentVal($data[$currentValueKey]);
119 }
120 $pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath);
121 $lastSegment = array_pop($pathSegments);
122 $setup = $this->typoScriptSetup;
123 foreach ($pathSegments as $segment) {
124 if (!array_key_exists(($segment . '.'), $setup)) {
125 throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception('TypoScript object path "' . htmlspecialchars($typoscriptObjectPath) . '" does not exist', 1253191023);
126 }
127 $setup = $setup[$segment . '.'];
128 }
129 $content = $contentObject->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.']);
130 if (TYPO3_MODE === 'BE') {
131 $this->resetFrontendEnvironment();
132 }
133 return $content;
134 }
135
136 /**
137 * Sets the $TSFE->cObjectDepthCounter in Backend mode
138 * This somewhat hacky work around is currently needed because the cObjGetSingle() function of \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer relies on this setting
139 *
140 * @return void
141 */
142 protected function simulateFrontendEnvironment()
143 {
144 $this->tsfeBackup = isset($GLOBALS['TSFE']) ? $GLOBALS['TSFE'] : null;
145 $GLOBALS['TSFE'] = new \stdClass();
146 $GLOBALS['TSFE']->cObjectDepthCounter = 100;
147 }
148
149 /**
150 * Resets $GLOBALS['TSFE'] if it was previously changed by simulateFrontendEnvironment()
151 *
152 * @return void
153 * @see simulateFrontendEnvironment()
154 */
155 protected function resetFrontendEnvironment()
156 {
157 $GLOBALS['TSFE'] = $this->tsfeBackup;
158 }
159 }