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