[BUGFIX] Make context menus work with modules
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / ContextMenu / AbstractContextMenuDataProvider.php
1 <?php
2 namespace TYPO3\CMS\Backend\ContextMenu;
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 use TYPO3\CMS\Backend\Utility\BackendUtility;
17
18 /**
19 * Abstract Context Menu Data Provider
20 *
21 * @author Stefan Galinski <stefan.galinski@gmail.com>
22 */
23 abstract class AbstractContextMenuDataProvider {
24
25 /**
26 * List of actions that are generally disabled
27 *
28 * @var array
29 */
30 protected $disableItems = array();
31
32 /**
33 * Context Menu Type (e.g. table.pages, table.tt_content)
34 *
35 * @var string
36 */
37 protected $contextMenuType = '';
38
39 /**
40 * Returns the context menu type
41 *
42 * @return string
43 */
44 public function getContextMenuType() {
45 return $this->contextMenuType;
46 }
47
48 /**
49 * Sets the context menu type
50 *
51 * @param string $contextMenuType
52 * @return void
53 */
54 public function setContextMenuType($contextMenuType) {
55 $this->contextMenuType = $contextMenuType;
56 }
57
58 /**
59 * Returns the actions of the node
60 *
61 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
62 * @return \TYPO3\CMS\Backend\ContextMenu\ContextMenuActionCollection
63 */
64 abstract public function getActionsForNode(\TYPO3\CMS\Backend\Tree\TreeNode $node);
65
66 /**
67 * Returns the configuration of the specified context menu type
68 *
69 * @return array
70 */
71 protected function getConfiguration() {
72 $contextMenuActions = $GLOBALS['BE_USER']->getTSConfig('options.contextMenu.' . $this->contextMenuType . '.items');
73 return $contextMenuActions['properties'];
74 }
75
76 /**
77 * Evaluates a given display condition and returns TRUE if the condition matches
78 *
79 * Examples:
80 * getContextInfo|inCutMode:1 || isInCopyMode:1
81 * isLeafNode:1
82 * isLeafNode:1 && isInCutMode:1
83 *
84 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
85 * @param string $displayCondition
86 * @return boolean
87 */
88 protected function evaluateDisplayCondition(\TYPO3\CMS\Backend\Tree\TreeNode $node, $displayCondition) {
89 if ($displayCondition === '') {
90 return TRUE;
91 }
92 // Parse condition string
93 $conditions = array();
94 preg_match_all('/(.+?)(>=|<=|!=|=|>|<)(.+?)(\\|\\||&&|$)/is', $displayCondition, $conditions);
95 $lastResult = FALSE;
96 $chainType = '';
97 $amountOfConditions = count($conditions[0]);
98 for ($i = 0; $i < $amountOfConditions; ++$i) {
99 // Check method for existence
100 $method = trim($conditions[1][$i]);
101 list($method, $index) = explode('|', $method);
102 if (!method_exists($node, $method)) {
103 continue;
104 }
105 // Fetch compare value
106 $returnValue = call_user_func(array($node, $method));
107 if (is_array($returnValue)) {
108 $returnValue = $returnValue[$index];
109 }
110 // Compare fetched and expected values
111 $operator = trim($conditions[2][$i]);
112 $expected = trim($conditions[3][$i]);
113 if ($operator === '=') {
114 $returnValue = $returnValue == $expected;
115 } elseif ($operator === '>') {
116 $returnValue = $returnValue > $expected;
117 } elseif ($operator === '<') {
118 $returnValue = $returnValue < $expected;
119 } elseif ($operator === '>=') {
120 $returnValue = $returnValue >= $expected;
121 } elseif ($operator === '<=') {
122 $returnValue = $returnValue <= $expected;
123 } elseif ($operator === '!=') {
124 $returnValue = $returnValue != $expected;
125 } else {
126 $returnValue = FALSE;
127 $lastResult = FALSE;
128 }
129 // Chain last result and the current if requested
130 if ($chainType === '||') {
131 $lastResult = $lastResult || $returnValue;
132 } elseif ($chainType === '&&') {
133 $lastResult = $lastResult && $returnValue;
134 } else {
135 $lastResult = $returnValue;
136 }
137 // Save chain type for the next condition
138 $chainType = trim($conditions[4][$i]);
139 }
140 return $lastResult;
141 }
142
143 /**
144 * Returns the next context menu level
145 *
146 * @param array $actions
147 * @param \TYPO3\CMS\Backend\Tree\TreeNode $node
148 * @param integer $level
149 * @return \TYPO3\CMS\Backend\ContextMenu\ContextMenuActionCollection
150 */
151 protected function getNextContextMenuLevel(array $actions, \TYPO3\CMS\Backend\Tree\TreeNode $node, $level = 0) {
152 /** @var $actionCollection \TYPO3\CMS\Backend\ContextMenu\ContextMenuActionCollection */
153 $actionCollection = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\ContextMenu\\ContextMenuActionCollection');
154 if ($level > 5) {
155 return $actionCollection;
156 }
157 $type = '';
158 foreach ($actions as $index => $actionConfiguration) {
159 if (substr($index, -1) !== '.') {
160 $type = $actionConfiguration;
161 if ($type !== 'DIVIDER') {
162 continue;
163 }
164 }
165 if (!in_array($type, array('DIVIDER', 'SUBMENU', 'ITEM'))) {
166 continue;
167 }
168 /** @var $action \TYPO3\CMS\Backend\ContextMenu\ContextMenuAction */
169 $action = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Backend\\ContextMenu\\ContextMenuAction');
170 $action->setId($index);
171 if ($type === 'DIVIDER') {
172 $action->setType('divider');
173 } else {
174 if (in_array($actionConfiguration['name'], $this->disableItems) || isset($actionConfiguration['displayCondition']) && trim($actionConfiguration['displayCondition']) !== '' && !$this->evaluateDisplayCondition($node, $actionConfiguration['displayCondition'])) {
175 unset($action);
176 continue;
177 }
178 $label = $GLOBALS['LANG']->sL($actionConfiguration['label'], TRUE);
179 if ($type === 'SUBMENU') {
180 $action->setType('submenu');
181 $action->setChildActions($this->getNextContextMenuLevel($actionConfiguration, $node, $level + 1));
182 } else {
183 $action->setType('action');
184 $action->setCallbackAction($actionConfiguration['callbackAction']);
185 if (is_array($actionConfiguration['customAttributes.'])) {
186 if (!empty($actionConfiguration['customAttributes.']['contentUrl'])) {
187 $actionConfiguration['customAttributes.']['contentUrl'] = $this->replaceModuleTokenInContentUrl($actionConfiguration['customAttributes.']['contentUrl']);
188 }
189 $action->setCustomAttributes($actionConfiguration['customAttributes.']);
190 }
191 }
192 $action->setLabel($label);
193 if (isset($actionConfiguration['icon']) && trim($actionConfiguration['icon']) !== '') {
194 $action->setIcon($actionConfiguration['icon']);
195 } elseif (isset($actionConfiguration['spriteIcon'])) {
196 $action->setClass(\TYPO3\CMS\Backend\Utility\IconUtility::getSpriteIconClasses($actionConfiguration['spriteIcon']));
197 }
198 }
199 $actionCollection->offsetSet($level . (int)$index, $action);
200 $actionCollection->ksort();
201 }
202 return $actionCollection;
203 }
204
205 /**
206 * Add the CSRF token to the module URL if a "M" parameter is found
207 *
208 * @param string $contentUrl
209 * @return string
210 */
211 protected function replaceModuleTokenInContentUrl($contentUrl) {
212 $parsedUrl = parse_url($contentUrl);
213 parse_str($parsedUrl['query'], $urlParameters);
214 if (isset($urlParameters['M'])) {
215 $moduleName = $urlParameters['M'];
216 unset($urlParameters['M']);
217 $contentUrl = BackendUtility::getModuleUrl($moduleName, $urlParameters);
218 }
219 return $contentUrl;
220 }
221 }