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