1<?php
2namespace TYPO3\CMS\Workspaces\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 TYPO3\CMS\Backend\Routing\UriBuilder;
18use TYPO3\CMS\Backend\Utility\BackendUtility;
19use TYPO3\CMS\Backend\View\BackendTemplateView;
20use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21use TYPO3\CMS\Core\Imaging\Icon;
22use TYPO3\CMS\Core\Imaging\IconFactory;
23use TYPO3\CMS\Core\Localization\LanguageService;
24use TYPO3\CMS\Core\Page\PageRenderer;
25use TYPO3\CMS\Core\Utility\GeneralUtility;
26use TYPO3\CMS\Core\Versioning\VersionState;
27use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
28use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
29use TYPO3\CMS\Workspaces\Service\AdditionalColumnService;
30use TYPO3\CMS\Workspaces\Service\AdditionalResourceService;
31use TYPO3\CMS\Workspaces\Service\WorkspaceService;
32
33/**
34 * @internal This is a specific Backend Controller implementation and is not considered part of the Public TYPO3 API.
35 */
36class ReviewController extends ActionController
37{
38    /**
39     * @var string
40     */
41    protected $defaultViewObjectName = BackendTemplateView::class;
42
43    /**
44     * @var BackendTemplateView
45     */
46    protected $view;
47
48    /**
49     * @var PageRenderer
50     */
51    protected $pageRenderer;
52
53    /**
54     * @var int
55     */
56    protected $pageId;
57
58    /**
59     * Set up the doc header properly here
60     *
61     * @param ViewInterface $view
62     */
63    protected function initializeView(ViewInterface $view)
64    {
65        parent::initializeView($view);
66        $this->registerButtons();
67        $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
68    }
69
70    /**
71     * Registers the DocHeader buttons
72     */
73    protected function registerButtons()
74    {
75        $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
76        $currentRequest = $this->request;
77        $moduleName = $currentRequest->getPluginName();
78        $getVars = $this->request->getArguments();
79        $extensionName = $currentRequest->getControllerExtensionName();
80        if (count($getVars) === 0) {
81            $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
82            $getVars = ['id', 'route', $modulePrefix];
83        }
84        $shortcutButton = $buttonBar->makeShortcutButton()
85            ->setModuleName($moduleName)
86            ->setGetVariables($getVars);
87        $buttonBar->addButton($shortcutButton);
88    }
89
90    /**
91     * Initializes the controller before invoking an action method.
92     */
93    protected function initializeAction()
94    {
95        $this->pageRenderer = $this->getPageRenderer();
96        // @todo Evaluate how the (int) typecast can be used with Extbase validators/filters
97        $this->pageId = (int)GeneralUtility::_GP('id');
98        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
99        $lang = $this->getLanguageService();
100        $icons = [
101            'language' => $iconFactory->getIcon('flags-multiple', Icon::SIZE_SMALL)->render(),
102            'integrity' => $iconFactory->getIcon('status-dialog-information', Icon::SIZE_SMALL)->render(),
103            'success' => $iconFactory->getIcon('status-dialog-ok', Icon::SIZE_SMALL)->render(),
104            'info' => $iconFactory->getIcon('status-dialog-information', Icon::SIZE_SMALL)->render(),
105            'warning' => $iconFactory->getIcon('status-dialog-warning', Icon::SIZE_SMALL)->render(),
106            'error' => $iconFactory->getIcon('status-dialog-error', Icon::SIZE_SMALL)->render()
107        ];
108        $this->pageRenderer->addInlineSetting('Workspaces', 'icons', $icons);
109        $this->pageRenderer->addInlineSetting('Workspaces', 'id', $this->pageId);
110        $this->pageRenderer->addInlineSetting('Workspaces', 'depth', $this->pageId === 0 ? 999 : 1);
111        $this->pageRenderer->addInlineSetting('Workspaces', 'language', $this->getLanguageSelection());
112        $this->pageRenderer->addInlineLanguageLabelArray([
113            'title' => $lang->getLL('title'),
114            'path' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.path'),
115            'table' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.table'),
116            'depth' => $lang->sL('LLL:EXT:beuser/Resources/Private/Language/locallang_mod_permission.xlf:Depth'),
117            'depth_0' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
118            'depth_1' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
119            'depth_2' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
120            'depth_3' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
121            'depth_4' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
122            'depth_infi' => $lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.depth_infi')
123        ]);
124        $this->pageRenderer->addInlineLanguageLabelFile('EXT:workspaces/Resources/Private/Language/locallang.xlf');
125        $states = $this->getBackendUser()->uc['moduleData']['Workspaces']['States'];
126        $this->pageRenderer->addInlineSetting('Workspaces', 'States', $states);
127
128        foreach ($this->getAdditionalResourceService()->getLocalizationResources() as $localizationResource) {
129            $this->pageRenderer->addInlineLanguageLabelFile($localizationResource);
130        }
131        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
132        $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Workspaces/Backend');
133        $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('record_edit'));
134        $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', (string)$uriBuilder->buildUriFromRoute('record_history'));
135        $this->pageRenderer->addInlineSetting('Workspaces', 'id', (int)GeneralUtility::_GP('id'));
136
137        $this->assignExtensionSettings();
138    }
139
140    /**
141     * Renders the review module user dependent with all workspaces.
142     * The module will show all records of one workspace.
143     */
144    public function indexAction()
145    {
146        $backendUser = $this->getBackendUser();
147        $moduleTemplate = $this->view->getModuleTemplate();
148
149        if (GeneralUtility::_GP('id')) {
150            $pageRecord = BackendUtility::getRecord('pages', GeneralUtility::_GP('id'));
151            if ($pageRecord) {
152                $moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageRecord);
153                $this->view->assign('pageTitle', BackendUtility::getRecordTitle('pages', $pageRecord));
154            }
155        }
156        $wsList = GeneralUtility::makeInstance(WorkspaceService::class)->getAvailableWorkspaces();
157        $activeWorkspace = $backendUser->workspace;
158        $performWorkspaceSwitch = false;
159        // Only admins see multiple tabs, we decided to use it this
160        // way for usability reasons. Regular users might be confused
161        // by switching workspaces with the tabs in a module.
162        if (!$backendUser->isAdmin()) {
163            $wsCur = [$activeWorkspace => true];
164            $wsList = array_intersect_key($wsList, $wsCur);
165        } else {
166            if ((string)GeneralUtility::_GP('workspace') !== '') {
167                $switchWs = (int)GeneralUtility::_GP('workspace');
168                if (array_key_exists($switchWs, $wsList) && $activeWorkspace != $switchWs) {
169                    $activeWorkspace = $switchWs;
170                    $backendUser->setWorkspace($activeWorkspace);
171                    $performWorkspaceSwitch = true;
172                    BackendUtility::setUpdateSignal('updatePageTree');
173                } elseif ($switchWs == WorkspaceService::SELECT_ALL_WORKSPACES) {
174                    $this->redirect('fullIndex');
175                }
176            }
177        }
178        $this->pageRenderer->addInlineSetting('Workspaces', 'isLiveWorkspace', (int)$backendUser->workspace === 0);
179        $this->pageRenderer->addInlineSetting('Workspaces', 'workspaceTabs', $this->prepareWorkspaceTabs($wsList, $activeWorkspace));
180        $this->pageRenderer->addInlineSetting('Workspaces', 'activeWorkspaceId', $activeWorkspace);
181        $workspaceIsAccessible = !($backendUser->workspace === 0 && !$backendUser->isAdmin());
182        $this->view->assignMultiple([
183            'showGrid' => $workspaceIsAccessible,
184            'showLegend' => $workspaceIsAccessible,
185            'pageUid' => (int)GeneralUtility::_GP('id'),
186            'performWorkspaceSwitch' => $performWorkspaceSwitch,
187            'workspaceList' => $this->prepareWorkspaceTabs($wsList, $activeWorkspace),
188            'activeWorkspaceUid' => $activeWorkspace,
189            'activeWorkspaceTitle' => WorkspaceService::getWorkspaceTitle($activeWorkspace),
190        ]);
191
192        if ($this->canCreatePreviewLink((int)GeneralUtility::_GP('id'), (int)$activeWorkspace)) {
193            $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
194            $iconFactory = $moduleTemplate->getIconFactory();
195            $showButton = $buttonBar->makeLinkButton()
196                ->setHref('#')
197                ->setClasses('t3js-preview-link')
198                ->setShowLabelText(true)
199                ->setTitle($this->getLanguageService()->sL('LLL:EXT:workspaces/Resources/Private/Language/locallang.xlf:tooltip.generatePagePreview'))
200                ->setIcon($iconFactory->getIcon('actions-version-workspaces-preview-link', Icon::SIZE_SMALL));
201            $buttonBar->addButton($showButton);
202        }
203        $backendUser->setAndSaveSessionData('tx_workspace_activeWorkspace', $activeWorkspace);
204    }
205
206    /**
207     * Renders the review module user dependent.
208     * The module will show all records of all workspaces.
209     */
210    public function fullIndexAction()
211    {
212        $wsService = GeneralUtility::makeInstance(WorkspaceService::class);
213        $wsList = $wsService->getAvailableWorkspaces();
214
215        $activeWorkspace = $this->getBackendUser()->workspace;
216        if (!$this->getBackendUser()->isAdmin()) {
217            $wsCur = [$activeWorkspace => true];
218            $wsList = array_intersect_key($wsList, $wsCur);
219        }
220
221        $this->pageRenderer->addInlineSetting('Workspaces', 'workspaceTabs', $this->prepareWorkspaceTabs($wsList, WorkspaceService::SELECT_ALL_WORKSPACES));
222        $this->pageRenderer->addInlineSetting('Workspaces', 'activeWorkspaceId', WorkspaceService::SELECT_ALL_WORKSPACES);
223        $this->view->assignMultiple([
224            'pageUid' => (int)GeneralUtility::_GP('id'),
225            'showGrid' => true,
226            'showLegend' => true,
227            'workspaceList' => $this->prepareWorkspaceTabs($wsList, $activeWorkspace),
228            'activeWorkspaceUid' => WorkspaceService::SELECT_ALL_WORKSPACES
229        ]);
230        $this->getBackendUser()->setAndSaveSessionData('tx_workspace_activeWorkspace', WorkspaceService::SELECT_ALL_WORKSPACES);
231        // set flag for javascript
232        $this->pageRenderer->addInlineSetting('Workspaces', 'allView', '1');
233    }
234
235    /**
236     * Renders the review module for a single page. This is used within the
237     * workspace-preview frame.
238     */
239    public function singleIndexAction()
240    {
241        $wsService = GeneralUtility::makeInstance(WorkspaceService::class);
242        $wsList = $wsService->getAvailableWorkspaces();
243        $activeWorkspace = $this->getBackendUser()->workspace;
244        $wsCur = [$activeWorkspace => true];
245        $wsList = array_intersect_key($wsList, $wsCur);
246        $this->view->assignMultiple([
247            'pageUid' => (int)GeneralUtility::_GP('id'),
248            'showGrid' => true,
249            'workspaceList' => $this->prepareWorkspaceTabs($wsList, (int)$activeWorkspace, false),
250            'activeWorkspaceUid' => $activeWorkspace,
251        ]);
252        $this->pageRenderer->addInlineSetting('Workspaces', 'singleView', '1');
253    }
254
255    /**
256     * Prepares available workspace tabs.
257     *
258     * @param array $workspaceList
259     * @param int $activeWorkspace
260     * @param bool $showAllWorkspaceTab
261     * @return array
262     */
263    protected function prepareWorkspaceTabs(array $workspaceList, int $activeWorkspace, bool $showAllWorkspaceTab = true)
264    {
265        $tabs = [];
266
267        if ($activeWorkspace !== WorkspaceService::SELECT_ALL_WORKSPACES) {
268            $tabs[] = [
269                'title' => $workspaceList[$activeWorkspace],
270                'itemId' => 'workspace-' . $activeWorkspace,
271                'workspaceId' => $activeWorkspace,
272                'triggerUrl' => $this->getModuleUri($activeWorkspace),
273            ];
274        }
275
276        if ($showAllWorkspaceTab) {
277            $tabs[] = [
278                'title' => 'All workspaces',
279                'itemId' => 'workspace-' . WorkspaceService::SELECT_ALL_WORKSPACES,
280                'workspaceId' => WorkspaceService::SELECT_ALL_WORKSPACES,
281                'triggerUrl' => $this->getModuleUri(WorkspaceService::SELECT_ALL_WORKSPACES),
282            ];
283        }
284
285        foreach ($workspaceList as $workspaceId => $workspaceTitle) {
286            if ($workspaceId === $activeWorkspace) {
287                continue;
288            }
289            $tabs[] = [
290                'title' => $workspaceTitle,
291                'itemId' => 'workspace-' . $workspaceId,
292                'workspaceId' => $workspaceId,
293                'triggerUrl' => $this->getModuleUri($workspaceId),
294            ];
295        }
296
297        return $tabs;
298    }
299
300    /**
301     * Gets the module URI.
302     *
303     * @param int $workspaceId
304     * @return string
305     */
306    protected function getModuleUri(int $workspaceId): string
307    {
308        $parameters = [
309            'id' => $this->pageId,
310            'workspace' => $workspaceId,
311        ];
312        // The "all workspaces" tab is handled in fullIndexAction
313        // which is required as additional GET parameter in the URI then
314        if ($workspaceId === WorkspaceService::SELECT_ALL_WORKSPACES) {
315            $this->uriBuilder->reset()->uriFor('fullIndex');
316            $parameters = array_merge($parameters, $this->uriBuilder->getArguments());
317        }
318        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
319        return (string)$uriBuilder->buildUriFromRoute('web_WorkspacesWorkspaces', $parameters);
320    }
321
322    /**
323     * Assigns additional Workspace settings to TYPO3.settings.Workspaces.extension
324     */
325    protected function assignExtensionSettings()
326    {
327        $extension = [
328            'AdditionalColumn' => [
329                'Definition' => [],
330                'Handler' => [],
331            ],
332        ];
333
334        $extension['AdditionalColumn']['Definition'] = $this->getAdditionalColumnService()->getDefinition();
335        $extension['AdditionalColumn']['Handler'] = $this->getAdditionalColumnService()->getHandler();
336        $this->pageRenderer->addInlineSetting('Workspaces', 'extension', $extension);
337    }
338
339    /**
340     * Determine whether this page for the current
341     *
342     * @param int $pageUid
343     * @param int $workspaceUid
344     * @return bool
345     */
346    protected function canCreatePreviewLink(int $pageUid, int $workspaceUid): bool
347    {
348        if ($pageUid > 0 && $workspaceUid > 0) {
349            $pageRecord = BackendUtility::getRecord('pages', $pageUid);
350            BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
351            if (VersionState::cast($pageRecord['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
352                return false;
353            }
354            return true;
355        }
356        return false;
357    }
358
359    /**
360     * Gets the selected language.
361     *
362     * @return string
363     */
364    protected function getLanguageSelection(): string
365    {
366        $language = 'all';
367        $backendUser = $this->getBackendUser();
368        if (isset($backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['language'])) {
369            $language = $backendUser->uc['moduleData']['Workspaces'][$backendUser->workspace]['language'];
370        }
371        return $language;
372    }
373
374    /**
375     * @return AdditionalColumnService
376     */
377    protected function getAdditionalColumnService(): AdditionalColumnService
378    {
379        return $this->objectManager->get(AdditionalColumnService::class);
380    }
381
382    /**
383     * @return AdditionalResourceService
384     */
385    protected function getAdditionalResourceService(): AdditionalResourceService
386    {
387        return $this->objectManager->get(AdditionalResourceService::class);
388    }
389
390    /**
391     * @return PageRenderer
392     */
393    protected function getPageRenderer(): PageRenderer
394    {
395        return GeneralUtility::makeInstance(PageRenderer::class);
396    }
397
398    /**
399     * @return LanguageService
400     */
401    protected function getLanguageService(): LanguageService
402    {
403        return $GLOBALS['LANG'];
404    }
405
406    /**
407     * @return BackendUserAuthentication
408     */
409    protected function getBackendUser(): BackendUserAuthentication
410    {
411        return $GLOBALS['BE_USER'];
412    }
413}
414