[BUGFIX] FormDataTraverser must check data type before explode()
[Packages/TYPO3.CMS.git] / typo3 / sysext / taskcenter / Classes / Controller / TaskModuleController.php
1 <?php
2 namespace TYPO3\CMS\Taskcenter\Controller;
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 Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Imaging\Icon;
21 use TYPO3\CMS\Core\Imaging\IconFactory;
22 use TYPO3\CMS\Core\Messaging\FlashMessage;
23 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Core\Utility\PathUtility;
26 use TYPO3\CMS\Taskcenter\TaskInterface;
27
28 /**
29 * This class provides a taskcenter for BE users
30 */
31 class TaskModuleController extends \TYPO3\CMS\Backend\Module\BaseScriptClass {
32
33 /**
34 * @var array
35 */
36 protected $pageinfo;
37
38 /**
39 * The name of the module
40 *
41 * @var string
42 */
43 protected $moduleName = 'user_task';
44
45 /**
46 * Initializes the Module
47 */
48 public function __construct() {
49 $this->getLanguageService()->includeLLFile('EXT:taskcenter/Resources/Private/Language/locallang_task.xlf');
50 $this->MCONF = array(
51 'name' => $this->moduleName
52 );
53 parent::init();
54 // Initialize document
55 $this->doc = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Template\DocumentTemplate::class);
56 $this->doc->setModuleTemplate(ExtensionManagementUtility::extPath('taskcenter') . 'Resources/Private/Templates/mod_template.html');
57 $this->getPageRenderer()->loadJquery();
58 $this->doc->addStyleSheet('tx_taskcenter', '../' . ExtensionManagementUtility::siteRelPath('taskcenter') . 'Resources/Public/Css/styles.css');
59 }
60
61 /**
62 * Adds items to the ->MOD_MENU array. Used for the function menu selector.
63 *
64 * @return void
65 */
66 public function menuConfig() {
67 $this->MOD_MENU = array('mode' => array());
68 $this->MOD_MENU['mode']['information'] = $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang.xlf:task_overview');
69 $this->MOD_MENU['mode']['tasks'] = $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang.xlf:task_tasks');
70 /* Copied from parent::menuConfig, because parent is hardcoded to menu.function,
71 * however menu.function is already used for the individual tasks.
72 * Therefore we use menu.mode here.
73 */
74 // Page/be_user TSconfig settings and blinding of menu-items
75 $this->modTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.' . $this->moduleName);
76 $this->MOD_MENU['mode'] = $this->mergeExternalItems($this->MCONF['name'], 'mode', $this->MOD_MENU['mode']);
77 $this->MOD_MENU['mode'] = BackendUtility::unsetMenuItems($this->modTSconfig['properties'], $this->MOD_MENU['mode'], 'menu.mode');
78 parent::menuConfig();
79 }
80
81
82 /**
83 * Injects the request object for the current request or subrequest
84 * Simply calls main() and writes the content to the response
85 *
86 * @param ServerRequestInterface $request the current request
87 * @param ResponseInterface $response
88 * @return ResponseInterface the response with the content
89 */
90 public function mainAction(ServerRequestInterface $request, ResponseInterface $response) {
91 $GLOBALS['SOBE'] = $this;
92 $this->main();
93
94 $response->getBody()->write($this->content);
95 return $response;
96 }
97
98 /**
99 * Creates the module's content. In this case it rather acts as a kind of #
100 * dispatcher redirecting requests to specific tasks.
101 *
102 * @return void
103 */
104 public function main() {
105 $docHeaderButtons = $this->getButtons();
106 $markers = array();
107 $this->doc->postCode = $this->doc->wrapScriptTags('if (top.fsMod) { top.fsMod.recentIds["web"] = 0; }');
108
109 // Render content depending on the mode
110 $mode = (string)$this->MOD_SETTINGS['mode'];
111 if ($mode === 'information') {
112 $this->renderInformationContent();
113 } else {
114 $this->renderModuleContent();
115 }
116 // Compile document
117 $markers['FUNC_MENU'] = BackendUtility::getFuncMenu(0, 'SET[mode]', $this->MOD_SETTINGS['mode'], $this->MOD_MENU['mode']);
118 $markers['CONTENT'] = $this->content;
119 // Build the <body> for the module
120 $this->content = $this->doc->moduleBody($this->pageinfo, $docHeaderButtons, $markers);
121 // Renders the module page
122 $this->content = $this->doc->render($this->getLanguageService()->getLL('title'), $this->content);
123 }
124
125 /**
126 * Prints out the module's HTML
127 *
128 * @return void
129 */
130 public function printContent() {
131 echo $this->content;
132 }
133
134 /**
135 * Generates the module content by calling the selected task
136 *
137 * @return void
138 */
139 protected function renderModuleContent() {
140 $chosenTask = (string)$this->MOD_SETTINGS['function'];
141 // Render the taskcenter task as default
142 if (empty($chosenTask) || $chosenTask == 'index') {
143 $chosenTask = 'taskcenter.tasks';
144 }
145 // Render the task
146 $actionContent = '';
147 list($extKey, $taskClass) = explode('.', $chosenTask, 2);
148 if (class_exists($taskClass)) {
149 $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
150 if ($taskInstance instanceof TaskInterface) {
151 // Check if the task is restricted to admins only
152 if ($this->checkAccess($extKey, $taskClass)) {
153 $actionContent .= $taskInstance->getTask();
154 } else {
155 $flashMessage = GeneralUtility::makeInstance(
156 FlashMessage::class,
157 $this->getLanguageService()->getLL('error-access', TRUE),
158 $this->getLanguageService()->getLL('error_header'),
159 FlashMessage::ERROR
160 );
161 $actionContent .= $flashMessage->render();
162 }
163 } else {
164 // Error if the task is not an instance of \TYPO3\CMS\Taskcenter\TaskInterface
165 $flashMessage = GeneralUtility::makeInstance(
166 FlashMessage::class,
167 sprintf($this->getLanguageService()->getLL('error_no-instance', TRUE), $taskClass, TaskInterface::class),
168 $this->getLanguageService()->getLL('error_header'),
169 FlashMessage::ERROR
170 );
171 $actionContent .= $flashMessage->render();
172 }
173 } else {
174 $flashMessage = GeneralUtility::makeInstance(
175 FlashMessage::class,
176 $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tabdescr'),
177 $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
178 FlashMessage::INFO
179 );
180 $actionContent .= $flashMessage->render();
181 }
182 $content = '<div id="taskcenter-main">
183 <div id="taskcenter-menu">' . $this->indexAction() . '</div>
184 <div id="taskcenter-item" class="' . htmlspecialchars(($extKey . '-' . $taskClass)) . '">' . $actionContent . '
185 </div>
186 </div>';
187 $this->content .= $content;
188 }
189
190 /**
191 * Generates the information content
192 *
193 * @return void
194 */
195 protected function renderInformationContent() {
196 $content = $this->description($this->getLanguageService()->getLL('mlang_tabs_tab'), $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tabdescr'));
197 $content .= $this->getLanguageService()->getLL('taskcenter-about');
198 if ($this->getBackendUser()->isAdmin()) {
199 $content .= '<br /><br />' . $this->description($this->getLanguageService()->getLL('taskcenter-adminheader'), $this->getLanguageService()->getLL('taskcenter-admin'));
200 }
201 $this->content .= $content;
202 }
203
204 /**
205 * Render the headline of a task including a title and an optional description.
206 *
207 * @param string $title Title
208 * @param string $description Description
209 * @return string formatted title and description
210 */
211 public function description($title, $description = '') {
212 $content = '<h1>' . nl2br(htmlspecialchars($title)) . '</h1>';
213 if (!empty($description)) {
214 $content .= '<p class="description">' . nl2br(htmlspecialchars($description)) . '</p>';
215 }
216 return $content;
217 }
218
219 /**
220 * Render a list of items as a nicely formated definition list including a
221 * link, icon, title and description.
222 * The keys of a single item are:
223 * - title: Title of the item
224 * - link: Link to the task
225 * - icon: Path to the icon or Icon as HTML if it begins with <img
226 * - description: Description of the task, using htmlspecialchars()
227 * - descriptionHtml: Description allowing HTML tags which will override the
228 * description
229 *
230 * @param array $items List of items to be displayed in the definition list.
231 * @param bool $mainMenu Set it to TRUE to render the main menu
232 * @return string Fefinition list
233 */
234 public function renderListMenu($items, $mainMenu = FALSE) {
235 $content = ($section = '');
236 $count = 0;
237 // Change the sorting of items to the user's one
238 if ($mainMenu) {
239 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Taskcenter/Taskcenter');
240 $userSorting = unserialize($this->getBackendUser()->uc['taskcenter']['sorting']);
241 if (is_array($userSorting)) {
242 $newSorting = array();
243 foreach ($userSorting as $item) {
244 if (isset($items[$item])) {
245 $newSorting[] = $items[$item];
246 unset($items[$item]);
247 }
248 }
249 $items = $newSorting + $items;
250 }
251 }
252 if (is_array($items) && !empty($items)) {
253 foreach ($items as $item) {
254 $title = htmlspecialchars($item['title']);
255 $icon = ($additionalClass = ($collapsedStyle = ''));
256 // Check for custom icon
257 if (!empty($item['icon'])) {
258 if (strpos($item['icon'], '<img ') === FALSE) {
259 $absIconPath = GeneralUtility::getFileAbsFilename($item['icon']);
260 // If the file indeed exists, assemble relative path to it
261 if (file_exists($absIconPath)) {
262 $icon = '../' . str_replace(PATH_site, '', $absIconPath);
263 $icon = '<img src="' . $icon . '" title="' . $title . '" alt="' . $title . '" />';
264 }
265 if (@is_file($icon)) {
266 $icon = '<img src="' . PathUtility::getAbsoluteWebPath($icon) . '" width="16" height="16" title="' . $title . '" alt="' . $title . '" />';
267 }
268 } else {
269 $icon = $item['icon'];
270 }
271 }
272 $description = $item['descriptionHtml'] ?: '<p>' . nl2br(htmlspecialchars($item['description'])) . '</p>';
273 $id = $this->getUniqueKey($item['uid']);
274 // Collapsed & expanded menu items
275 if ($mainMenu && isset($this->getBackendUser()->uc['taskcenter']['states'][$id]) && $this->getBackendUser()->uc['taskcenter']['states'][$id]) {
276 $collapsedStyle = 'style="display:none"';
277 $additionalClass = 'collapsed';
278 } else {
279 $additionalClass = 'expanded';
280 }
281 // First & last menu item
282 if ($count == 0) {
283 $additionalClass .= ' first-item';
284 } elseif ($count + 1 === count($items)) {
285 $additionalClass .= ' last-item';
286 }
287 // Active menu item
288 $active = (string)$this->MOD_SETTINGS['function'] == $item['uid'] ? ' active-task' : '';
289 // Main menu: Render additional syntax to sort tasks
290 if ($mainMenu) {
291 $section = '<div class="down"><i class="fa fa-caret-down fa-fw"></i></div>
292 <div class="drag"><i class="fa fa-arrows"></i></div>';
293 $backgroundClass = 't3-row-header ';
294 } else {
295 $backgroundClass = '';
296 }
297 $content .= '<li class="' . $additionalClass . $active . '" id="el_' . $id . '">
298 ' . $section . '
299 <div class="image">' . $icon . '</div>
300 <div class="' . $backgroundClass . 'link"><a href="' . $item['link'] . '">' . $title . '</a></div>
301 <div class="content " ' . $collapsedStyle . '>' . $description . '</div>
302 </li>';
303 $count++;
304 }
305 $navigationId = $mainMenu ? 'id="task-list"' : '';
306 $content = '<ul ' . $navigationId . ' class="task-list">' . $content . '</ul>';
307 }
308 return $content;
309 }
310
311 /**
312 * Shows an overview list of available reports.
313 *
314 * @return string List of available reports
315 */
316 protected function indexAction() {
317 $content = '';
318 $tasks = array();
319 $icon = ExtensionManagementUtility::extRelPath('taskcenter') . 'Resources/Public/Icons/module-taskcenter.svg';
320 // Render the tasks only if there are any available
321 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter']) && !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'])) {
322 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'] as $extKey => $extensionReports) {
323 foreach ($extensionReports as $taskClass => $task) {
324 if (!$this->checkAccess($extKey, $taskClass)) {
325 continue;
326 }
327 $link = BackendUtility::getModuleUrl('user_task') . '&SET[function]=' . $extKey . '.' . $taskClass;
328 $taskTitle = $this->getLanguageService()->sL($task['title']);
329 $taskDescriptionHtml = '';
330 // Check for custom icon
331 if (!empty($task['icon'])) {
332 $icon = GeneralUtility::getFileAbsFilename($task['icon']);
333 }
334 if (class_exists($taskClass)) {
335 $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
336 if ($taskInstance instanceof TaskInterface) {
337 $taskDescriptionHtml = $taskInstance->getOverview();
338 }
339 }
340 // Generate an array of all tasks
341 $uniqueKey = $this->getUniqueKey($extKey . '.' . $taskClass);
342 $tasks[$uniqueKey] = array(
343 'title' => $taskTitle,
344 'descriptionHtml' => $taskDescriptionHtml,
345 'description' => $this->getLanguageService()->sL($task['description']),
346 'icon' => $icon,
347 'link' => $link,
348 'uid' => $extKey . '.' . $taskClass
349 );
350 }
351 }
352 $content .= $this->renderListMenu($tasks, TRUE);
353 } else {
354 $flashMessage = GeneralUtility::makeInstance(
355 FlashMessage::class,
356 $this->getLanguageService()->getLL('no-tasks', TRUE),
357 '',
358 FlashMessage::INFO
359 );
360 $this->content .= $flashMessage->render();
361 }
362 return $content;
363 }
364
365 /**
366 * Create the panel of buttons for submitting the form or otherwise
367 * perform operations.
368 *
369 * @return array All available buttons as an assoc. array
370 */
371 protected function getButtons() {
372 $buttons = array(
373 'shortcut' => '',
374 'open_new_window' => $this->openInNewWindow()
375 );
376 // Shortcut
377 if ($this->getBackendUser()->mayMakeShortcut()) {
378 $buttons['shortcut'] = $this->doc->makeShortcutIcon('', 'function', $this->moduleName);
379 }
380 return $buttons;
381 }
382
383 /**
384 * Check the access to a task. Considered are:
385 * - Admins are always allowed
386 * - Tasks can be restriced to admins only
387 * - Tasks can be blinded for Users with TsConfig taskcenter.<extensionkey>.<taskName> = 0
388 *
389 * @param string $extKey Extension key
390 * @param string $taskClass Name of the task
391 * @return bool Access to the task allowed or not
392 */
393 protected function checkAccess($extKey, $taskClass) {
394 // Check if task is blinded with TsConfig (taskcenter.<extkey>.<taskName>
395 $tsConfig = $this->getBackendUser()->getTSConfig('taskcenter.' . $extKey . '.' . $taskClass);
396 if (isset($tsConfig['value']) && (int)$tsConfig['value'] === 0) {
397 return FALSE;
398 }
399 // Admins are always allowed
400 if ($this->getBackendUser()->isAdmin()) {
401 return TRUE;
402 }
403 // Check if task is restricted to admins
404 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'][$extKey][$taskClass]['admin'] === 1) {
405 return FALSE;
406 }
407 return TRUE;
408 }
409
410 /**
411 * Returns HTML code to dislay an url in an iframe at the right side of the taskcenter
412 *
413 * @param string $url Url to display
414 * @param int $max
415 * @return string Code that inserts the iframe (HTML)
416 */
417 public function urlInIframe($url, $max = 0) {
418 return '<iframe scrolling="auto" width="100%" src="' . $url . '" name="list_frame" id="list_frame" frameborder="no"></iframe>';
419 }
420
421 /**
422 * Create a unique key from a string which can be used in JS for sorting
423 * Therefore '_' are replaced
424 *
425 * @param string $string string which is used to generate the identifier
426 * @return string Modified string
427 */
428 protected function getUniqueKey($string) {
429 $search = array('.', '_');
430 $replace = array('-', '');
431 return str_replace($search, $replace, $string);
432 }
433
434 /**
435 * This method prepares the link for opening the devlog in a new window
436 *
437 * @return string Hyperlink with icon and appropriate JavaScript
438 */
439 protected function openInNewWindow() {
440 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
441 $url = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
442 $onClick = 'devlogWin=window.open(' . GeneralUtility::quoteJSvalue($url) . ',\'taskcenter\',\'width=790,status=0,menubar=1,resizable=1,location=0,scrollbars=1,toolbar=0\');return false;';
443 $content = '<a href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow', TRUE) . '">'
444 . $iconFactory->getIcon('actions-window-open', Icon::SIZE_SMALL)->render()
445 . '</a>';
446 return $content;
447 }
448
449 /**
450 * Returns the current BE user.
451 *
452 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
453 */
454 protected function getBackendUser() {
455 return $GLOBALS['BE_USER'];
456 }
457
458 /**
459 * Returns LanguageService
460 *
461 * @return \TYPO3\CMS\Lang\LanguageService
462 */
463 protected function getLanguageService() {
464 return $GLOBALS['LANG'];
465 }
466
467 }