da4e5c531311e7e08b3550b32cef6c6ae887aff8
[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\Module\BaseScriptClass;
20 use TYPO3\CMS\Backend\Template\ModuleTemplate;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Core\Imaging\Icon;
23 use TYPO3\CMS\Core\Messaging\FlashMessage;
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 BaseScriptClass
32 {
33 /**
34 * @var array
35 */
36 protected $pageinfo;
37
38 /**
39 * ModuleTemplate Container
40 *
41 * @var ModuleTemplate
42 */
43 protected $moduleTemplate;
44
45 /**
46 * The name of the module
47 *
48 * @var string
49 */
50 protected $moduleName = 'user_task';
51
52 /**
53 * Initializes the Module
54 */
55 public function __construct()
56 {
57 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
58 $this->getLanguageService()->includeLLFile('EXT:taskcenter/Resources/Private/Language/locallang_task.xlf');
59 $this->MCONF = [
60 'name' => $this->moduleName
61 ];
62 parent::init();
63 }
64
65 /**
66 * Adds items to the ->MOD_MENU array. Used for the function menu selector.
67 *
68 * @return void
69 */
70 public function menuConfig()
71 {
72 $this->MOD_MENU = ['mode' => []];
73 $this->MOD_MENU['mode']['information'] = $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang.xlf:task_overview');
74 $this->MOD_MENU['mode']['tasks'] = $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang.xlf:task_tasks');
75 /* Copied from parent::menuConfig, because parent is hardcoded to menu.function,
76 * however menu.function is already used for the individual tasks.
77 * Therefore we use menu.mode here.
78 */
79 // Page/be_user TSconfig settings and blinding of menu-items
80 $this->modTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.' . $this->moduleName);
81 $this->MOD_MENU['mode'] = $this->mergeExternalItems($this->MCONF['name'], 'mode', $this->MOD_MENU['mode']);
82 $this->MOD_MENU['mode'] = BackendUtility::unsetMenuItems($this->modTSconfig['properties'], $this->MOD_MENU['mode'], 'menu.mode');
83 parent::menuConfig();
84 }
85
86 /**
87 * Generates the menu based on $this->MOD_MENU
88 *
89 * @throws \InvalidArgumentException
90 */
91 protected function generateMenu()
92 {
93 $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
94 $menu->setIdentifier('WebFuncJumpMenu');
95 foreach ($this->MOD_MENU['mode'] as $controller => $title) {
96 $item = $menu
97 ->makeMenuItem()
98 ->setHref(
99 BackendUtility::getModuleUrl(
100 $this->moduleName,
101 [
102 'id' => $this->id,
103 'SET' => [
104 'mode' => $controller
105 ]
106 ]
107 )
108 )
109 ->setTitle($title);
110 if ($controller === $this->MOD_SETTINGS['mode']) {
111 $item->setActive(true);
112 }
113 $menu->addMenuItem($item);
114 }
115 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
116 }
117
118 /**
119 * Injects the request object for the current request or subrequest
120 * Simply calls main() and writes the content to the response
121 *
122 * @param ServerRequestInterface $request the current request
123 * @param ResponseInterface $response
124 * @return ResponseInterface the response with the content
125 */
126 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
127 {
128 $GLOBALS['SOBE'] = $this;
129 $this->main();
130
131 $this->moduleTemplate->setContent($this->content);
132
133 $response->getBody()->write($this->moduleTemplate->renderContent());
134 return $response;
135 }
136
137 /**
138 * Creates the module's content. In this case it rather acts as a kind of #
139 * dispatcher redirecting requests to specific tasks.
140 *
141 * @return void
142 */
143 public function main()
144 {
145 $this->getButtons();
146 $this->generateMenu();
147 $this->moduleTemplate->addJavaScriptCode(
148 'TaskCenterInlineJavascript',
149 'if (top.fsMod) { top.fsMod.recentIds["web"] = 0; }'
150 );
151
152 // Render content depending on the mode
153 $mode = (string)$this->MOD_SETTINGS['mode'];
154 if ($mode === 'information') {
155 $this->renderInformationContent();
156 } else {
157 $this->renderModuleContent();
158 }
159 // Renders the module page
160 $this->moduleTemplate->setTitle($this->getLanguageService()->getLL('title'));
161 }
162
163 /**
164 * Prints out the module's HTML
165 *
166 * @return void
167 */
168 public function printContent()
169 {
170 echo $this->content;
171 }
172
173 /**
174 * Generates the module content by calling the selected task
175 *
176 * @return void
177 */
178 protected function renderModuleContent()
179 {
180 $chosenTask = (string)$this->MOD_SETTINGS['function'];
181 // Render the taskcenter task as default
182 if (empty($chosenTask) || $chosenTask == 'index') {
183 $chosenTask = 'taskcenter.tasks';
184 }
185 // Render the task
186 $actionContent = '';
187 $flashMessage = null;
188 list($extKey, $taskClass) = explode('.', $chosenTask, 2);
189 if (class_exists($taskClass)) {
190 $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
191 if ($taskInstance instanceof TaskInterface) {
192 // Check if the task is restricted to admins only
193 if ($this->checkAccess($extKey, $taskClass)) {
194 $actionContent .= $taskInstance->getTask();
195 } else {
196 $flashMessage = GeneralUtility::makeInstance(
197 FlashMessage::class,
198 $this->getLanguageService()->getLL('error-access'),
199 $this->getLanguageService()->getLL('error_header'),
200 FlashMessage::ERROR
201 );
202 }
203 } else {
204 // Error if the task is not an instance of \TYPO3\CMS\Taskcenter\TaskInterface
205 $flashMessage = GeneralUtility::makeInstance(
206 FlashMessage::class,
207 sprintf($this->getLanguageService()->getLL('error_no-instance'), $taskClass, TaskInterface::class),
208 $this->getLanguageService()->getLL('error_header'),
209 FlashMessage::ERROR
210 );
211 }
212 } else {
213 $flashMessage = GeneralUtility::makeInstance(
214 FlashMessage::class,
215 $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tabdescr'),
216 $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
217 FlashMessage::INFO
218 );
219 }
220
221 if ($flashMessage) {
222 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
223 $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
224 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
225 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
226 $defaultFlashMessageQueue->enqueue($flashMessage);
227 }
228
229 $content = '<div class="taskcenter">
230 <div class="row">
231 <div class="col-sm-4 col-md-3">
232 <div class="taskcenter-menu">
233 ' . $this->indexAction() . '
234 </div>
235 </div>
236 <div class="col-sm-8 col-md-9">
237 <div class="taskcenter-content taskcenter-content-' . strtolower(str_replace('\\', '-', htmlspecialchars(($extKey . '-' . $taskClass)))) . '">
238 ' . $actionContent . '
239 </div>
240 </div>
241 </div>
242 </div>';
243 $this->content .= $content;
244 }
245
246 /**
247 * Generates the information content
248 *
249 * @return void
250 */
251 protected function renderInformationContent()
252 {
253 $content = $this->description($this->getLanguageService()->getLL('mlang_tabs_tab'), $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tabdescr'));
254 $content .= $this->getLanguageService()->getLL('taskcenter-about');
255 if ($this->getBackendUser()->isAdmin()) {
256 $content .= '<br /><br />' . $this->description($this->getLanguageService()->getLL('taskcenter-adminheader'), $this->getLanguageService()->getLL('taskcenter-admin'));
257 }
258 $this->content .= $content;
259 }
260
261 /**
262 * Render the headline of a task including a title and an optional description.
263 *
264 * @param string $title Title
265 * @param string $description Description
266 * @return string formatted title and description
267 */
268 public function description($title, $description = '')
269 {
270 $content = '<h1>' . nl2br(htmlspecialchars($title)) . '</h1>';
271 if (!empty($description)) {
272 $content .= '<p>' . nl2br(htmlspecialchars($description)) . '</p>';
273 }
274 return $content;
275 }
276
277 /**
278 * Render a list of items as a nicely formated definition list including a
279 * link, icon, title and description.
280 * The keys of a single item are:
281 * - title: Title of the item
282 * - link: Link to the task
283 * - icon: Path to the icon or Icon as HTML if it begins with <img
284 * - description: Description of the task, using htmlspecialchars()
285 * - descriptionHtml: Description allowing HTML tags which will override the
286 * description
287 *
288 * @param array $items List of items to be displayed in the definition list.
289 * @param bool $mainMenu Set it to TRUE to render the main menu
290 * @return string Fefinition list
291 */
292 public function renderListMenu($items, $mainMenu = false)
293 {
294 $content = ($section = '');
295 $count = 0;
296 // Change the sorting of items to the user's one
297 if ($mainMenu) {
298 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Taskcenter/Taskcenter');
299 $userSorting = unserialize($this->getBackendUser()->uc['taskcenter']['sorting']);
300 if (is_array($userSorting)) {
301 $newSorting = [];
302 foreach ($userSorting as $item) {
303 if (isset($items[$item])) {
304 $newSorting[] = $items[$item];
305 unset($items[$item]);
306 }
307 }
308 $items = $newSorting + $items;
309 }
310 }
311 if (is_array($items) && !empty($items)) {
312 foreach ($items as $itemKey => $item) {
313 $title = htmlspecialchars($item['title']);
314 $icon = ($additionalClass = ($collapsedStyle = ''));
315 // Check for custom icon
316 if (!empty($item['icon'])) {
317 if (strpos($item['icon'], '<img ') === false) {
318 $iconFile = GeneralUtility::getFileAbsFileName($item['icon']);
319 if (@is_file($iconFile)) {
320 $icon = '<img src="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($iconFile)) . '" width="16" height="16" title="' . $title . '" alt="' . $title . '" />';
321 }
322 } else {
323 $icon = $item['icon'];
324 }
325 }
326 $description = $item['descriptionHtml'] ?: '<p>' . nl2br(htmlspecialchars($item['description'])) . '</p>';
327 $id = $this->getUniqueKey($item['uid']);
328 $contentId = strtolower(str_replace('\\', '-', $id));
329 // Collapsed & expanded menu items
330 if (isset($this->getBackendUser()->uc['taskcenter']['states'][$id]) && $this->getBackendUser()->uc['taskcenter']['states'][$id]) {
331 $collapsed = true;
332 $collapseIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-view-list-expand', Icon::SIZE_SMALL)->render('inline');
333 } else {
334 $collapsed = false;
335 $collapseIcon = $this->moduleTemplate->getIconFactory()->getIcon('actions-view-list-collapse', Icon::SIZE_SMALL)->render('inline');
336 }
337 // Active menu item
338 $panelState = (string)$this->MOD_SETTINGS['function'] == $item['uid'] ? 'panel-active' : 'panel-default';
339 $content .= '<li id="el_' . $id . '">
340 <div id="' . $contentId . '" data-taskcenter-id="' . $id . '" class="panel ' . $panelState . '">
341 <div class="panel-heading">
342 <div class="panel-heading-right">
343 <a href="#task_content_' . $contentId . '" class="panel-header-collapse t3js-taskcenter-header-collapse" role="button" data-toggle="collapse" data-uid="' . $contentId . '" aria-expanded="' . ($collapsed ? 'false' : 'true') . '">
344 ' . $collapseIcon . '
345 </a>
346 </div>
347 <div class="panel-heading-left">
348 <a href="' . $item['link'] . '" class="panel-title">
349 ' . ($icon ? '<span class="panel-title-icon">' . $icon . '</span>' : '') . '
350 <span class="panel-title-name">'
351 . $title . ' '
352 . $this->moduleTemplate->getIconFactory()->getIcon(
353 'actions-view-table-expand',
354 Icon::SIZE_SMALL
355 )->render('inline')
356 . '</span>
357 </a>
358 </div>
359 </div>
360 <div id="task_content_' . $contentId . '" class="panel-collapse collapse t3js-taskcenter-collapse ' . ($collapsed ? '' : 'in') . '" aria-expanded="true">
361 <div class="panel-body">
362 ' . $description . '
363 </div>
364 </div>
365 </div>
366 </li>';
367 $count++;
368 }
369 $navigationId = $mainMenu ? 'id="task-list"' : '';
370 $content = '<ul ' . $navigationId . ' class="list-unstyled">' . $content . '</ul>';
371 }
372 return $content;
373 }
374
375 /**
376 * Shows an overview list of available reports.
377 *
378 * @return string List of available reports
379 */
380 protected function indexAction()
381 {
382 $content = '';
383 $tasks = [];
384 $defaultIcon = 'EXT:taskcenter/Resources/Public/Icons/module-taskcenter.svg';
385 // Render the tasks only if there are any available
386 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter']) && !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'])) {
387 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'] as $extKey => $extensionReports) {
388 foreach ($extensionReports as $taskClass => $task) {
389 if (!$this->checkAccess($extKey, $taskClass)) {
390 continue;
391 }
392 $link = BackendUtility::getModuleUrl('user_task') . '&SET[function]=' . $extKey . '.' . $taskClass;
393 $taskTitle = $this->getLanguageService()->sL($task['title']);
394 $taskDescriptionHtml = '';
395
396 if (class_exists($taskClass)) {
397 $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
398 if ($taskInstance instanceof TaskInterface) {
399 $taskDescriptionHtml = $taskInstance->getOverview();
400 }
401 }
402 // Generate an array of all tasks
403 $uniqueKey = $this->getUniqueKey($extKey . '.' . $taskClass);
404 $tasks[$uniqueKey] = [
405 'title' => $taskTitle,
406 'descriptionHtml' => $taskDescriptionHtml,
407 'description' => $this->getLanguageService()->sL($task['description']),
408 'icon' => !empty($task['icon']) ? $task['icon'] : $defaultIcon,
409 'link' => $link,
410 'uid' => $extKey . '.' . $taskClass
411 ];
412 }
413 }
414 $content .= $this->renderListMenu($tasks, true);
415 } else {
416 $flashMessage = GeneralUtility::makeInstance(
417 FlashMessage::class,
418 $this->getLanguageService()->getLL('no-tasks'),
419 '',
420 FlashMessage::INFO
421 );
422 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
423 $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
424 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
425 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
426 $defaultFlashMessageQueue->enqueue($flashMessage);
427 }
428 return $content;
429 }
430
431 /**
432 * Create the panel of buttons for submitting the form or otherwise
433 * perform operations.
434 *
435 * @return void
436 */
437 protected function getButtons()
438 {
439 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
440
441 // Shortcut
442 $shortcutButton = $buttonBar->makeShortcutButton()
443 ->setModuleName($this->moduleName)
444 ->setSetVariables(['function']);
445 $buttonBar->addButton($shortcutButton);
446 }
447
448 /**
449 * Check the access to a task. Considered are:
450 * - Admins are always allowed
451 * - Tasks can be restriced to admins only
452 * - Tasks can be blinded for Users with TsConfig taskcenter.<extensionkey>.<taskName> = 0
453 *
454 * @param string $extKey Extension key
455 * @param string $taskClass Name of the task
456 * @return bool Access to the task allowed or not
457 */
458 protected function checkAccess($extKey, $taskClass)
459 {
460 // Check if task is blinded with TsConfig (taskcenter.<extkey>.<taskName>
461 $tsConfig = $this->getBackendUser()->getTSConfig('taskcenter.' . $extKey . '.' . $taskClass);
462 if (isset($tsConfig['value']) && (int)$tsConfig['value'] === 0) {
463 return false;
464 }
465 // Admins are always allowed
466 if ($this->getBackendUser()->isAdmin()) {
467 return true;
468 }
469 // Check if task is restricted to admins
470 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'][$extKey][$taskClass]['admin'] === 1) {
471 return false;
472 }
473 return true;
474 }
475
476 /**
477 * Returns HTML code to dislay an url in an iframe at the right side of the taskcenter
478 *
479 * @param string $url Url to display
480 * @return string Code that inserts the iframe (HTML)
481 */
482 public function urlInIframe($url)
483 {
484 return '<div class="panel panel-default"><iframe scrolling="auto" width="100%" src="' . $url . '" name="list_frame" id="list_frame" frameborder="no" style="height: 500px"></iframe></div>';
485 }
486
487 /**
488 * Create a unique key from a string which can be used in JS for sorting
489 * Therefore '_' are replaced
490 *
491 * @param string $string string which is used to generate the identifier
492 * @return string Modified string
493 */
494 protected function getUniqueKey($string)
495 {
496 $search = ['.', '_'];
497 $replace = ['-', ''];
498 return str_replace($search, $replace, $string);
499 }
500
501 /**
502 * Returns the current BE user.
503 *
504 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
505 */
506 protected function getBackendUser()
507 {
508 return $GLOBALS['BE_USER'];
509 }
510
511 /**
512 * Returns LanguageService
513 *
514 * @return \TYPO3\CMS\Lang\LanguageService
515 */
516 protected function getLanguageService()
517 {
518 return $GLOBALS['LANG'];
519 }
520
521 /**
522 * @return ModuleTemplate
523 */
524 public function getModuleTemplate()
525 {
526 return $this->moduleTemplate;
527 }
528 }