1<?php
2namespace 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
17use Psr\Http\Message\ResponseInterface;
18use Psr\Http\Message\ServerRequestInterface;
19use TYPO3\CMS\Backend\Template\ModuleTemplate;
20use TYPO3\CMS\Backend\Utility\BackendUtility;
21use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
22use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
23use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
24use TYPO3\CMS\Core\Http\HtmlResponse;
25use TYPO3\CMS\Core\Localization\LanguageService;
26use TYPO3\CMS\Core\Messaging\FlashMessage;
27use TYPO3\CMS\Core\Messaging\FlashMessageService;
28use TYPO3\CMS\Core\Page\PageRenderer;
29use TYPO3\CMS\Core\Type\Bitmask\Permission;
30use TYPO3\CMS\Core\Utility\GeneralUtility;
31use TYPO3\CMS\Core\Utility\PathUtility;
32use TYPO3\CMS\Fluid\View\StandaloneView;
33use TYPO3\CMS\Taskcenter\TaskInterface;
34
35/**
36 * This class provides a task center for BE users
37 * @internal This is a specific Backend Controller implementation and is not considered part of the Public TYPO3 API.
38 */
39class TaskModuleController
40{
41    use PublicPropertyDeprecationTrait;
42    use PublicMethodDeprecationTrait;
43
44    /**
45     * @var array
46     */
47    private $deprecatedPublicProperties = [
48        'MCONF' => 'Using TaskModuleController::$MCONF is deprecated and will not be possible anymore in TYPO3 v10.0.',
49        'id' => 'Using TaskModuleController::$id is deprecated and will not be possible anymore in TYPO3 v10.0.',
50        'MOD_MENU' => 'Using TaskModuleController::$MOD_MENU is deprecated and will not be possible anymore in TYPO3 v10.0.',
51        'modMenu_type' => 'Using TaskModuleController::$modMenu_type is deprecated and will not be possible anymore in TYPO3 v10.0.',
52        'modMenu_setDefaultList' => 'Using TaskModuleController::$$modMenu_setDefaultList is deprecated and will not be possible anymore in TYPO3 v10.0.',
53        'modMenu_dontValidateList' => 'Using TaskModuleController::$modMenu_dontValidateList is deprecated and will not be possible anymore in TYPO3 v10.0.',
54        'content' => 'Using TaskModuleController::$content is deprecated and will not be possible anymore in TYPO3 v10.0.',
55        'perms_clause' => 'Using TaskModuleController::$perms_clause is deprecated, the property will be removed in TYPO3 v10.0.',
56        'CMD' => 'Using TaskModuleController::$CMD is deprecated, the property will be removed in TYPO3 v10.0.',
57        'extClassConf' => 'Using TaskModuleController::$extClassConf is deprecated, the property will be removed in TYPO3 v10.0.',
58        'extObj' => 'Using TaskModuleController::$extObj is deprecated, the property will be removed in TYPO3 v10.0.',
59    ];
60
61    /**
62     * @var array
63     */
64    private $deprecatedPublicMethods = [
65        'menuConfig' => 'Using TaskModuleController::menuConfig() is deprecated and will not be possible anymore in TYPO3 v10.0.',
66        'mergeExternalItems' => 'Using TaskModuleController::mergeExternalItems() is deprecated and will not be possible anymore in TYPO3 v10.0.',
67        'handleExternalFunctionValue' => 'Using TaskModuleController::handleExternalFunctionValue() is deprecated and will not be possible anymore in TYPO3 v10.0.',
68        'getExternalItemConfig' => 'Using TaskModuleController::getExternalItemConfig() is deprecated and will not be possible anymore in TYPO3 v10.0.',
69        'main' => 'Using TaskModuleController::main() is deprecated and will not be possible anymore in TYPO3 v10.0.',
70        'urlInIframe' => 'Using TaskModuleController::urlInIframe() is deprecated. The method will be removed in TYPO3 v10.0.',
71        'extObjHeader' => 'Using TaskModuleController::extObjHeader() is deprecated. The method will be removed in TYPO3 v10.0.',
72        'checkSubExtObj' => 'Using TaskModuleController::checkSubExtObj() is deprecated. The method will be removed in TYPO3 v10.0.',
73        'checkExtObj' => 'Using TaskModuleController::checkExtObj() is deprecated. The method will be removed in TYPO3 v10.0.',
74        'extObjContent' => 'Using TaskModuleController::extObjContent() is deprecated. The method will be removed in TYPO3 v10.0.',
75        'getExtObjContent' => 'Using TaskModuleController::getExtObjContent() is deprecated. The method will be removed in TYPO3 v10.0.',
76    ];
77
78    /**
79     * Loaded with the global array $MCONF which holds some module configuration from the conf.php file of backend modules.
80     *
81     * @see init()
82     * @var array
83     */
84    protected $MCONF = [];
85
86    /**
87     * The integer value of the GET/POST var, 'id'. Used for submodules to the 'Web' module (page id)
88     *
89     * @see init()
90     * @var int
91     */
92    protected $id;
93
94    /**
95     * The value of GET/POST var, 'CMD'
96     *
97     * @see init()
98     * @var mixed
99     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
100     */
101    protected $CMD;
102
103    /**
104     * A WHERE clause for selection records from the pages table based on read-permissions of the current backend user.
105     *
106     * @see init()
107     * @var string
108     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
109     */
110    protected $perms_clause;
111
112    /**
113     * The module menu items array. Each key represents a key for which values can range between the items in the array of that key.
114     *
115     * @see init()
116     * @var array
117     */
118    protected $MOD_MENU = [
119        'function' => []
120    ];
121
122    /**
123     * Current settings for the keys of the MOD_MENU array
124     * Public since task objects use this.
125     *
126     * @see $MOD_MENU
127     * @var array
128     */
129    public $MOD_SETTINGS = [];
130
131    /**
132     * Module TSconfig based on PAGE TSconfig / USER TSconfig
133     * Public since task objects use this.
134     *
135     * @see menuConfig()
136     * @var array
137     */
138    public $modTSconfig;
139
140    /**
141     * If type is 'ses' then the data is stored as session-lasting data. This means that it'll be wiped out the next time the user logs in.
142     * Can be set from extension classes of this class before the init() function is called.
143     *
144     * @see menuConfig(), \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData()
145     * @var string
146     */
147    protected $modMenu_type = '';
148
149    /**
150     * dontValidateList can be used to list variables that should not be checked if their value is found in the MOD_MENU array. Used for dynamically generated menus.
151     * Can be set from extension classes of this class before the init() function is called.
152     *
153     * @see menuConfig(), \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData()
154     * @var string
155     */
156    protected $modMenu_dontValidateList = '';
157
158    /**
159     * List of default values from $MOD_MENU to set in the output array (only if the values from MOD_MENU are not arrays)
160     * Can be set from extension classes of this class before the init() function is called.
161     *
162     * @see menuConfig(), \TYPO3\CMS\Backend\Utility\BackendUtility::getModuleData()
163     * @var string
164     */
165    protected $modMenu_setDefaultList = '';
166
167    /**
168     * Contains module configuration parts from TBE_MODULES_EXT if found
169     *
170     * @see handleExternalFunctionValue()
171     * @var array
172     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
173     */
174    protected $extClassConf;
175
176    /**
177     * Generally used for accumulating the output content of backend modules
178     *
179     * @var string
180     */
181    protected $content = '';
182
183    /**
184     * May contain an instance of a 'Function menu module' which connects to this backend module.
185     *
186     * @see checkExtObj()
187     * @var \object
188     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
189     */
190    protected $extObj;
191
192    /**
193     * @var array
194     */
195    protected $pageinfo;
196
197    /**
198     * ModuleTemplate Container
199     *
200     * @var ModuleTemplate
201     */
202    protected $moduleTemplate;
203
204    /**
205     * The name of the module
206     *
207     * @var string
208     */
209    protected $moduleName = 'user_task';
210
211    /**
212     * Initializes the Module
213     */
214    public function __construct()
215    {
216        $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
217        $this->getLanguageService()->includeLLFile('EXT:taskcenter/Resources/Private/Language/locallang_task.xlf');
218        $this->MCONF = [
219            'name' => $this->moduleName
220        ];
221        // Name might be set from outside
222        if (!$this->MCONF['name']) {
223            $this->MCONF = $GLOBALS['MCONF'];
224        }
225        $this->id = (int)GeneralUtility::_GP('id');
226        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
227        $this->CMD = GeneralUtility::_GP('CMD');
228        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
229        $this->perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
230        $this->menuConfig();
231        $this->handleExternalFunctionValue();
232    }
233
234    /**
235     * Adds items to the ->MOD_MENU array. Used for the function menu selector.
236     */
237    protected function menuConfig()
238    {
239        $this->MOD_MENU = ['mode' => []];
240        $languageService = $this->getLanguageService();
241        $this->MOD_MENU['mode']['information'] = $languageService->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang.xlf:task_overview');
242        $this->MOD_MENU['mode']['tasks'] = $languageService->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang.xlf:task_tasks');
243        // Copied from parent::menuConfig, because parent is hardcoded to menu.function,
244        // however menu.function is already used for the individual tasks. Therefore we use menu.mode here.
245        // Page/be_user TSconfig settings and blinding of menu-items
246        $this->modTSconfig['properties'] = BackendUtility::getPagesTSconfig($this->id)['mod.'][$this->moduleName . '.'] ?? [];
247        $this->MOD_MENU['mode'] = $this->mergeExternalItems($this->MCONF['name'], 'mode', $this->MOD_MENU['mode']);
248        $blindActions = $this->modTSconfig['properties']['menu.']['mode.'] ?? [];
249        foreach ($blindActions as $key => $value) {
250            if (!$value && array_key_exists($key, $this->MOD_MENU['mode'])) {
251                unset($this->MOD_MENU['mode'][$key]);
252            }
253        }
254        // Page / user TSconfig settings and blinding of menu-items
255        // Now overwrite the stuff again for unknown reasons
256        $this->modTSconfig['properties'] = BackendUtility::getPagesTSconfig($this->id)['mod.'][$this->MCONF['name'] . '.'] ?? [];
257        $this->MOD_MENU['function'] = $this->mergeExternalItems($this->MCONF['name'], 'function', $this->MOD_MENU['function']);
258        $blindActions = $this->modTSconfig['properties']['menu.']['function.'] ?? [];
259        foreach ($blindActions as $key => $value) {
260            if (!$value && array_key_exists($key, $this->MOD_MENU['function'])) {
261                unset($this->MOD_MENU['function'][$key]);
262            }
263        }
264        $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->MCONF['name'], $this->modMenu_type, $this->modMenu_dontValidateList, $this->modMenu_setDefaultList);
265    }
266
267    /**
268     * Generates the menu based on $this->MOD_MENU
269     *
270     * @throws \InvalidArgumentException
271     */
272    protected function generateMenu()
273    {
274        $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
275        $menu->setIdentifier('WebFuncJumpMenu');
276        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
277        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
278        foreach ($this->MOD_MENU['mode'] as $controller => $title) {
279            $item = $menu
280                ->makeMenuItem()
281                ->setHref(
282                    (string)$uriBuilder->buildUriFromRoute(
283                        $this->moduleName,
284                        [
285                            'id' => $this->id,
286                            'SET' => [
287                                'mode' => $controller
288                            ]
289                        ]
290                    )
291                )
292                ->setTitle($title);
293            if ($controller === $this->MOD_SETTINGS['mode']) {
294                $item->setActive(true);
295            }
296            $menu->addMenuItem($item);
297        }
298        $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
299    }
300
301    /**
302     * Injects the request object for the current request or subrequest
303     * Simply calls main() and writes the content to the response
304     *
305     * @param ServerRequestInterface $request the current request
306     * @return ResponseInterface the response with the content
307     */
308    public function mainAction(ServerRequestInterface $request): ResponseInterface
309    {
310        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
311        $GLOBALS['SOBE'] = $this;
312
313        $this->main();
314        $this->moduleTemplate->setContent($this->content);
315        return new HtmlResponse($this->moduleTemplate->renderContent());
316    }
317
318    /**
319     * Creates the module's content. In this case it rather acts as a kind of #
320     * dispatcher redirecting requests to specific tasks.
321     */
322    protected function main()
323    {
324        $this->getButtons();
325        $this->generateMenu();
326        $this->moduleTemplate->addJavaScriptCode(
327            'TaskCenterInlineJavascript',
328            'if (top.fsMod) { top.fsMod.recentIds["web"] = 0; }'
329        );
330
331        // Render content depending on the mode
332        $mode = (string)$this->MOD_SETTINGS['mode'];
333        if ($mode === 'information') {
334            $this->renderInformationContent();
335        } else {
336            $this->renderModuleContent();
337        }
338        // Renders the module page
339        $this->moduleTemplate->setTitle($this->getLanguageService()->getLL('title'));
340    }
341
342    /**
343     * Generates the module content by calling the selected task
344     */
345    protected function renderModuleContent()
346    {
347        $languageService = $this->getLanguageService();
348        $chosenTask = (string)$this->MOD_SETTINGS['function'];
349        // Render the taskcenter task as default
350        if (empty($chosenTask) || $chosenTask === 'index') {
351            $chosenTask = 'taskcenter.tasks';
352        }
353        // Render the task
354        $actionContent = '';
355        $flashMessage = null;
356        list($extKey, $taskClass) = explode('.', $chosenTask, 2);
357        if (class_exists($taskClass)) {
358            $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
359            if ($taskInstance instanceof TaskInterface) {
360                // Check if the task is restricted to admins only
361                if ($this->checkAccess($extKey, $taskClass)) {
362                    $actionContent .= $taskInstance->getTask();
363                } else {
364                    $flashMessage = GeneralUtility::makeInstance(
365                        FlashMessage::class,
366                        $languageService->getLL('error-access'),
367                        $languageService->getLL('error_header'),
368                        FlashMessage::ERROR
369                    );
370                }
371            } else {
372                // Error if the task is not an instance of \TYPO3\CMS\Taskcenter\TaskInterface
373                $flashMessage = GeneralUtility::makeInstance(
374                    FlashMessage::class,
375                    sprintf($languageService->getLL('error_no-instance'), $taskClass, TaskInterface::class),
376                    $languageService->getLL('error_header'),
377                    FlashMessage::ERROR
378                );
379            }
380        } else {
381            $flashMessage = GeneralUtility::makeInstance(
382                FlashMessage::class,
383                $languageService->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tabdescr'),
384                $languageService->sL('LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:mlang_tabs_tab'),
385                FlashMessage::INFO
386            );
387        }
388
389        if ($flashMessage) {
390            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
391            $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
392            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
393            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
394            $defaultFlashMessageQueue->enqueue($flashMessage);
395        }
396
397        $assigns = [];
398        $assigns['reports'] = $this->indexAction();
399        $assigns['taskClass'] = strtolower(str_replace('\\', '-', htmlspecialchars($extKey . '-' . $taskClass)));
400        $assigns['actionContent'] = $actionContent;
401
402        // Rendering of the output via fluid
403        $view = GeneralUtility::makeInstance(StandaloneView::class);
404        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
405            'EXT:taskcenter/Resources/Private/Templates/ModuleContent.html'
406        ));
407        $view->assignMultiple($assigns);
408        $this->content .= $view->render();
409    }
410
411    /**
412     * Generates the information content
413     */
414    protected function renderInformationContent()
415    {
416        $assigns = [];
417        $assigns['LLPrefix'] = 'LLL:EXT:taskcenter/Resources/Private/Language/locallang.xlf:';
418        $assigns['LLPrefixMod'] = 'LLL:EXT:taskcenter/Resources/Private/Language/locallang_mod.xlf:';
419        $assigns['LLPrefixTask'] = 'LLL:EXT:taskcenter/Resources/Private/Language/locallang_task.xlf:';
420        $assigns['admin'] = $this->getBackendUser()->isAdmin();
421
422        // Rendering of the output via fluid
423        $view = GeneralUtility::makeInstance(StandaloneView::class);
424        $view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:taskcenter/Resources/Private/Templates')]);
425        $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:taskcenter/Resources/Private/Partials')]);
426        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
427            'EXT:taskcenter/Resources/Private/Templates/InformationContent.html'
428        ));
429        $view->assignMultiple($assigns);
430        $this->content .= $view->render();
431    }
432
433    /**
434     * Render the headline of a task including a title and an optional description.
435     * Public since task objects use this.
436     *
437     * @param string $title Title
438     * @param string $description Description
439     * @return string formatted title and description
440     */
441    public function description($title, $description = '')
442    {
443        $descriptionView = GeneralUtility::makeInstance(StandaloneView::class);
444        $descriptionView->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
445            'EXT:taskcenter/Resources/Private/Partials/Description.html'
446        ));
447        $descriptionView->assign('title', $title);
448        $descriptionView->assign('description', $description);
449        return $descriptionView->render();
450    }
451
452    /**
453     * Render a list of items as a nicely formatted definition list including a link, icon, title and description.
454     * The keys of a single item are:
455     * - title:             Title of the item
456     * - link:              Link to the task
457     * - icon:              Path to the icon or Icon as HTML if it begins with <img
458     * - description:       Description of the task, using htmlspecialchars()
459     * - descriptionHtml:   Description allowing HTML tags which will override the description
460     * Public since task objects use this.
461     *
462     * @param array $items List of items to be displayed in the definition list.
463     * @param bool $mainMenu Set it to TRUE to render the main menu
464     * @return string Formatted definition list
465     */
466    public function renderListMenu($items, $mainMenu = false)
467    {
468        $assigns = [];
469        $assigns['mainMenu'] = $mainMenu;
470
471        // Change the sorting of items to the user's one
472        if ($mainMenu) {
473            $userSorting = unserialize($this->getBackendUser()->uc['taskcenter']['sorting'], ['allowed_classes' => false]);
474            if (is_array($userSorting)) {
475                $newSorting = [];
476                foreach ($userSorting as $item) {
477                    if (isset($items[$item])) {
478                        $newSorting[] = $items[$item];
479                        unset($items[$item]);
480                    }
481                }
482                $items = $newSorting + $items;
483            }
484        }
485        if (is_array($items) && !empty($items)) {
486            foreach ($items as $itemKey => &$item) {
487                // Check for custom icon
488                if (!empty($item['icon'])) {
489                    if (strpos($item['icon'], '<img ') === false) {
490                        $iconFile = GeneralUtility::getFileAbsFileName($item['icon']);
491                        if (@is_file($iconFile)) {
492                            $item['iconFile'] = PathUtility::getAbsoluteWebPath($iconFile);
493                        }
494                    }
495                }
496                $id = $this->getUniqueKey($item['uid']);
497                $contentId = strtolower(str_replace('\\', '-', $id));
498                $item['uniqueKey'] = $id;
499                $item['contentId'] = $contentId;
500                // Collapsed & expanded menu items
501                if (isset($this->getBackendUser()->uc['taskcenter']['states'][$id]) && $this->getBackendUser()->uc['taskcenter']['states'][$id]) {
502                    $item['ariaExpanded'] = 'true';
503                    $item['collapseIcon'] = 'actions-view-list-expand';
504                    $item['collapsed'] = '';
505                } else {
506                    $item['ariaExpanded'] = 'false';
507                    $item['collapseIcon'] = 'actions-view-list-collapse';
508                    $item['collapsed'] = 'in';
509                }
510                // Active menu item
511                $panelState = (string)$this->MOD_SETTINGS['function'] == $item['uid'] ? 'panel-active' : 'panel-default';
512                $item['panelState'] = $panelState;
513            }
514        }
515        $assigns['items'] = $items;
516
517        // Rendering of the output via fluid
518        $view = GeneralUtility::makeInstance(StandaloneView::class);
519        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
520            'EXT:taskcenter/Resources/Private/Templates/ListMenu.html'
521        ));
522        $view->assignMultiple($assigns);
523        return $view->render();
524    }
525
526    /**
527     * Shows an overview list of available reports.
528     *
529     * @return string List of available reports
530     */
531    protected function indexAction()
532    {
533        $languageService = $this->getLanguageService();
534        $content = '';
535        $tasks = [];
536        $defaultIcon = 'EXT:taskcenter/Resources/Public/Icons/module-taskcenter.svg';
537        /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
538        $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
539        // Render the tasks only if there are any available
540        if (count($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'] ?? [])) {
541            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'] as $extKey => $extensionReports) {
542                foreach ($extensionReports as $taskClass => $task) {
543                    if (!$this->checkAccess($extKey, $taskClass)) {
544                        continue;
545                    }
546                    $link = (string)$uriBuilder->buildUriFromRoute('user_task') . '&SET[function]=' . $extKey . '.' . $taskClass;
547                    $taskTitle = $languageService->sL($task['title']);
548                    $taskDescriptionHtml = '';
549
550                    if (class_exists($taskClass)) {
551                        $taskInstance = GeneralUtility::makeInstance($taskClass, $this);
552                        if ($taskInstance instanceof TaskInterface) {
553                            $taskDescriptionHtml = $taskInstance->getOverview();
554                        }
555                    }
556                    // Generate an array of all tasks
557                    $uniqueKey = $this->getUniqueKey($extKey . '.' . $taskClass);
558                    $tasks[$uniqueKey] = [
559                        'title' => $taskTitle,
560                        'descriptionHtml' => $taskDescriptionHtml,
561                        'description' => $languageService->sL($task['description']),
562                        'icon' => !empty($task['icon']) ? $task['icon'] : $defaultIcon,
563                        'link' => $link,
564                        'uid' => $extKey . '.' . $taskClass
565                    ];
566                }
567            }
568            $content .= $this->renderListMenu($tasks, true);
569        } else {
570            $flashMessage = GeneralUtility::makeInstance(
571                FlashMessage::class,
572                $languageService->getLL('no-tasks'),
573                '',
574                FlashMessage::INFO
575            );
576            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
577            $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
578            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
579            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
580            $defaultFlashMessageQueue->enqueue($flashMessage);
581        }
582        return $content;
583    }
584
585    /**
586     * Create the panel of buttons for submitting the form or otherwise
587     * perform operations.
588     */
589    protected function getButtons()
590    {
591        $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
592
593        // Shortcut
594        $shortcutButton = $buttonBar->makeShortcutButton()
595            ->setModuleName($this->moduleName)
596            ->setSetVariables(['function']);
597        $buttonBar->addButton($shortcutButton);
598    }
599
600    /**
601     * Check the access to a task. Considered are:
602     * - Admins are always allowed
603     * - Tasks can be restriced to admins only
604     * - Tasks can be blinded for Users with TsConfig taskcenter.<extensionkey>.<taskName> = 0
605     *
606     * @param string $extKey Extension key
607     * @param string $taskClass Name of the task
608     * @return bool Access to the task allowed or not
609     */
610    protected function checkAccess($extKey, $taskClass): bool
611    {
612        $backendUser = $this->getBackendUser();
613        // Admins are always allowed
614        if ($backendUser->isAdmin()) {
615            return true;
616        }
617        // Check if task is restricted to admins
618        if ((int)$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['taskcenter'][$extKey][$taskClass]['admin'] === 1) {
619            return false;
620        }
621        // Check if task is blinded with TsConfig (taskcenter.<extkey>.<taskName>
622        return (bool)($backendUser->getTSConfig()['taskcenter.'][$extKey . '.'][$taskClass] ?? true);
623    }
624
625    /**
626     * Returns HTML code to dislay an url in an iframe at the right side of the taskcenter
627     *
628     * @param string $url Url to display
629     * @return string Code that inserts the iframe (HTML)
630     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0. Remember to remove the fluid template, too.
631     */
632    protected function urlInIframe($url)
633    {
634        $urlView = GeneralUtility::makeInstance(StandaloneView::class);
635        $urlView->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName(
636            'EXT:taskcenter/Resources/Private/Partials/UrlInIframe.html'
637        ));
638        $urlView->assign('url', $url);
639        return $urlView->render();
640    }
641
642    /**
643     * Create a unique key from a string which can be used in JS for sorting
644     * Therefore '_' are replaced
645     *
646     * @param string $string string which is used to generate the identifier
647     * @return string Modified string
648     */
649    protected function getUniqueKey($string)
650    {
651        $search = ['.', '_'];
652        $replace = ['-', ''];
653        return str_replace($search, $replace, $string);
654    }
655
656    /**
657     * Returns the current BE user.
658     *
659     * @return BackendUserAuthentication
660     */
661    protected function getBackendUser(): BackendUserAuthentication
662    {
663        return $GLOBALS['BE_USER'];
664    }
665
666    /**
667     * Returns LanguageService
668     *
669     * @return LanguageService
670     */
671    protected function getLanguageService(): LanguageService
672    {
673        return $GLOBALS['LANG'];
674    }
675
676    /**
677     * Public since task objects use this.
678     *
679     * @return ModuleTemplate
680     */
681    public function getModuleTemplate(): ModuleTemplate
682    {
683        return $this->moduleTemplate;
684    }
685
686    /**
687     * Merges menu items from global array $TBE_MODULES_EXT
688     *
689     * @param string $modName Module name for which to find value
690     * @param string $menuKey Menu key, eg. 'function' for the function menu.
691     * @param array $menuArr The part of a MOD_MENU array to work on.
692     * @return array Modified array part.
693     * @internal
694     * @see \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::insertModuleFunction(), menuConfig()
695     */
696    protected function mergeExternalItems($modName, $menuKey, $menuArr)
697    {
698        $mergeArray = $GLOBALS['TBE_MODULES_EXT'][$modName]['MOD_MENU'][$menuKey];
699        if (is_array($mergeArray)) {
700            foreach ($mergeArray as $k => $v) {
701                if (((string)$v['ws'] === '' || $this->getBackendUser()->workspace === 0 && GeneralUtility::inList($v['ws'], 'online')) || $this->getBackendUser()->workspace === -1 && GeneralUtility::inList($v['ws'], 'offline') || $this->getBackendUser()->workspace > 0 && GeneralUtility::inList($v['ws'], 'custom')) {
702                    $menuArr[$k] = $this->getLanguageService()->sL($v['title']);
703                }
704            }
705        }
706        return $menuArr;
707    }
708
709    /**
710     * Loads $this->extClassConf with the configuration for the CURRENT function of the menu.
711     *
712     * @param string $MM_key The key to MOD_MENU for which to fetch configuration. 'function' is default since it is first and foremost used to get information per "extension object" (I think that is what its called)
713     * @param string $MS_value The value-key to fetch from the config array. If NULL (default) MOD_SETTINGS[$MM_key] will be used. This is useful if you want to force another function than the one defined in MOD_SETTINGS[function]. Call this in init() function of your Script Class: handleExternalFunctionValue('function', $forcedSubModKey)
714     * @see getExternalItemConfig(), init()
715     */
716    protected function handleExternalFunctionValue($MM_key = 'function', $MS_value = null)
717    {
718        if ($MS_value === null) {
719            $MS_value = $this->MOD_SETTINGS[$MM_key];
720        }
721        $this->extClassConf = $this->getExternalItemConfig($this->MCONF['name'], $MM_key, $MS_value);
722    }
723
724    /**
725     * Returns configuration values from the global variable $TBE_MODULES_EXT for the module given.
726     * For example if the module is named "web_info" and the "function" key ($menuKey) of MOD_SETTINGS is "stat" ($value) then you will have the values of $TBE_MODULES_EXT['webinfo']['MOD_MENU']['function']['stat'] returned.
727     *
728     * @param string $modName Module name
729     * @param string $menuKey Menu key, eg. "function" for the function menu. See $this->MOD_MENU
730     * @param string $value Optionally the value-key to fetch from the array that would otherwise have been returned if this value was not set. Look source...
731     * @return mixed The value from the TBE_MODULES_EXT array.
732     * @see handleExternalFunctionValue()
733     */
734    protected function getExternalItemConfig($modName, $menuKey, $value = '')
735    {
736        if (isset($GLOBALS['TBE_MODULES_EXT'][$modName])) {
737            return (string)$value !== '' ? $GLOBALS['TBE_MODULES_EXT'][$modName]['MOD_MENU'][$menuKey][$value] : $GLOBALS['TBE_MODULES_EXT'][$modName]['MOD_MENU'][$menuKey];
738        }
739        return null;
740    }
741
742    /**
743     * Creates an instance of the class found in $this->extClassConf['name'] in $this->extObj if any (this should hold three keys, "name", "path" and "title" if a "Function menu module" tries to connect...)
744     * This value in extClassConf might be set by an extension (in an ext_tables/ext_localconf file) which thus "connects" to a module.
745     * The array $this->extClassConf is set in handleExternalFunctionValue() based on the value of MOD_SETTINGS[function]
746     * If an instance is created it is initiated with $this passed as value and $this->extClassConf as second argument. Further the $this->MOD_SETTING is cleaned up again after calling the init function.
747     *
748     * @see handleExternalFunctionValue(), \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::insertModuleFunction(), $extObj
749     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
750     */
751    protected function checkExtObj()
752    {
753        if (is_array($this->extClassConf) && $this->extClassConf['name']) {
754            $this->extObj = GeneralUtility::makeInstance($this->extClassConf['name']);
755            $this->extObj->init($this, $this->extClassConf);
756            // Re-write:
757            $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->MCONF['name'], $this->modMenu_type, $this->modMenu_dontValidateList, $this->modMenu_setDefaultList);
758        }
759    }
760
761    /**
762     * Calls the checkExtObj function in sub module if present.
763     *
764     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
765     */
766    protected function checkSubExtObj()
767    {
768        if (is_object($this->extObj)) {
769            $this->extObj->checkExtObj();
770        }
771    }
772
773    /**
774     * Calls the 'header' function inside the "Function menu module" if present.
775     * A header function might be needed to add JavaScript or other stuff in the head. This can't be done in the main function because the head is already written.
776     *
777     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
778     */
779    protected function extObjHeader()
780    {
781        if (is_callable([$this->extObj, 'head'])) {
782            $this->extObj->head();
783        }
784    }
785
786    /**
787     * Calls the 'main' function inside the "Function menu module" if present
788     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
789     */
790    protected function extObjContent()
791    {
792        if ($this->extObj === null) {
793            $flashMessage = GeneralUtility::makeInstance(
794                FlashMessage::class,
795                $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:no_modules_registered'),
796                $this->getLanguageService()->getLL('title'),
797                FlashMessage::ERROR
798            );
799            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
800            $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
801            /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
802            $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
803            $defaultFlashMessageQueue->enqueue($flashMessage);
804        } else {
805            $this->extObj->pObj = $this;
806            if (is_callable([$this->extObj, 'main'])) {
807                $this->content .= $this->extObj->main();
808            }
809        }
810    }
811
812    /**
813     * Return the content of the 'main' function inside the "Function menu module" if present
814     *
815     * @return string
816     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
817     */
818    protected function getExtObjContent()
819    {
820        $savedContent = $this->content;
821        $this->content = '';
822        $this->extObjContent();
823        $newContent = $this->content;
824        $this->content = $savedContent;
825        return $newContent;
826    }
827
828    /**
829     * @return PageRenderer
830     * @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0.
831     */
832    protected function getPageRenderer(): PageRenderer
833    {
834        return GeneralUtility::makeInstance(PageRenderer::class);
835    }
836}
837