1<?php 2 3declare(strict_types=1); 4 5/* 6 * This file is part of the TYPO3 CMS project. 7 * 8 * It is free software; you can redistribute it and/or modify it under 9 * the terms of the GNU General Public License, either version 2 10 * of the License, or any later version. 11 * 12 * For the full copyright and license information, please read the 13 * LICENSE.txt file that was distributed with this source code. 14 * 15 * The TYPO3 project - inspiring people to share! 16 */ 17 18namespace TYPO3\CMS\Backend\Controller\Page; 19 20use Psr\Http\Message\ResponseInterface; 21use Psr\Http\Message\ServerRequestInterface; 22use TYPO3\CMS\Backend\Template\ModuleTemplate; 23use TYPO3\CMS\Backend\Utility\BackendUtility; 24use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; 25use TYPO3\CMS\Core\Database\ConnectionPool; 26use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; 27use TYPO3\CMS\Core\DataHandling\DataHandler; 28use TYPO3\CMS\Core\Http\HtmlResponse; 29use TYPO3\CMS\Core\Imaging\Icon; 30use TYPO3\CMS\Core\Imaging\IconFactory; 31use TYPO3\CMS\Core\Localization\LanguageService; 32use TYPO3\CMS\Core\Type\Bitmask\Permission; 33use TYPO3\CMS\Core\Utility\GeneralUtility; 34use TYPO3\CMS\Fluid\View\StandaloneView; 35 36/** 37 * "Sort sub pages" controller - reachable from context menu "more" on page records 38 * @internal This class is a specific Backend controller implementation and is not considered part of the Public TYPO3 API. 39 */ 40class SortSubPagesController 41{ 42 /** 43 * ModuleTemplate object 44 * 45 * @var ModuleTemplate 46 */ 47 protected $moduleTemplate; 48 49 /** 50 * Constructor Method 51 * 52 * @var ModuleTemplate $moduleTemplate 53 */ 54 public function __construct(ModuleTemplate $moduleTemplate = null) 55 { 56 $this->moduleTemplate = $moduleTemplate ?? GeneralUtility::makeInstance(ModuleTemplate::class); 57 } 58 59 /** 60 * Main function Handling input variables and rendering main view 61 * 62 * @param ServerRequestInterface $request 63 * @return ResponseInterface Response 64 */ 65 public function mainAction(ServerRequestInterface $request): ResponseInterface 66 { 67 $backendUser = $this->getBackendUser(); 68 $parentPageUid = (int)$request->getQueryParams()['id']; 69 70 // Show only if there is a valid page and if this page may be viewed by the user 71 $pageInformation = BackendUtility::readPageAccess($parentPageUid, $backendUser->getPagePermsClause(Permission::PAGE_SHOW)); 72 if (!is_array($pageInformation)) { 73 // User has no permission on parent page, should not happen, just render an empty page 74 $this->moduleTemplate->setContent(''); 75 return new HtmlResponse($this->moduleTemplate->renderContent()); 76 } 77 78 // Doc header handling 79 $iconFactory = GeneralUtility::makeInstance(IconFactory::class); 80 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageInformation); 81 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); 82 $cshButton = $buttonBar->makeHelpButton() 83 ->setModuleName('pages_sort') 84 ->setFieldName('pages_sort'); 85 $viewButton = $buttonBar->makeLinkButton() 86 ->setOnClick(BackendUtility::viewOnClick($parentPageUid, '', BackendUtility::BEgetRootLine($parentPageUid))) 87 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage')) 88 ->setIcon($iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL)) 89 ->setHref('#'); 90 $shortcutButton = $buttonBar->makeShortcutButton() 91 ->setModuleName('pages_sort') 92 ->setGetVariables(['id']); 93 $buttonBar->addButton($cshButton)->addButton($viewButton)->addButton($shortcutButton); 94 95 // Main view setup 96 $view = GeneralUtility::makeInstance(StandaloneView::class); 97 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName( 98 'EXT:backend/Resources/Private/Templates/Page/SortSubPages.html' 99 )); 100 101 $isInWorkspace = $backendUser->workspace !== 0; 102 $view->assign('isInWorkspace', $isInWorkspace); 103 $view->assign('maxTitleLength', $backendUser->uc['titleLen'] ?? 20); 104 $view->assign('parentPageUid', $parentPageUid); 105 $view->assign('dateFormat', $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy']); 106 $view->assign('timeFormat', $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm']); 107 108 if (!$isInWorkspace) { 109 // Apply new sorting if given 110 $newSortBy = $request->getQueryParams()['newSortBy'] ?? null; 111 if ($newSortBy && in_array($newSortBy, ['title', 'subtitle', 'nav_title', 'crdate', 'tstamp'], true)) { 112 $this->sortSubPagesByField($parentPageUid, (string)$newSortBy); 113 } elseif ($newSortBy && $newSortBy === 'reverseCurrentSorting') { 114 $this->reverseSortingOfPages($parentPageUid); 115 } 116 117 // Get sub pages, loop through them and add page/user specific permission details 118 $pageRecords = $this->getSubPagesOfPage($parentPageUid); 119 $hasInvisiblePage = false; 120 $subPages = []; 121 foreach ($pageRecords as $page) { 122 $pageWithPermissions = []; 123 $pageWithPermissions['record'] = $page; 124 $calculatedPermissions = $backendUser->calcPerms($page); 125 $pageWithPermissions['canEdit'] = $backendUser->isAdmin() || $calculatedPermissions & Permission::PAGE_EDIT; 126 $canSeePage = $backendUser->isAdmin() || $calculatedPermissions & Permission::PAGE_SHOW; 127 if ($canSeePage) { 128 $subPages[] = $pageWithPermissions; 129 } else { 130 $hasInvisiblePage = true; 131 } 132 } 133 $view->assign('subPages', $subPages); 134 $view->assign('hasInvisiblePage', $hasInvisiblePage); 135 } 136 137 $this->moduleTemplate->setContent($view->render()); 138 return new HtmlResponse($this->moduleTemplate->renderContent()); 139 } 140 141 /** 142 * Sort sub pages of given uid by field name alphabetically 143 * 144 * @param int $parentPageUid Parent page uid 145 * @param string $newSortBy Field name to sort by 146 * @throws \RuntimeException If $newSortBy does not validate 147 */ 148 protected function sortSubPagesByField(int $parentPageUid, string $newSortBy) 149 { 150 if (!in_array($newSortBy, ['title', 'subtitle', 'nav_title', 'crdate', 'tstamp'], true)) { 151 throw new \RuntimeException( 152 'New sort by must be one of "title", "subtitle", "nav_title", "crdate" or tstamp', 153 1498924810 154 ); 155 } 156 $subPages = $this->getSubPagesOfPage($parentPageUid, $newSortBy); 157 if (!empty($subPages)) { 158 $subPages = array_reverse($subPages); 159 $this->persistNewSubPageOrder($parentPageUid, $subPages); 160 } 161 } 162 163 /** 164 * Reverse current sorting of sub pages 165 * 166 * @param int $parentPageUid Parent page uid 167 */ 168 protected function reverseSortingOfPages(int $parentPageUid) 169 { 170 $subPages = $this->getSubPagesOfPage($parentPageUid); 171 if (!empty($subPages)) { 172 $this->persistNewSubPageOrder($parentPageUid, $subPages); 173 } 174 } 175 176 /** 177 * Store new sub page order 178 * 179 * @param int $parentPageUid Parent page uid 180 * @param array $subPages List of sub pages in new order 181 */ 182 protected function persistNewSubPageOrder(int $parentPageUid, array $subPages) 183 { 184 $commandArray = []; 185 foreach ($subPages as $subPage) { 186 $commandArray['pages'][$subPage['uid']]['move'] = $parentPageUid; 187 } 188 $dataHandler = GeneralUtility::makeInstance(DataHandler::class); 189 $dataHandler->start([], $commandArray); 190 $dataHandler->process_cmdmap(); 191 BackendUtility::setUpdateSignal('updatePageTree'); 192 } 193 194 /** 195 * Get a list of sub pages with some all fields from given page. 196 * Fetch all data fields for full page icon display 197 * 198 * @param int $parentPageUid Get sub pages from this pages 199 * @param string $orderBy Order pages by this field 200 * @return array 201 */ 202 protected function getSubPagesOfPage(int $parentPageUid, string $orderBy = 'sorting'): array 203 { 204 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); 205 $queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); 206 return $queryBuilder->select('*') 207 ->from('pages') 208 ->where( 209 $queryBuilder->expr()->eq('sys_language_uid', 0), 210 $queryBuilder->expr()->eq( 211 'pid', 212 $queryBuilder->createNamedParameter($parentPageUid, \PDO::PARAM_INT) 213 ) 214 ) 215 ->orderBy($orderBy) 216 ->execute() 217 ->fetchAll(); 218 } 219 220 /** 221 * Returns LanguageService 222 * 223 * @return LanguageService 224 */ 225 protected function getLanguageService() 226 { 227 return $GLOBALS['LANG']; 228 } 229 230 /** 231 * Returns current BE user 232 * 233 * @return BackendUserAuthentication 234 */ 235 protected function getBackendUser() 236 { 237 return $GLOBALS['BE_USER']; 238 } 239} 240