‪TYPO3CMS  ‪main
ShortcutRepository.php
Go to the documentation of this file.
1 <?php
2 
3 declare(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 
19 
23 use TYPO3\CMS\Backend\Utility\BackendUtility;
28 use TYPO3\CMS\Core\Imaging\IconSize;
35 
42 {
46  protected const ‪SUPERGLOBAL_GROUP = -100;
47 
48  protected const ‪TABLE_NAME = 'sys_be_shortcuts';
49 
50  protected array ‪$shortcuts;
51 
52  protected array ‪$shortcutGroups;
53 
54  public function ‪__construct(
55  protected readonly ‪ConnectionPool $connectionPool,
56  protected readonly ‪IconFactory $iconFactory,
57  protected readonly ‪ModuleProvider $moduleProvider,
58  protected readonly ‪Router $router,
59  protected readonly ‪UriBuilder $uriBuilder,
60  ) {
61  $this->shortcutGroups = $this->‪initShortcutGroups();
62  $this->shortcuts = $this->‪initShortcuts();
63  }
64 
71  public function ‪getShortcutById(int $shortcutId)
72  {
73  foreach ($this->shortcuts as $shortcut) {
74  if ($shortcut['raw']['uid'] === $shortcutId) {
75  return $shortcut;
76  }
77  }
78 
79  return false;
80  }
81 
88  public function ‪getShortcutsByGroup(int $groupId): array
89  {
90  ‪$shortcuts = [];
91 
92  foreach ($this->shortcuts as $shortcut) {
93  if ($shortcut['group'] === $groupId) {
94  ‪$shortcuts[] = $shortcut;
95  }
96  }
97 
98  return ‪$shortcuts;
99  }
100 
104  public function ‪getShortcutGroups(): array
105  {
107 
108  if (!$this->‪getBackendUser()->isAdmin()) {
109  foreach (‪$shortcutGroups as $groupId => $groupName) {
110  if ((int)$groupId < 0) {
111  unset(‪$shortcutGroups[$groupId]);
112  }
113  }
114  }
115 
116  return ‪$shortcutGroups;
117  }
118 
124  public function ‪getGroupsFromShortcuts(): array
125  {
126  $groups = [];
127 
128  foreach ($this->shortcuts as $shortcut) {
129  $groups[$shortcut['group']] = $this->shortcutGroups[$shortcut['group']] ?? '';
130  }
131 
132  return array_unique($groups);
133  }
134 
138  public function ‪shortcutExists(string $routeIdentifier, string $arguments): bool
139  {
140  $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
141  $queryBuilder->getRestrictions()->removeAll();
142 
143  ‪$uid = $queryBuilder->select('uid')
144  ->from(self::TABLE_NAME)
145  ->where(
146  $queryBuilder->expr()->eq(
147  'userid',
148  $queryBuilder->createNamedParameter($this->getBackendUser()->user['uid'], ‪Connection::PARAM_INT)
149  ),
150  $queryBuilder->expr()->eq('route', $queryBuilder->createNamedParameter($routeIdentifier)),
151  $queryBuilder->expr()->eq('arguments', $queryBuilder->createNamedParameter($arguments))
152  )
153  ->executeQuery()
154  ->fetchOne();
155 
156  return (bool)‪$uid;
157  }
158 
167  public function ‪addShortcut(string $routeIdentifier, string $arguments = '', string $title = ''): bool
168  {
169  // Do not add shortcuts for routes which do not exist
170  if (!$this->router->hasRoute($routeIdentifier)) {
171  return false;
172  }
173 
174  $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
175  $affectedRows = $queryBuilder
176  ->insert(self::TABLE_NAME)
177  ->values([
178  'userid' => $this->‪getBackendUser()->user['uid'],
179  'route' => $routeIdentifier,
180  'arguments' => $arguments,
181  'description' => $title ?: 'Shortcut', // Fall back to "Shortcut", see: initShortcuts()
182  'sorting' => ‪$GLOBALS['EXEC_TIME'],
183  ])
184  ->executeStatement();
185 
186  return $affectedRows === 1;
187  }
188 
196  public function ‪updateShortcut(int $id, string $title, int $groupId): bool
197  {
198  $backendUser = $this->‪getBackendUser();
199  $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
200  $queryBuilder->update(self::TABLE_NAME)
201  ->where(
202  $queryBuilder->expr()->eq(
203  'uid',
204  $queryBuilder->createNamedParameter($id, ‪Connection::PARAM_INT)
205  )
206  )
207  ->set('description', $title)
208  ->set('sc_group', $groupId);
209 
210  if (!$backendUser->isAdmin()) {
211  // Users can only modify their own shortcuts
212  $queryBuilder->andWhere(
213  $queryBuilder->expr()->eq(
214  'userid',
215  $queryBuilder->createNamedParameter($backendUser->user['uid'], ‪Connection::PARAM_INT)
216  )
217  );
218 
219  if ($groupId < 0) {
220  $queryBuilder->set('sc_group', 0);
221  }
222  }
223 
224  $affectedRows = $queryBuilder->executeStatement();
225 
226  return $affectedRows === 1;
227  }
228 
234  public function ‪removeShortcut(int $id): bool
235  {
236  $shortcut = $this->‪getShortcutById($id);
237  $success = false;
238 
239  if ((int)$shortcut['raw']['userid'] === (int)$this->‪getBackendUser()->user['uid']) {
240  $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
241  $affectedRows = $queryBuilder->delete(self::TABLE_NAME)
242  ->where(
243  $queryBuilder->expr()->eq(
244  'uid',
245  $queryBuilder->createNamedParameter($id, ‪Connection::PARAM_INT)
246  )
247  )
248  ->executeStatement();
249 
250  if ($affectedRows === 1) {
251  $success = true;
252  }
253  }
254 
255  return $success;
256  }
257 
261  protected function ‪initShortcutGroups(): array
262  {
263  $languageService = $this->‪getLanguageService();
264  $backendUser = $this->‪getBackendUser();
265  // By default, 5 groups are set
266  $shortcutGroups = [
267  1 => '1',
268  2 => '1',
269  3 => '1',
270  4 => '1',
271  5 => '1',
272  ];
273 
274  // Groups from TSConfig
275  $bookmarkGroups = $backendUser->getTSConfig()['options.']['bookmarkGroups.'] ?? [];
276 
277  if (is_array($bookmarkGroups)) {
278  foreach ($bookmarkGroups as $groupId => $label) {
279  if (!empty($label)) {
280  $label = (string)$label;
281  ‪$shortcutGroups[$groupId] = $languageService->sL($label);
282  } elseif ($backendUser->isAdmin()) {
283  unset(‪$shortcutGroups[$groupId]);
284  }
285  }
286  }
287 
288  // Generate global groups, all global groups have negative IDs.
289  if (!empty(‪$shortcutGroups)) {
290  foreach (‪$shortcutGroups as $groupId => $groupLabel) {
291  ‪$shortcutGroups[$groupId * -1] = $groupLabel;
292  }
293  }
294 
295  // Group -100 is kind of superglobal and can't be changed.
297 
298  // Add labels
299  $languageFile = 'LLL:EXT:core/Resources/Private/Language/locallang_misc.xlf';
300 
301  foreach (‪$shortcutGroups as $groupId => $groupLabel) {
302  $groupId = (int)$groupId;
303  $label = $groupLabel;
304 
305  if ($groupLabel === '1') {
306  $label = $languageService->sL($languageFile . ':bookmark_group_' . abs($groupId));
307 
308  if (empty($label)) {
309  // Fallback label
310  $label = $languageService->sL($languageFile . ':bookmark_group') . ' ' . abs($groupId);
311  }
312  }
313 
314  if ($groupId < 0) {
315  // Global group
316  $label = $languageService->sL($languageFile . ':bookmark_global') . ': ' . (!empty($label) ? $label : abs($groupId));
317 
318  if ($groupId === self::SUPERGLOBAL_GROUP) {
319  $label = $languageService->sL($languageFile . ':bookmark_global') . ': ' . $languageService->sL($languageFile . ':bookmark_all');
320  }
321  }
322 
323  ‪$shortcutGroups[$groupId] = htmlspecialchars($label);
324  }
325 
326  return ‪$shortcutGroups;
327  }
328 
334  protected function ‪initShortcuts(): array
335  {
336  $backendUser = $this->‪getBackendUser();
337  $lastGroup = 0;
338  ‪$shortcuts = [];
339 
340  $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLE_NAME);
341  $result = $queryBuilder->select('*')
342  ->from(self::TABLE_NAME)
343  ->where(
344  $queryBuilder->expr()->and(
345  $queryBuilder->expr()->eq(
346  'userid',
347  $queryBuilder->createNamedParameter($backendUser->user['uid'], ‪Connection::PARAM_INT)
348  ),
349  $queryBuilder->expr()->gte(
350  'sc_group',
351  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
352  )
353  )
354  )
355  ->orWhere(
356  $queryBuilder->expr()->in(
357  'sc_group',
358  $queryBuilder->createNamedParameter(
359  array_keys($this->‪getGlobalShortcutGroups()),
361  )
362  )
363  )
364  ->orderBy('sc_group')
365  ->addOrderBy('sorting')
366  ->executeQuery();
367 
368  while ($row = $result->fetchAssociative()) {
369  $pageId = 0;
370  $shortcut = ['raw' => $row];
371  $routeIdentifier = $row['route'] ?? '';
372  $arguments = json_decode($row['arguments'] ?? '', true) ?? [];
373 
374  if ($routeIdentifier === 'record_edit' && is_array($arguments['edit'])) {
375  $shortcut['table'] = (string)(key($arguments['edit']) ?? '');
376  $shortcut['recordid'] = key($arguments['edit'][$shortcut['table']]);
377 
378  if ($arguments['edit'][$shortcut['table']][$shortcut['recordid']] === 'edit') {
379  $shortcut['type'] = 'edit';
380  } elseif ($arguments['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') {
381  $shortcut['type'] = 'new';
382  }
383 
384  if (str_ends_with((string)$shortcut['recordid'], ',')) {
385  $shortcut['recordid'] = substr((string)$shortcut['recordid'], 0, -1);
386  }
387  } else {
388  $shortcut['type'] = 'other';
389  }
390 
391  $moduleName = $this->‪getModuleNameFromRouteIdentifier($routeIdentifier);
392 
393  // Skip shortcut if module name can not be resolved
394  if ($moduleName === '') {
395  continue;
396  }
397 
398  // Check if the user has access to this module
399  // @todo Hack for EditDocumentController / FormEngine, see issues #91368 and #91210
400  if ($routeIdentifier !== 'record_edit' && !$this->moduleProvider->accessGranted($moduleName, $backendUser)) {
401  continue;
402  }
403 
404  if ($moduleName === 'file_FilelistList' || $moduleName === 'media_management') {
405  $combinedIdentifier = (string)($arguments['id'] ?? '');
406  if ($combinedIdentifier !== '') {
407  $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByCombinedIdentifier($combinedIdentifier);
408  if ($storage === null || $storage->isFallbackStorage()) {
409  // Continue, if invalid storage or disallowed fallback storage
410  continue;
411  }
412  $folderIdentifier = substr($combinedIdentifier, strpos($combinedIdentifier, ':') + 1);
413  try {
414  // By using $storage->getFolder() we implicitly check whether the folder
415  // still exists and the user has necessary permissions to access it.
416  $storage->getFolder($folderIdentifier);
418  // Continue, since current user does not have access to the folder
419  continue;
420  } catch (‪FolderDoesNotExistException $e) {
421  // Folder does not longer exists. However, the shortcut
422  // is still displayed, allowing the user to remove it.
423  }
424  }
425  } else {
426  if ($moduleName === 'record_edit' && isset($shortcut['table'], $shortcut['recordid'])) {
427  // Check if user is allowed to modify the requested record
428  if (!$backendUser->check('tables_modify', $shortcut['table'])) {
429  continue;
430  }
431  if ($shortcut['type'] === 'edit'
432  || ($shortcut['type'] === 'new' && (int)$shortcut['recordid'] < 0)
433  ) {
434  ‪$record = BackendUtility::getRecord($shortcut['table'], abs((int)$shortcut['recordid']));
435  // Check if requested record exists
436  if (‪$record === null || ‪$record === []) {
437  continue;
438  }
439  // Store the page id of the record in question
440  $pageId = ($shortcut['table'] === 'pages' ? (int)(‪$record['uid'] ?? 0) : (int)(‪$record['pid'] ?? 0));
441  } elseif ($shortcut['type'] === 'new' && (int)$shortcut['recordid'] > 0) {
442  // If type is new and "recordid" is positive, it references the current page
443  $pageId = (int)$shortcut['recordid'];
444  }
445  } else {
446  // In case this is no record edit shortcut, treat a possible "id" as page id
447  $pageId = (int)($arguments['id'] ?? 0);
448  }
449  if ($pageId > 0 && !$backendUser->isAdmin()) {
450  // Check for webmount access
451  if ($backendUser->isInWebMount($pageId) === null) {
452  continue;
453  }
454  // Check for record access
455  $pageRow = BackendUtility::getRecord('pages', $pageId);
456  if ($pageRow === null || !$backendUser->doesUserHaveAccess($pageRow, ‪Permission::PAGE_SHOW)) {
457  continue;
458  }
459  }
460  }
461 
462  $shortcutGroup = (int)$row['sc_group'];
463  if ($shortcutGroup && $lastGroup !== $shortcutGroup && $shortcutGroup !== self::SUPERGLOBAL_GROUP) {
464  $shortcut['groupLabel'] = $this->‪getShortcutGroupLabel($shortcutGroup);
465  }
466  $lastGroup = $shortcutGroup;
467 
468  $shortcut['group'] = $shortcutGroup;
469  $shortcut['icon'] = $this->‪getShortcutIcon($routeIdentifier, $moduleName, $shortcut);
470  $shortcut['label'] = ($row['description'] ?? false) ?: 'Shortcut'; // Fall back to "Shortcut", see: addShortcut()
471  $shortcut['href'] = (string)$this->uriBuilder->buildUriFromRoute($routeIdentifier, $arguments);
472  $shortcut['route'] = $routeIdentifier;
473  $shortcut['module'] = $moduleName;
474  $shortcut['pageId'] = $pageId;
475  ‪$shortcuts[] = $shortcut;
476  }
477 
478  return ‪$shortcuts;
479  }
480 
486  protected function ‪getGlobalShortcutGroups(): array
487  {
488  $globalGroups = [];
489 
490  foreach ($this->shortcutGroups as $groupId => $groupLabel) {
491  if ($groupId < 0) {
492  $globalGroups[$groupId] = $groupLabel;
493  }
494  }
495 
496  return $globalGroups;
497  }
498 
505  protected function ‪getShortcutGroupLabel(int $groupId): string
506  {
507  return $this->shortcutGroups[$groupId] ?? '';
508  }
509 
515  protected function ‪getShortcutIcon(string $routeIdentifier, string $moduleName, array $shortcut): string
516  {
517  switch ($routeIdentifier) {
518  case 'record_edit':
519  $table = $shortcut['table'];
520  $recordid = $shortcut['recordid'];
521  $icon = '';
522 
523  if ($shortcut['type'] === 'edit') {
524  $row = BackendUtility::getRecordWSOL($table, $recordid) ?? [];
525  $icon = $this->iconFactory->getIconForRecord($table, $row, IconSize::SMALL)->render();
526  } elseif ($shortcut['type'] === 'new') {
527  $icon = $this->iconFactory->getIconForRecord($table, [], IconSize::SMALL)->render();
528  }
529  break;
530  case 'file_edit':
531  $icon = $this->iconFactory->getIcon('mimetypes-text-html', IconSize::SMALL)->render();
532  break;
533  default:
534  $iconIdentifier = '';
535  if ($module = $this->moduleProvider->getModule($moduleName, null, false)) {
536  $iconIdentifier = $module->getIconIdentifier();
537  }
538  if ($iconIdentifier === '') {
539  $iconIdentifier = 'empty-empty';
540  }
541  $icon = $this->iconFactory->getIcon($iconIdentifier, IconSize::SMALL)->render();
542  }
543 
544  return $icon;
545  }
546 
550  protected function ‪getModuleNameFromRouteIdentifier(string $routeIdentifier): string
551  {
552  if ($this->‪isSpecialRoute($routeIdentifier)) {
553  return $routeIdentifier;
554  }
555 
556  return (string)($this->router->getRoute($routeIdentifier)?->getOption('module')?->getIdentifier() ?? '');
557  }
558 
562  protected function ‪isSpecialRoute(string $routeIdentifier): bool
563  {
564  return in_array($routeIdentifier, ['record_edit', 'file_edit'], true);
565  }
566 
568  {
569  return ‪$GLOBALS['BE_USER'];
570  }
571 
573  {
574  return ‪$GLOBALS['LANG'];
575  }
576 }
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository
Definition: ShortcutRepository.php:42
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\initShortcutGroups
‪initShortcutGroups()
Definition: ShortcutRepository.php:261
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getShortcutIcon
‪string getShortcutIcon(string $routeIdentifier, string $moduleName, array $shortcut)
Definition: ShortcutRepository.php:515
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:52
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\initShortcuts
‪array initShortcuts()
Definition: ShortcutRepository.php:334
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\shortcutExists
‪shortcutExists(string $routeIdentifier, string $arguments)
Definition: ShortcutRepository.php:138
‪TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException
Definition: InsufficientFolderAccessPermissionsException.php:23
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\$shortcuts
‪array $shortcuts
Definition: ShortcutRepository.php:50
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\isSpecialRoute
‪isSpecialRoute(string $routeIdentifier)
Definition: ShortcutRepository.php:562
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getLanguageService
‪getLanguageService()
Definition: ShortcutRepository.php:572
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Backend\Backend\Shortcut
Definition: ShortcutRepository.php:18
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getShortcutGroups
‪getShortcutGroups()
Definition: ShortcutRepository.php:104
‪TYPO3\CMS\Backend\Module\ModuleProvider
Definition: ModuleProvider.php:29
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\removeShortcut
‪removeShortcut(int $id)
Definition: ShortcutRepository.php:234
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getGlobalShortcutGroups
‪array getGlobalShortcutGroups()
Definition: ShortcutRepository.php:486
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\addShortcut
‪addShortcut(string $routeIdentifier, string $arguments='', string $title='')
Definition: ShortcutRepository.php:167
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getShortcutsByGroup
‪array getShortcutsByGroup(int $groupId)
Definition: ShortcutRepository.php:88
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getModuleNameFromRouteIdentifier
‪getModuleNameFromRouteIdentifier(string $routeIdentifier)
Definition: ShortcutRepository.php:550
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\__construct
‪__construct(protected readonly ConnectionPool $connectionPool, protected readonly IconFactory $iconFactory, protected readonly ModuleProvider $moduleProvider, protected readonly Router $router, protected readonly UriBuilder $uriBuilder,)
Definition: ShortcutRepository.php:54
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\updateShortcut
‪updateShortcut(int $id, string $title, int $groupId)
Definition: ShortcutRepository.php:196
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException
Definition: FolderDoesNotExistException.php:21
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getShortcutById
‪mixed getShortcutById(int $shortcutId)
Definition: ShortcutRepository.php:71
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getGroupsFromShortcuts
‪array getGroupsFromShortcuts()
Definition: ShortcutRepository.php:124
‪TYPO3\CMS\Core\Resource\StorageRepository
Definition: StorageRepository.php:38
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:41
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\SUPERGLOBAL_GROUP
‪const SUPERGLOBAL_GROUP
Definition: ShortcutRepository.php:46
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getShortcutGroupLabel
‪string getShortcutGroupLabel(int $groupId)
Definition: ShortcutRepository.php:505
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\getBackendUser
‪getBackendUser()
Definition: ShortcutRepository.php:567
‪TYPO3\CMS\Backend\Routing\Router
Definition: Router.php:40
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT_ARRAY
‪const PARAM_INT_ARRAY
Definition: Connection.php:72
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\$shortcutGroups
‪array $shortcutGroups
Definition: ShortcutRepository.php:52
‪TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository\TABLE_NAME
‪const TABLE_NAME
Definition: ShortcutRepository.php:48