[FOLLOWUP][TASK] Use only one instance of iconFactory per class
[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 $this->moduleTemplate->setContent($this->content);
145
146 $response->getBody()->write($this->moduleTemplate->renderContent());
147 return $response;
148 }
149
150 /**
151 * Creates the module's content. In this case it rather acts as a kind of #
152 * dispatcher redirecting requests to specific tasks.
153 *
154 * @return void
155 */
156 public function main()
157 {
158 $this->getButtons();
159 $this->generateMenu();
160 $this->moduleTemplate->addJavaScriptCode(
161 'TaskCenterInlineJavascript',
162 'if (top.fsMod) { top.fsMod.recentIds["web"] = 0; }'
163 );
164
165 // Render content depending on the mode
166 $mode = (string)$this->MOD_SETTINGS['mode'];
167 if ($mode === 'information') {
168 $this->renderInformationContent();
169 } else {
170 $this->renderModuleContent();
171 }
172 // Renders the module page
173 $this->moduleTemplate->setTitle($this->getLanguageService()->getLL('title'));
174 }
175
176 /**
177 * Prints out the module's HTML
178 *
179 * @return void
180 */
181 public function printContent()
182 {
183 echo $this->content;
184 }
185
186 /**
187 * Generates the module content by calling the selected task
188 *
189 * @return void
190 */
191 protected function renderModuleContent()
192 {
193 $chosenTask = (string)$this->MOD_SETTINGS['function'];
194 // Render the taskcenter task as default
195 if (empty($chosenTask) || $chosenTask == 'index') {
196 $chosenTask = 'taskcenter.tasks';
197 }
198 // Render the task
199 $actionContent = '';
200 list($extKey, $taskClass) = explode('.', $chosenTask, 2);
201 if (class_exists($taskClass)) {
202 $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
203 if ($taskInstance instanceof TaskInterface) {
204 // Check if the task is restricted to admins only
205 if ($this->checkAccess($extKey, $taskClass)) {
206 $actionContent .= $taskInstance->getTask();
207 } else {
208 $flashMessage = GeneralUtility::makeInstance(
209 FlashMessage::class,
210 $this->getLanguageService()->getLL('error-access', true),
211 $this->getLanguageService()->getLL('error_header'),
212 FlashMessage::ERROR
213 );
214 $actionContent .= $flashMessage->render();
215 }
216 } else {
217 // Error if the task is not an instance of \TYPO3\CMS\Taskcenter\TaskInterface
218 $flashMessage = GeneralUtility::makeInstance(
219 FlashMessage::class,
220 sprintf($this->getLanguageService()->getLL('error_no-instance', true), $taskClass, TaskInterface::class),
221 $this->getLanguageService()->getLL('error_header'),
222 FlashMessage::ERROR
223 );
224 $actionContent .= $flashMessage->render();
225 }
226 } else {
227 $flashMessage = GeneralUtility::makeInstance(
228 FlashMessage::class,
229 $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tabdescr'),
230 $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
231 FlashMessage::INFO
232 );
233 $actionContent .= $flashMessage->render();
234 }
235 $content = '<div id="taskcenter-main">
236 <div id="taskcenter-menu">' . $this->indexAction() . '</div>
237 <div id="taskcenter-item" class="' . htmlspecialchars(($extKey . '-' . $taskClass)) . '">' . $actionContent . '
238 </div>
239 </div>';
240 $this->content .= $content;
241 }
242
243 /**
244 * Generates the information content
245 *
246 * @return void
247 */
248 protected function renderInformationContent()
249 {
250 $content = $this->description($this->getLanguageService()->getLL('mlang_tabs_tab'), $this->getLanguageService()->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tabdescr'));
251 $content .= $this->getLanguageService()->getLL('taskcenter-about');
252 if ($this->getBackendUser()->isAdmin()) {
253 $content .= '<br /><br />' . $this->description($this->getLanguageService()->getLL('taskcenter-adminheader'), $this->getLanguageService()->getLL('taskcenter-admin'));
254 }
255 $this->content .= $content;
256 }
257
258 /**
259 * Render the headline of a task including a title and an optional description.
260 *
261 * @param string $title Title
262 * @param string $description Description
263 * @return string formatted title and description
264 */
265 public function description($title, $description = '')
266 {
267 $content = '<h1>' . nl2br(htmlspecialchars($title)) . '</h1>';
268 if (!empty($description)) {
269 $content .= '<p class="description">' . nl2br(htmlspecialchars($description)) . '</p>';
270 }
271 return $content;
272 }
273
274 /**
275 * Render a list of items as a nicely formated definition list including a
276 * link, icon, title and description.
277 * The keys of a single item are:
278 * - title: Title of the item
279 * - link: Link to the task
280 * - icon: Path to the icon or Icon as HTML if it begins with <img
281 * - description: Description of the task, using htmlspecialchars()
282 * - descriptionHtml: Description allowing HTML tags which will override the
283 * description
284 *
285 * @param array $items List of items to be displayed in the definition list.
286 * @param bool $mainMenu Set it to TRUE to render the main menu
287 * @return string Fefinition list
288 */
289 public function renderListMenu($items, $mainMenu = false)
290 {
291 $content = ($section = '');
292 $count = 0;
293 // Change the sorting of items to the user's one
294 if ($mainMenu) {
295 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Taskcenter/Taskcenter');
296 $userSorting = unserialize($this->getBackendUser()->uc['taskcenter']['sorting']);
297 if (is_array($userSorting)) {
298 $newSorting = array();
299 foreach ($userSorting as $item) {
300 if (isset($items[$item])) {
301 $newSorting[] = $items[$item];
302 unset($items[$item]);
303 }
304 }
305 $items = $newSorting + $items;
306 }
307 }
308 if (is_array($items) && !empty($items)) {
309 foreach ($items as $item) {
310 $title = htmlspecialchars($item['title']);
311 $icon = ($additionalClass = ($collapsedStyle = ''));
312 // Check for custom icon
313 if (!empty($item['icon'])) {
314 if (strpos($item['icon'], '<img ') === false) {
315 $absIconPath = GeneralUtility::getFileAbsFilename($item['icon']);
316 // If the file indeed exists, assemble relative path to it
317 if (file_exists($absIconPath)) {
318 $icon = '../' . str_replace(PATH_site, '', $absIconPath);
319 $icon = '<img src="' . $icon . '" title="' . $title . '" alt="' . $title . '" />';
320 }
321 if (@is_file($icon)) {
322 $icon = '<img src="' . PathUtility::getAbsoluteWebPath($icon) . '" width="16" height="16" title="' . $title . '" alt="' . $title . '" />';
323 }
324 } else {
325 $icon = $item['icon'];
326 }
327 }
328 $description = $item['descriptionHtml'] ?: '<p>' . nl2br(htmlspecialchars($item['description'])) . '</p>';
329 $id = $this->getUniqueKey($item['uid']);
330 // Collapsed & expanded menu items
331 if ($mainMenu && isset($this->getBackendUser()->uc['taskcenter']['states'][$id]) && $this->getBackendUser()->uc['taskcenter']['states'][$id]) {
332 $collapsedStyle = 'style="display:none"';
333 $additionalClass = 'collapsed';
334 } else {
335 $additionalClass = 'expanded';
336 }
337 // First & last menu item
338 if ($count == 0) {
339 $additionalClass .= ' first-item';
340 } elseif ($count + 1 === count($items)) {
341 $additionalClass .= ' last-item';
342 }
343 // Active menu item
344 $active = (string)$this->MOD_SETTINGS['function'] == $item['uid'] ? ' active-task' : '';
345 // Main menu: Render additional syntax to sort tasks
346 if ($mainMenu) {
347 $section = '<div class="down"><i class="fa fa-caret-down fa-fw"></i></div>
348 <div class="drag"><i class="fa fa-arrows"></i></div>';
349 $backgroundClass = 't3-row-header ';
350 } else {
351 $backgroundClass = '';
352 }
353 $content .= '<li class="' . $additionalClass . $active . '" id="el_' . $id . '">
354 ' . $section . '
355 <div class="image">' . $icon . '</div>
356 <div class="' . $backgroundClass . 'link"><a href="' . $item['link'] . '">' . $title . '</a></div>
357 <div class="content " ' . $collapsedStyle . '>' . $description . '</div>
358 </li>';
359 $count++;
360 }
361 $navigationId = $mainMenu ? 'id="task-list"' : '';
362 $content = '<ul ' . $navigationId . ' class="task-list">' . $content . '</ul>';
363 }
364 return $content;
365 }
366
367 /**
368 * Shows an overview list of available reports.
369 *
370 * @return string List of available reports
371 */
372 protected function indexAction()
373 {
374 $content = '';
375 $tasks = array();
376 $icon = ExtensionManagementUtility::extRelPath('taskcenter') . 'Resources/Public/Icons/module-taskcenter.svg';
377 // Render the tasks only if there are any available
378 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter']) && !empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'])) {
379 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'] as $extKey => $extensionReports) {
380 foreach ($extensionReports as $taskClass => $task) {
381 if (!$this->checkAccess($extKey, $taskClass)) {
382 continue;
383 }
384 $link = BackendUtility::getModuleUrl('user_task') . '&SET[function]=' . $extKey . '.' . $taskClass;
385 $taskTitle = $this->getLanguageService()->sL($task['title']);
386 $taskDescriptionHtml = '';
387 // Check for custom icon
388 if (!empty($task['icon'])) {
389 $icon = GeneralUtility::getFileAbsFilename($task['icon']);
390 }
391 if (class_exists($taskClass)) {
392 $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
393 if ($taskInstance instanceof TaskInterface) {
394 $taskDescriptionHtml = $taskInstance->getOverview();
395 }
396 }
397 // Generate an array of all tasks
398 $uniqueKey = $this->getUniqueKey($extKey . '.' . $taskClass);
399 $tasks[$uniqueKey] = array(
400 'title' => $taskTitle,
401 'descriptionHtml' => $taskDescriptionHtml,
402 'description' => $this->getLanguageService()->sL($task['description']),
403 'icon' => $icon,
404 'link' => $link,
405 'uid' => $extKey . '.' . $taskClass
406 );
407 }
408 }
409 $content .= $this->renderListMenu($tasks, true);
410 } else {
411 $flashMessage = GeneralUtility::makeInstance(
412 FlashMessage::class,
413 $this->getLanguageService()->getLL('no-tasks', true),
414 '',
415 FlashMessage::INFO
416 );
417 $this->content .= $flashMessage->render();
418 }
419 return $content;
420 }
421
422 /**
423 * Create the panel of buttons for submitting the form or otherwise
424 * perform operations.
425 *
426 * @return array All available buttons as an assoc. array
427 */
428 protected function getButtons()
429 {
430 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
431 $buttons = array(
432 'shortcut' => '',
433 'open_new_window' => $this->openInNewWindow()
434 );
435 // Fullscreen Button
436 $url = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
437 $onClick = 'devlogWin=window.open(' . GeneralUtility::quoteJSvalue($url) . ',\'taskcenter\',\'width=790,status=0,menubar=1,resizable=1,location=0,scrollbars=1,toolbar=0\');return false;';
438 $fullscreenButton = $buttonBar->makeLinkButton()
439 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow', true))
440 ->setOnClick($onClick)
441 ->setHref('#')
442 ->setIcon($this->iconFactory->getIcon('actions-window-open', Icon::SIZE_SMALL))
443 ;
444 $buttonBar->addButton($fullscreenButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
445
446 // Shortcut
447 if ($this->getBackendUser()->mayMakeShortcut()) {
448 $shortCutButton = $buttonBar->makeFullyRenderedButton()
449 ->setHtmlSource($this->moduleTemplate->makeShortcutIcon('', 'function', $this->moduleName));
450 $buttonBar->addButton($shortCutButton, ButtonBar::BUTTON_POSITION_RIGHT, 2);
451 $buttons['shortcut'] = $this->doc->makeShortcutIcon('', 'function', $this->moduleName);
452 }
453 return $buttons;
454 }
455
456 /**
457 * Check the access to a task. Considered are:
458 * - Admins are always allowed
459 * - Tasks can be restriced to admins only
460 * - Tasks can be blinded for Users with TsConfig taskcenter.<extensionkey>.<taskName> = 0
461 *
462 * @param string $extKey Extension key
463 * @param string $taskClass Name of the task
464 * @return bool Access to the task allowed or not
465 */
466 protected function checkAccess($extKey, $taskClass)
467 {
468 // Check if task is blinded with TsConfig (taskcenter.<extkey>.<taskName>
469 $tsConfig = $this->getBackendUser()->getTSConfig('taskcenter.' . $extKey . '.' . $taskClass);
470 if (isset($tsConfig['value']) && (int)$tsConfig['value'] === 0) {
471 return false;
472 }
473 // Admins are always allowed
474 if ($this->getBackendUser()->isAdmin()) {
475 return true;
476 }
477 // Check if task is restricted to admins
478 if ((int)$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'][$extKey][$taskClass]['admin'] === 1) {
479 return false;
480 }
481 return true;
482 }
483
484 /**
485 * Returns HTML code to dislay an url in an iframe at the right side of the taskcenter
486 *
487 * @param string $url Url to display
488 * @param int $max
489 * @return string Code that inserts the iframe (HTML)
490 */
491 public function urlInIframe($url, $max = 0)
492 {
493 return '<iframe scrolling="auto" width="100%" src="' . $url . '" name="list_frame" id="list_frame" frameborder="no"></iframe>';
494 }
495
496 /**
497 * Create a unique key from a string which can be used in JS for sorting
498 * Therefore '_' are replaced
499 *
500 * @param string $string string which is used to generate the identifier
501 * @return string Modified string
502 */
503 protected function getUniqueKey($string)
504 {
505 $search = array('.', '_');
506 $replace = array('-', '');
507 return str_replace($search, $replace, $string);
508 }
509
510 /**
511 * This method prepares the link for opening the devlog in a new window
512 *
513 * @return string Hyperlink with icon and appropriate JavaScript
514 */
515 protected function openInNewWindow()
516 {
517 $url = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
518 $onClick = 'devlogWin=window.open(' . GeneralUtility::quoteJSvalue($url) . ',\'taskcenter\',\'width=790,status=0,menubar=1,resizable=1,location=0,scrollbars=1,toolbar=0\');return false;';
519 $content = '<a href="#" onclick="' . htmlspecialchars($onClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow', true) . '">'
520 . $this->iconFactory->getIcon('actions-window-open', Icon::SIZE_SMALL)->render()
521 . '</a>';
522 return $content;
523 }
524
525 /**
526 * Returns the current BE user.
527 *
528 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
529 */
530 protected function getBackendUser()
531 {
532 return $GLOBALS['BE_USER'];
533 }
534
535 /**
536 * Returns LanguageService
537 *
538 * @return \TYPO3\CMS\Lang\LanguageService
539 */
540 protected function getLanguageService()
541 {
542 return $GLOBALS['LANG'];
543 }
544 }