497b45b4fb0c2dbeca9cee0f49883fdaffe3ed7b
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / ContextMenu / Pagetree / ContextMenuDataProvider.php
1 <?php
2 namespace TYPO3\CMS\Backend\ContextMenu\Pagetree;
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\Backend\ContextMenu\ContextMenuAction;
18 use TYPO3\CMS\Backend\ContextMenu\ContextMenuActionCollection;
19 use TYPO3\CMS\Backend\Tree\TreeNode;
20 use TYPO3\CMS\Backend\Utility\BackendUtility;
21 use TYPO3\CMS\Core\Imaging\Icon;
22 use TYPO3\CMS\Core\Imaging\IconFactory;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24
25 /**
26 * Context Menu Data Provider for the Page Tree
27 */
28 class ContextMenuDataProvider
29 {
30 /**
31 * List of actions that are generally disabled
32 *
33 * @var array
34 */
35 protected $disableItems = [];
36
37 /**
38 * Context Menu Type (e.g. table.pages, table.tt_content)
39 *
40 * @var string
41 */
42 protected $contextMenuType = '';
43
44 /**
45 * Old Context Menu Options (access mapping)
46 *
47 * Note: Only option with different namings are mapped!
48 *
49 * @var array
50 */
51 protected $legacyContextMenuMapping = [
52 'hide' => 'disable',
53 'paste' => 'pasteInto,pasteAfter',
54 'mount_as_treeroot' => 'mountAsTreeroot'
55 ];
56
57 /**
58 * Returns the context menu type
59 *
60 * @return string
61 */
62 public function getContextMenuType()
63 {
64 return $this->contextMenuType;
65 }
66
67 /**
68 * Sets the context menu type
69 *
70 * @param string $contextMenuType
71 * @return void
72 */
73 public function setContextMenuType($contextMenuType)
74 {
75 $this->contextMenuType = $contextMenuType;
76 }
77
78 /**
79 * Returns the configuration of the specified context menu type
80 *
81 * @return array
82 */
83 protected function getConfiguration()
84 {
85 $contextMenuActions = $this->getBackendUser()
86 ->getTSConfig('options.contextMenu.' . $this->contextMenuType . '.items');
87 return $contextMenuActions['properties'];
88 }
89
90 /**
91 * Evaluates a given display condition and returns TRUE if the condition matches
92 *
93 * Examples:
94 * getContextInfo|inCutMode:1 || isInCopyMode:1
95 * isLeafNode:1
96 * isLeafNode:1 && isInCutMode:1
97 *
98 * @param TreeNode $node
99 * @param string $displayCondition
100 * @return bool
101 */
102 protected function evaluateDisplayCondition(TreeNode $node, $displayCondition)
103 {
104 if ($displayCondition === '') {
105 return true;
106 }
107 // Parse condition string
108 $conditions = [];
109 preg_match_all('/(.+?)(>=|<=|!=|=|>|<)(.+?)(\\|\\||&&|$)/is', $displayCondition, $conditions);
110 $lastResult = false;
111 $chainType = '';
112 $amountOfConditions = count($conditions[0]);
113 for ($i = 0; $i < $amountOfConditions; ++$i) {
114 // Check method for existence
115 $method = trim($conditions[1][$i]);
116 list($method, $index) = explode('|', $method);
117 if (!method_exists($node, $method)) {
118 continue;
119 }
120 // Fetch compare value
121 $returnValue = call_user_func([$node, $method]);
122 if (is_array($returnValue)) {
123 $returnValue = $returnValue[$index];
124 }
125 // Compare fetched and expected values
126 $operator = trim($conditions[2][$i]);
127 $expected = trim($conditions[3][$i]);
128 if ($operator === '=') {
129 $returnValue = $returnValue == $expected;
130 } elseif ($operator === '>') {
131 $returnValue = $returnValue > $expected;
132 } elseif ($operator === '<') {
133 $returnValue = $returnValue < $expected;
134 } elseif ($operator === '>=') {
135 $returnValue = $returnValue >= $expected;
136 } elseif ($operator === '<=') {
137 $returnValue = $returnValue <= $expected;
138 } elseif ($operator === '!=') {
139 $returnValue = $returnValue != $expected;
140 } else {
141 $returnValue = false;
142 $lastResult = false;
143 }
144 // Chain last result and the current if requested
145 if ($chainType === '||') {
146 $lastResult = $lastResult || $returnValue;
147 } elseif ($chainType === '&&') {
148 $lastResult = $lastResult && $returnValue;
149 } else {
150 $lastResult = $returnValue;
151 }
152 // Save chain type for the next condition
153 $chainType = trim($conditions[4][$i]);
154 }
155 return $lastResult;
156 }
157
158 /**
159 * Returns the next context menu level
160 *
161 * @param array $actions
162 * @param TreeNode $node
163 * @param int $level
164 * @return ContextMenuActionCollection
165 */
166 protected function getNextContextMenuLevel(array $actions, TreeNode $node, $level = 0)
167 {
168 /** @var $actionCollection ContextMenuActionCollection */
169 $actionCollection = GeneralUtility::makeInstance(ContextMenuActionCollection::class);
170 /** @var $iconFactory IconFactory */
171 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
172 if ($level > 5) {
173 return $actionCollection;
174 }
175 $type = '';
176 foreach ($actions as $index => $actionConfiguration) {
177 if (substr($index, -1) !== '.') {
178 $type = $actionConfiguration;
179 if ($type !== 'DIVIDER') {
180 continue;
181 }
182 }
183 if (!in_array($type, ['DIVIDER', 'SUBMENU', 'ITEM'], true)) {
184 continue;
185 }
186 /** @var $action ContextMenuAction */
187 $action = GeneralUtility::makeInstance(ContextMenuAction::class);
188 $action->setId($index);
189 if ($type === 'DIVIDER') {
190 $action->setType('divider');
191 } else {
192 if (in_array($actionConfiguration['name'], $this->disableItems, true)
193 || isset($actionConfiguration['displayCondition'])
194 && trim($actionConfiguration['displayCondition']) !== ''
195 && !$this->evaluateDisplayCondition($node, $actionConfiguration['displayCondition'])
196 ) {
197 unset($action);
198 continue;
199 }
200 $label = htmlspecialchars($this->getLanguageService()->sL($actionConfiguration['label']));
201 if ($type === 'SUBMENU') {
202 $action->setType('submenu');
203 $action->setChildActions($this->getNextContextMenuLevel($actionConfiguration, $node, $level + 1));
204 } else {
205 $action->setType('action');
206 $action->setCallbackAction($actionConfiguration['callbackAction']);
207 if (is_array($actionConfiguration['customAttributes.'])) {
208 if (!empty($actionConfiguration['customAttributes.']['contentUrl'])) {
209 $actionConfiguration['customAttributes.']['contentUrl'] = $this
210 ->replaceModuleTokenInContentUrl(
211 $actionConfiguration['customAttributes.']['contentUrl']
212 );
213 }
214 $action->setCustomAttributes($actionConfiguration['customAttributes.']);
215 }
216 }
217 $action->setLabel($label);
218 if (!isset($actionConfiguration['iconName'])) {
219 $actionConfiguration['iconName'] = 'miscellaneous-placeholder';
220 }
221 $action->setIcon($iconFactory->getIcon($actionConfiguration['iconName'], Icon::SIZE_SMALL)->render());
222 }
223 $actionCollection->offsetSet($level . (int)$index, $action);
224 }
225 $actionCollection->ksort();
226 return $actionCollection;
227 }
228
229 /**
230 * Add the CSRF token to the module URL if a "M" parameter is found
231 *
232 * @param string $contentUrl
233 * @return string
234 */
235 protected function replaceModuleTokenInContentUrl($contentUrl)
236 {
237 $parsedUrl = parse_url($contentUrl);
238 parse_str($parsedUrl['query'], $urlParameters);
239 if (!empty($urlParameters['M'])) {
240 $moduleName = $urlParameters['M'];
241 unset($urlParameters['M']);
242 $contentUrl = BackendUtility::getModuleUrl($moduleName, $urlParameters);
243 }
244 return $contentUrl;
245 }
246
247 /**
248 * Fetches the items that should be disabled from the context menu
249 *
250 * @return array
251 */
252 protected function getDisableActions()
253 {
254 $tsConfig = $this->getBackendUser()
255 ->getTSConfig('options.contextMenu.' . $this->getContextMenuType() . '.disableItems');
256 $disableItems = [];
257 if (trim($tsConfig['value']) !== '') {
258 $disableItems = GeneralUtility::trimExplode(',', $tsConfig['value']);
259 }
260 $tsConfig = $this->getBackendUser()->getTSConfig('options.contextMenu.pageTree.disableItems');
261 $oldDisableItems = [];
262 if (trim($tsConfig['value']) !== '') {
263 $oldDisableItems = GeneralUtility::trimExplode(',', $tsConfig['value']);
264 }
265 $additionalItems = [];
266 foreach ($oldDisableItems as $item) {
267 if (empty($this->legacyContextMenuMapping[$item])) {
268 $additionalItems[] = $item;
269 continue;
270 }
271 if (strpos($this->legacyContextMenuMapping[$item], ',')) {
272 $actions = GeneralUtility::trimExplode(',', $this->legacyContextMenuMapping[$item]);
273 $additionalItems = array_merge($additionalItems, $actions);
274 } else {
275 $additionalItems[] = $item;
276 }
277 }
278 $disableItems = array_merge($disableItems, $additionalItems);
279
280 // Further manipulation of disableItems array via hook
281 // @internal: This is an internal hook for extension impexp only, this hook may change without further notice
282 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['backend']['contextMenu']['disableItems'])
283 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['backend']['contextMenu']['disableItems'])
284 ) {
285 $hooks = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['backend']['contextMenu']['disableItems'];
286 foreach ($hooks as $hook) {
287 $parameterArray = [
288 'disableItems' => &$disableItems,
289 ];
290 $null = null;
291 GeneralUtility::callUserFunction($hook, $parameterArray, $null);
292 }
293 }
294
295 return $disableItems;
296 }
297
298 /**
299 * Returns the actions for the node
300 * @param TreeNode $node
301 *
302 * @return array|ContextMenuActionCollection
303 */
304 public function getActionsForNode(TreeNode $node)
305 {
306 $this->disableItems = $this->getDisableActions();
307 $configuration = $this->getConfiguration();
308 $contextMenuActions = [];
309 if (is_array($configuration)) {
310 $contextMenuActions = $this->getNextContextMenuLevel($configuration, $node);
311 }
312 return $contextMenuActions;
313 }
314
315 /**
316 * Returns LanguageService
317 *
318 * @return \TYPO3\CMS\Lang\LanguageService
319 */
320 protected function getLanguageService()
321 {
322 return $GLOBALS['LANG'];
323 }
324
325 /**
326 * Returns the current BE user.
327 *
328 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
329 */
330 protected function getBackendUser()
331 {
332 return $GLOBALS['BE_USER'];
333 }
334 }