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