fb209173b3eee6b3669781f132efe405f048dfdc
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Backend / ToolbarItems / ShortcutToolbarItem.php
1 <?php
2 namespace TYPO3\CMS\Backend\Backend\ToolbarItems;
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
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Module\ModuleLoader;
20 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Core\Database\PreparedStatement;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Imaging\IconFactory;
25 use TYPO3\CMS\Core\Page\PageRenderer;
26 use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
27 use TYPO3\CMS\Core\Resource\ResourceFactory;
28 use TYPO3\CMS\Core\Utility\GeneralUtility;
29 use TYPO3\CMS\Core\Utility\MathUtility;
30 use TYPO3\CMS\Core\Utility\PathUtility;
31
32 /**
33 * Class to render the shortcut menu
34 */
35 class ShortcutToolbarItem implements ToolbarItemInterface
36 {
37 /**
38 * @const integer Number of super global group
39 */
40 const SUPERGLOBAL_GROUP = -100;
41
42 /**
43 * @var string
44 */
45 public $perms_clause;
46
47 /**
48 * @var array
49 */
50 public $fieldArray;
51
52 /**
53 * All available shortcuts
54 *
55 * @var array
56 */
57 protected $shortcuts;
58
59 /**
60 * @var array
61 */
62 protected $shortcutGroups;
63
64 /**
65 * Labels of all groups.
66 * If value is 1, the system will try to find a label in the locallang array.
67 *
68 * @var array
69 */
70 protected $groupLabels;
71
72 /**
73 * @var IconFactory
74 */
75 protected $iconFactory;
76
77 /**
78 * Constructor
79 */
80 public function __construct()
81 {
82 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
83 if (TYPO3_REQUESTTYPE & TYPO3_REQUESTTYPE_AJAX) {
84 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_misc.xlf');
85 // Needed to get the correct icons when reloading the menu after saving it
86 $loadModules = GeneralUtility::makeInstance(ModuleLoader::class);
87 $loadModules->load($GLOBALS['TBE_MODULES']);
88 }
89
90 // By default, 5 groups are set
91 $this->shortcutGroups = [
92 1 => '1',
93 2 => '1',
94 3 => '1',
95 4 => '1',
96 5 => '1'
97 ];
98 $this->shortcutGroups = $this->initShortcutGroups();
99 $this->shortcuts = $this->initShortcuts();
100
101 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar/ShortcutMenu');
102 $languageService = $this->getLanguageService();
103 $this->getPageRenderer()->addInlineLanguageLabelArray([
104 'bookmark.delete' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarksDelete'),
105 'bookmark.confirmDelete' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.confirmBookmarksDelete'),
106 'bookmark.create' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.createBookmark'),
107 'bookmark.savedTitle' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarkSavedTitle'),
108 'bookmark.savedMessage' => $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarkSavedMessage'),
109 ]);
110 }
111
112 /**
113 * Checks whether the user has access to this toolbar item
114 *
115 * @return bool TRUE if user has access, FALSE if not
116 */
117 public function checkAccess()
118 {
119 return (bool)$this->getBackendUser()->getTSConfigVal('options.enableBookmarks');
120 }
121
122 /**
123 * Render shortcut icon
124 *
125 * @return string HTML
126 */
127 public function getItem()
128 {
129 $title = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarks', true);
130 return '<span title="' . $title . '">' . $this->iconFactory->getIcon('apps-toolbar-menu-shortcut', Icon::SIZE_SMALL)->render('inline') . '</span>';
131 }
132
133 /**
134 * Render drop down content
135 *
136 * @return string HTML
137 */
138 public function getDropDown()
139 {
140 $languageService = $this->getLanguageService();
141 $shortcutGroup = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarksGroup', true);
142 $shortcutEdit = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarksEdit', true);
143 $shortcutDelete = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarksDelete', true);
144 $editIcon = '<a href="#" class="dropdown-list-link-edit shortcut-edit" ' . $shortcutEdit . '>'
145 . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render('inline') . '</a>';
146 $deleteIcon = '<a href="#" class="dropdown-list-link-delete shortcut-delete" title="' . $shortcutDelete . '">'
147 . $this->iconFactory->getIcon('actions-delete', Icon::SIZE_SMALL)->render('inline') . '</a>';
148
149 $shortcutMenu[] = '<ul class="dropdown-list">';
150
151 // Render shortcuts with no group (group id = 0) first
152 $noGroupShortcuts = $this->getShortcutsByGroup(0);
153 foreach ($noGroupShortcuts as $shortcut) {
154 $shortcutMenu[] = '
155 <li class="shortcut" data-shortcutid="' . (int)$shortcut['raw']['uid'] . '">
156 <a class="dropdown-list-link dropdown-link-list-add-editdelete" href="#" onclick="' . htmlspecialchars($shortcut['action']) . ' return false;">' .
157 $shortcut['icon'] . ' ' .
158 htmlspecialchars($shortcut['label']) .
159 '</a>
160 ' . $editIcon . $deleteIcon . '
161 </li>';
162 }
163 // Now render groups and the contained shortcuts
164 $groups = $this->getGroupsFromShortcuts();
165 krsort($groups, SORT_NUMERIC);
166 foreach ($groups as $groupId => $groupLabel) {
167 if ($groupId != 0) {
168 $shortcutGroup = '';
169 if (count($shortcutMenu) > 1) {
170 $shortcutGroup .= '<li class="divider"></li>';
171 }
172 $shortcutGroup .= '
173 <li class="dropdown-header" id="shortcut-group-' . (int)$groupId . '">
174 ' . $groupLabel . '
175 </li>';
176 $shortcuts = $this->getShortcutsByGroup($groupId);
177 $i = 0;
178 foreach ($shortcuts as $shortcut) {
179 $i++;
180 $shortcutGroup .= '
181 <li class="shortcut" data-shortcutid="' . (int)$shortcut['raw']['uid'] . '" data-shortcutgroup="' . (int)$groupId . '">
182 <a class="dropdown-list-link dropdown-link-list-add-editdelete" href="#" onclick="' . htmlspecialchars($shortcut['action']) . ' return false;">' .
183 $shortcut['icon'] . ' ' .
184 htmlspecialchars($shortcut['label']) .
185 '</a>
186 ' . $editIcon . $deleteIcon . '
187 </li>';
188 }
189 $shortcutMenu[] = $shortcutGroup;
190 }
191 }
192 $shortcutMenu[] = '</ul>';
193
194 if (count($shortcutMenu) === 2) {
195 // No shortcuts added yet, show a small help message how to add shortcuts
196 $title = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.bookmarks', true);
197 $icon = '<span title="' . $title . '">' . $this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render('inline') . '</span>';
198 $label = str_replace('%icon%', $icon, $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:bookmarkDescription', true));
199 $compiledShortcutMenu = '<p>' . $label . '</p>';
200 } else {
201 $compiledShortcutMenu = implode(LF, $shortcutMenu);
202 }
203
204 return $compiledShortcutMenu;
205 }
206
207 /**
208 * Renders the menu so that it can be returned as response to an AJAX call
209 *
210 * @param ServerRequestInterface $request
211 * @param ResponseInterface $response
212 * @return ResponseInterface
213 */
214 public function menuAction(ServerRequestInterface $request, ResponseInterface $response)
215 {
216 $menuContent = $this->getDropDown();
217
218 $response->getBody()->write($menuContent);
219 $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
220 return $response;
221 }
222
223 /**
224 * This toolbar item needs no additional attributes
225 *
226 * @return array
227 */
228 public function getAdditionalAttributes()
229 {
230 return [];
231 }
232
233 /**
234 * This item has a drop down
235 *
236 * @return bool
237 */
238 public function hasDropDown()
239 {
240 return true;
241 }
242
243 /**
244 * Retrieves the shortcuts for the current user
245 *
246 * @return array Array of shortcuts
247 */
248 protected function initShortcuts()
249 {
250 $databaseConnection = $this->getDatabaseConnection();
251 $globalGroupIdList = implode(',', array_keys($this->getGlobalShortcutGroups()));
252 $backendUser = $this->getBackendUser();
253 $res = $databaseConnection->exec_SELECTquery(
254 '*',
255 'sys_be_shortcuts',
256 '(userid = ' . (int)$backendUser->user['uid'] . ' AND sc_group>=0) OR sc_group IN (' . $globalGroupIdList . ')',
257 '',
258 'sc_group,sorting'
259 );
260 // Traverse shortcuts
261 $lastGroup = 0;
262 $shortcuts = [];
263 while ($row = $databaseConnection->sql_fetch_assoc($res)) {
264 $shortcut = ['raw' => $row];
265
266 list($row['module_name'], $row['M_module_name']) = explode('|', $row['module_name']);
267
268 $queryParts = parse_url($row['url']);
269 $queryParameters = GeneralUtility::explodeUrl2Array($queryParts['query'], 1);
270 if ($row['module_name'] === 'xMOD_alt_doc.php' && is_array($queryParameters['edit'])) {
271 $shortcut['table'] = key($queryParameters['edit']);
272 $shortcut['recordid'] = key($queryParameters['edit'][$shortcut['table']]);
273 if ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'edit') {
274 $shortcut['type'] = 'edit';
275 } elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] === 'new') {
276 $shortcut['type'] = 'new';
277 }
278 if (substr($shortcut['recordid'], -1) === ',') {
279 $shortcut['recordid'] = substr($shortcut['recordid'], 0, -1);
280 }
281 } else {
282 $shortcut['type'] = 'other';
283 }
284 // Check for module access
285 $moduleName = $row['M_module_name'] ?: $row['module_name'];
286 if (!isset($this->getLanguageService()->moduleLabels['tabs_images'][$moduleName . '_tab'])) {
287 // Nice hack to check if the user has access to this module
288 // - otherwise the translation label would not have been loaded :-)
289 continue;
290 }
291 if (!$backendUser->isAdmin()) {
292 $pageId = $this->getLinkedPageId($row['url']);
293 if (MathUtility::canBeInterpretedAsInteger($pageId)) {
294 // Check for webmount access
295 if (!$backendUser->isInWebMount($pageId)) {
296 continue;
297 }
298 // Check for record access
299 $pageRow = BackendUtility::getRecord('pages', $pageId);
300 if (!$backendUser->doesUserHaveAccess($pageRow, ($perms = 1))) {
301 continue;
302 }
303 }
304 }
305 $moduleParts = explode('_', $moduleName);
306 $shortcutGroup = (int)$row['sc_group'];
307 if ($shortcutGroup && $lastGroup !== $shortcutGroup && $shortcutGroup !== self::SUPERGLOBAL_GROUP) {
308 $shortcut['groupLabel'] = $this->getShortcutGroupLabel($shortcutGroup);
309 }
310 $lastGroup = $shortcutGroup;
311
312 if ($row['description']) {
313 $shortcut['label'] = $row['description'];
314 } else {
315 $shortcut['label'] = GeneralUtility::fixed_lgd_cs(rawurldecode($queryParts['query']), 150);
316 }
317 $shortcut['group'] = $shortcutGroup;
318 $shortcut['icon'] = $this->getShortcutIcon($row, $shortcut);
319 $shortcut['iconTitle'] = $this->getShortcutIconTitle($shortcut['label'], $row['module_name'], $row['M_module_name']);
320 $shortcut['action'] = 'jump(' . GeneralUtility::quoteJSvalue($this->getTokenUrl($row['url'])) . ',' . GeneralUtility::quoteJSvalue($moduleName) . ',' . GeneralUtility::quoteJSvalue($moduleParts[0]) . ', ' . (int)$pageId . ');';
321
322 $shortcuts[] = $shortcut;
323 }
324 return $shortcuts;
325 }
326
327 /**
328 * Adds the correct token, if the url is an index.php script
329 *
330 * @param string $url
331 * @return string
332 */
333 protected function getTokenUrl($url)
334 {
335 $parsedUrl = parse_url($url);
336 parse_str($parsedUrl['query'], $parameters);
337
338 // parse the returnUrl and replace the module token of it
339 if (isset($parameters['returnUrl'])) {
340 $parsedReturnUrl = parse_url($parameters['returnUrl']);
341 parse_str($parsedReturnUrl['query'], $returnUrlParameters);
342 if (strpos($parsedReturnUrl['path'], 'index.php') !== false && isset($returnUrlParameters['M'])) {
343 $module = $returnUrlParameters['M'];
344 $returnUrl = BackendUtility::getModuleUrl($module, $returnUrlParameters);
345 $parameters['returnUrl'] = $returnUrl;
346 $url = $parsedUrl['path'] . '?' . http_build_query($parameters);
347 }
348 }
349
350 if (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['M'])) {
351 $module = $parameters['M'];
352 $url = BackendUtility::getModuleUrl($module, $parameters);
353 } elseif (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['route'])) {
354 $routePath = $parameters['route'];
355 /** @var \TYPO3\CMS\Backend\Routing\Router $router */
356 $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
357 try {
358 $route = $router->match($routePath);
359 if ($route) {
360 $routeIdentifier = $route->getOption('_identifier');
361 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
362 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
363 unset($parameters['route']);
364 $url = (string)$uriBuilder->buildUriFromRoute($routeIdentifier, $parameters);
365 }
366 } catch (\TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException $e) {
367 $url = '';
368 }
369 }
370 return $url;
371 }
372
373 /**
374 * Gets shortcuts for a specific group
375 *
376 * @param int $groupId Group Id
377 * @return array Array of shortcuts that matched the group
378 */
379 protected function getShortcutsByGroup($groupId)
380 {
381 $shortcuts = [];
382 foreach ($this->shortcuts as $shortcut) {
383 if ($shortcut['group'] == $groupId) {
384 $shortcuts[] = $shortcut;
385 }
386 }
387 return $shortcuts;
388 }
389
390 /**
391 * Gets a shortcut by its uid
392 *
393 * @param int $shortcutId Shortcut id to get the complete shortcut for
394 * @return mixed An array containing the shortcut's data on success or FALSE on failure
395 */
396 protected function getShortcutById($shortcutId)
397 {
398 $returnShortcut = false;
399 foreach ($this->shortcuts as $shortcut) {
400 if ($shortcut['raw']['uid'] == (int)$shortcutId) {
401 $returnShortcut = $shortcut;
402 continue;
403 }
404 }
405 return $returnShortcut;
406 }
407
408 /**
409 * Gets the available shortcut groups from default groups, user TSConfig, and global groups
410 *
411 * @return array
412 */
413 protected function initShortcutGroups()
414 {
415 $languageService = $this->getLanguageService();
416 $backendUser = $this->getBackendUser();
417 // Groups from TSConfig
418 $bookmarkGroups = $backendUser->getTSConfigProp('options.bookmarkGroups');
419 if (is_array($bookmarkGroups) && !empty($bookmarkGroups)) {
420 foreach ($bookmarkGroups as $groupId => $label) {
421 if (!empty($label)) {
422 $this->shortcutGroups[$groupId] = (string)$label;
423 } elseif ($backendUser->isAdmin()) {
424 unset($this->shortcutGroups[$groupId]);
425 }
426 }
427 }
428 // Generate global groups, all global groups have negative IDs.
429 if (!empty($this->shortcutGroups)) {
430 $groups = $this->shortcutGroups;
431 foreach ($groups as $groupId => $groupLabel) {
432 $this->shortcutGroups[$groupId * -1] = $groupLabel;
433 }
434 }
435 // Group -100 is kind of superglobal and can't be changed.
436 $this->shortcutGroups[self::SUPERGLOBAL_GROUP] = 1;
437 // Add labels
438 foreach ($this->shortcutGroups as $groupId => $groupLabel) {
439 $groupId = (int)$groupId;
440 $label = $groupLabel;
441 if ($groupLabel == '1') {
442 $label = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:bookmark_group_' . abs($groupId), true);
443 if (empty($label)) {
444 // Fallback label
445 $label = $languageService->getLL('bookmark_group', true) . ' ' . abs($groupId);
446 }
447 }
448 if ($groupId < 0) {
449 // Global group
450 $label = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:bookmark_global', true) . ': ' . (!empty($label) ? $label : abs($groupId));
451 if ($groupId === self::SUPERGLOBAL_GROUP) {
452 $label = $languageService->getLL('bookmark_global', true) . ': ' . $languageService->getLL('bookmark_all', true);
453 }
454 }
455 $this->shortcutGroups[$groupId] = $label;
456 }
457 return $this->shortcutGroups;
458 }
459
460 /**
461 * Fetches the available shortcut groups, renders a form so it can be saved later on, usually called via AJAX
462 *
463 * @param ServerRequestInterface $request
464 * @param ResponseInterface $response
465 * @return ResponseInterface the full HTML for the form
466 */
467 public function editFormAction(ServerRequestInterface $request, ResponseInterface $response)
468 {
469 $parsedBody = $request->getParsedBody();
470 $queryParams = $request->getQueryParams();
471
472 $selectedShortcutId = (int)(isset($parsedBody['shortcutId']) ? $parsedBody['shortcutId'] : $queryParams['shortcutId']);
473 $selectedShortcutGroupId = (int)(isset($parsedBody['shortcutGroup']) ? $parsedBody['shortcutGroup'] : $queryParams['shortcutGroup']);
474 $selectedShortcut = $this->getShortcutById($selectedShortcutId);
475
476 $shortcutGroups = $this->shortcutGroups;
477 if (!$this->getBackendUser()->isAdmin()) {
478 foreach ($shortcutGroups as $groupId => $groupName) {
479 if ((int)$groupId < 0) {
480 unset($shortcutGroups[$groupId]);
481 }
482 }
483 }
484
485 // build the form
486 $content = '
487 <form class="shortcut-form" role="form">
488 <div class="form-group">
489 <input type="text" class="form-control" name="shortcut-title" value="' . htmlspecialchars($selectedShortcut['label']) . '">
490 </div>';
491
492 $content .= '
493 <div class="form-group">
494 <select class="form-control" name="shortcut-group">';
495 foreach ($shortcutGroups as $shortcutGroupId => $shortcutGroupTitle) {
496 $content .= '<option value="' . (int)$shortcutGroupId . '"' . ($selectedShortcutGroupId == $shortcutGroupId ? ' selected="selected"' : '') . '>' . htmlspecialchars($shortcutGroupTitle) . '</option>';
497 }
498 $content .= '
499 </select>
500 </div>
501 <input type="button" class="btn btn-default shortcut-form-cancel" value="Cancel">
502 <input type="button" class="btn btn-success shortcut-form-save" value="Save">
503 </form>';
504
505 $response->getBody()->write($content);
506 $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
507 return $response;
508 }
509
510 /**
511 * Deletes a shortcut through an AJAX call
512 *
513 * @param ServerRequestInterface $request
514 * @param ResponseInterface $response
515 * @return ResponseInterface
516 */
517 public function removeShortcutAction(ServerRequestInterface $request, ResponseInterface $response)
518 {
519 $parsedBody = $request->getParsedBody();
520 $queryParams = $request->getQueryParams();
521
522 $databaseConnection = $this->getDatabaseConnection();
523 $shortcutId = (int)(isset($parsedBody['shortcutId']) ? $parsedBody['shortcutId'] : $queryParams['shortcutId']);
524 $fullShortcut = $this->getShortcutById($shortcutId);
525 $success = false;
526 if ($fullShortcut['raw']['userid'] == $this->getBackendUser()->user['uid']) {
527 $databaseConnection->exec_DELETEquery('sys_be_shortcuts', 'uid = ' . $shortcutId);
528 if ($databaseConnection->sql_affected_rows() === 1) {
529 $success = true;
530 }
531 }
532 $response->getBody()->write(json_encode(['success' => $success]));
533 return $response;
534 }
535
536 /**
537 * Creates a shortcut through an AJAX call
538 *
539 * @param ServerRequestInterface $request
540 * @param ResponseInterface $response
541 * @return ResponseInterface
542 */
543 public function createShortcutAction(ServerRequestInterface $request, ResponseInterface $response)
544 {
545 $languageService = $this->getLanguageService();
546 $parsedBody = $request->getParsedBody();
547 $queryParams = $request->getQueryParams();
548
549 // Default name
550 $shortcutName = 'Shortcut';
551 $shortcutNamePrepend = '';
552 $url = isset($parsedBody['url']) ? $parsedBody['url'] : $queryParams['url'];
553
554 // Use given display name
555 if (!empty($parsedBody['displayName'])) {
556 $shortcutName = $parsedBody['displayName'];
557 }
558
559 // Determine shortcut type
560 $url = rawurldecode($url);
561 $queryParts = parse_url($url);
562 $queryParameters = GeneralUtility::explodeUrl2Array($queryParts['query'], true);
563
564 // Proceed only if no scheme is defined, as URL is expected to be relative
565 if (empty($queryParts['scheme'])) {
566 if (is_array($queryParameters['edit'])) {
567 $shortcut['table'] = key($queryParameters['edit']);
568 $shortcut['recordid'] = key($queryParameters['edit'][$shortcut['table']]);
569 $shortcut['pid'] = BackendUtility::getRecord($shortcut['table'], $shortcut['recordid'])['pid'];
570 if ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] == 'edit') {
571 $shortcut['type'] = 'edit';
572 $shortcutNamePrepend = $languageService->getLL('shortcut_edit', true);
573 } elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] == 'new') {
574 $shortcut['type'] = 'new';
575 $shortcutNamePrepend = $languageService->getLL('shortcut_create', true);
576 }
577 } else {
578 $shortcut['type'] = 'other';
579 $shortcut['table'] = '';
580 $shortcut['recordid'] = 0;
581 }
582
583 // Check if given id is a combined identifier
584 if (!empty($queryParameters['id']) && preg_match('/^[0-9]+:/', $queryParameters['id'])) {
585 try {
586 $resourceFactory = ResourceFactory::getInstance();
587 $resource = $resourceFactory->getObjectFromCombinedIdentifier($queryParameters['id']);
588 $shortcutName = trim($shortcutNamePrepend . ' ' . $resource->getName());
589 } catch (ResourceDoesNotExistException $e) {
590 }
591 } else {
592 // Lookup the title of this page and use it as default description
593 $pageId = (int)($shortcut['pid'] ?: ($shortcut['recordid'] ?: $this->getLinkedPageId($url)));
594 $page = false;
595 if ($pageId) {
596 $page = BackendUtility::getRecord('pages', $pageId);
597 }
598 if (!empty($page)) {
599 // Set the name to the title of the page
600 if ($shortcut['type'] === 'other') {
601 if (empty($shortcutName)) {
602 $shortcutName = $page['title'];
603 } else {
604 $shortcutName .= ' (' . $page['title'] . ')';
605 }
606 } else {
607 $shortcutName = $shortcutNamePrepend . ' ' .
608 $languageService->sL($GLOBALS['TCA'][$shortcut['table']]['ctrl']['title']) .
609 ' (' . $page['title'] . ')';
610 }
611 } elseif ($shortcut['table'] !== '' && $shortcut['type'] !== 'other') {
612 $shortcutName = $shortcutNamePrepend . ' ' .
613 $languageService->sL($GLOBALS['TCA'][$shortcut['table']]['ctrl']['title']);
614 }
615 }
616
617 return $this->tryAddingTheShortcut($response, $url, $shortcutName);
618 }
619 }
620
621 /**
622 * Try to adding a shortcut
623 *
624 * @param ResponseInterface $response
625 * @param string $url
626 * @param string $shortcutName
627 * @return ResponseInterface
628 */
629 protected function tryAddingTheShortcut(ResponseInterface $response, $url, $shortcutName)
630 {
631 $module = GeneralUtility::_POST('module');
632 $shortcutCreated = 'failed';
633
634 if (!empty($module) && !empty($url)) {
635 $shortcutCreated = 'alreadyExists';
636
637 if (!BackendUtility::shortcutExists($url)) {
638 $shortcutCreated = $this->addShortcut($url, $shortcutName, $module);
639 }
640 }
641
642 $response->getBody()->write($shortcutCreated);
643 $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
644 return $response;
645 }
646
647 /**
648 * Add a shortcut now with some user stuffs
649 *
650 * @param string $url
651 * @param string $shortcutName
652 * @param string $module
653 *
654 * @return string
655 */
656 protected function addShortcut($url, $shortcutName, $module)
657 {
658 // Shorts
659 $db = $this->getDatabaseConnection();
660 $lS = $this->getLanguageService();
661
662 if ($shortcutName === 'Shortcut' && !empty($lS->moduleLabels['labels'][$module . '_tablabel'])) {
663 $shortcutName = $lS->moduleLabels['labels'][$module . '_tablabel'];
664 }
665
666 $motherModule = GeneralUtility::_POST('motherModName');
667 $fieldValues = [
668 'userid' => $this->getBackendUser()->user['uid'],
669 'module_name' => $module . '|' . $motherModule,
670 'url' => $url,
671 'description' => $shortcutName,
672 'sorting' => $GLOBALS['EXEC_TIME']
673 ];
674
675 $db->exec_INSERTquery('sys_be_shortcuts', $fieldValues);
676
677 $shortcutCreated = 'failed';
678 if ($db->sql_affected_rows() === 1) {
679 $shortcutCreated = 'success';
680 }
681
682 return $shortcutCreated;
683 }
684
685 /**
686 * Exists already a shortcut entry for this TYPO3 url?
687 *
688 * @param string $url
689 *
690 * @return bool
691 */
692 protected function shortcutExists($url)
693 {
694 $statement = $this->getDatabaseConnection()->prepare_SELECTquery(
695 'uid',
696 'sys_be_shortcuts',
697 'userid = :userid AND url = :url'
698 );
699
700 $statement->bindValues([
701 ':userid' => $this->getBackendUser()->user['uid'],
702 ':url' => $url
703 ]);
704
705 $statement->execute();
706 $rows = $statement->fetch(PreparedStatement::FETCH_ASSOC);
707 $statement->free();
708
709 return !empty($rows);
710 }
711
712 /**
713 * Gets called when a shortcut is changed, checks whether the user has
714 * permissions to do so and saves the changes if everything is ok
715 *
716 * @param ServerRequestInterface $request
717 * @param ResponseInterface $response
718 * @return ResponseInterface
719 */
720 public function saveFormAction(ServerRequestInterface $request, ResponseInterface $response)
721 {
722 $parsedBody = $request->getParsedBody();
723 $queryParams = $request->getQueryParams();
724
725 $databaseConnection = $this->getDatabaseConnection();
726 $backendUser = $this->getBackendUser();
727 $shortcutId = (int)(isset($parsedBody['shortcutId']) ? $parsedBody['shortcutId'] : $queryParams['shortcutId']);
728 $shortcutName = strip_tags(isset($parsedBody['shortcutTitle']) ? $parsedBody['shortcutTitle'] : $queryParams['shortcutTitle']);
729 $shortcutGroupId = (int)(isset($parsedBody['shortcutGroup']) ? $parsedBody['shortcutGroup'] : $queryParams['shortcutGroup']);
730 // Users can only modify their own shortcuts (except admins)
731 $addUserWhere = !$backendUser->isAdmin() ? ' AND userid=' . (int)$backendUser->user['uid'] : '';
732 $fieldValues = [
733 'description' => $shortcutName,
734 'sc_group' => $shortcutGroupId
735 ];
736 if ($fieldValues['sc_group'] < 0 && !$backendUser->isAdmin()) {
737 $fieldValues['sc_group'] = 0;
738 }
739 $databaseConnection->exec_UPDATEquery('sys_be_shortcuts', 'uid=' . $shortcutId . $addUserWhere, $fieldValues);
740 $affectedRows = $databaseConnection->sql_affected_rows();
741 if ($affectedRows == 1) {
742 $response->getBody()->write($shortcutName);
743 } else {
744 $response->getBody()->write('failed');
745 }
746 return $response->withHeader('Content-Type', 'text/html; charset=utf-8');
747 }
748
749 /**
750 * Gets the label for a shortcut group
751 *
752 * @param int $groupId A shortcut group id
753 * @return string The shortcut group label, can be an empty string if no group was found for the id
754 */
755 protected function getShortcutGroupLabel($groupId)
756 {
757 return isset($this->shortcutGroups[$groupId]) ? $this->shortcutGroups[$groupId] : '';
758 }
759
760 /**
761 * Gets a list of global groups, shortcuts in these groups are available to all users
762 *
763 * @return array Array of global groups
764 */
765 protected function getGlobalShortcutGroups()
766 {
767 $globalGroups = [];
768 foreach ($this->shortcutGroups as $groupId => $groupLabel) {
769 if ($groupId < 0) {
770 $globalGroups[$groupId] = $groupLabel;
771 }
772 }
773 return $globalGroups;
774 }
775
776 /**
777 * runs through the available shortcuts an collects their groups
778 *
779 * @return array Array of groups which have shortcuts
780 */
781 protected function getGroupsFromShortcuts()
782 {
783 $groups = [];
784 foreach ($this->shortcuts as $shortcut) {
785 $groups[$shortcut['group']] = $this->shortcutGroups[$shortcut['group']];
786 }
787 return array_unique($groups);
788 }
789
790 /**
791 * Gets the icon for the shortcut
792 *
793 * @param array $row
794 * @param array $shortcut
795 * @return string Shortcut icon as img tag
796 */
797 protected function getShortcutIcon($row, $shortcut)
798 {
799 $databaseConnection = $this->getDatabaseConnection();
800 $languageService = $this->getLanguageService();
801 $titleAttribute = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.shortcut', true);
802 switch ($row['module_name']) {
803 case 'xMOD_alt_doc.php':
804 $table = $shortcut['table'];
805 $recordid = $shortcut['recordid'];
806 $icon = '';
807 if ($shortcut['type'] == 'edit') {
808 // Creating the list of fields to include in the SQL query:
809 $selectFields = $this->fieldArray;
810 $selectFields[] = 'uid';
811 $selectFields[] = 'pid';
812 if ($table == 'pages') {
813 $selectFields[] = 'module';
814 $selectFields[] = 'extendToSubpages';
815 $selectFields[] = 'doktype';
816 }
817 if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
818 $selectFields = array_merge($selectFields, $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']);
819 }
820 if ($GLOBALS['TCA'][$table]['ctrl']['type']) {
821 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['type'];
822 }
823 if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
824 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
825 }
826 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
827 $selectFields[] = 't3ver_state';
828 }
829 // Unique list!
830 $selectFields = array_unique($selectFields);
831 $permissionClause = $table === 'pages' && $this->perms_clause ? ' AND ' . $this->perms_clause : '';
832 $sqlQueryParts = [
833 'SELECT' => implode(',', $selectFields),
834 'FROM' => $table,
835 'WHERE' => 'uid IN (' . $recordid . ') ' . $permissionClause . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table)
836 ];
837 $result = $databaseConnection->exec_SELECT_queryArray($sqlQueryParts);
838 $row = $databaseConnection->sql_fetch_assoc($result);
839 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIconForRecord($table, (array)$row, Icon::SIZE_SMALL)->render() . '</span>';
840 } elseif ($shortcut['type'] == 'new') {
841 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIconForRecord($table, [], Icon::SIZE_SMALL)->render() . '</span>';
842 }
843 break;
844 case 'file_edit':
845 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIcon('mimetypes-text-html', Icon::SIZE_SMALL)->render() . '</span>';
846 break;
847 case 'wizard_rte':
848 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIcon('mimetypes-word', Icon::SIZE_SMALL)->render() . '</span>';
849 break;
850 default:
851 if ($languageService->moduleLabels['tabs_images'][$row['module_name'] . '_tab']) {
852 $icon = $languageService->moduleLabels['tabs_images'][$row['module_name'] . '_tab'];
853 // Change icon of fileadmin references - otherwise it doesn't differ with Web->List
854 $icon = str_replace('mod/file/list/list.gif', 'mod/file/file.gif', $icon);
855 if (GeneralUtility::isAbsPath($icon)) {
856 $icon = '../' . PathUtility::stripPathSitePrefix($icon);
857 }
858 // @todo: hardcoded width as we don't have a way to address module icons with an API yet.
859 $icon = '<img src="' . htmlspecialchars($icon) . '" alt="' . $titleAttribute . '" width="16">';
860 } else {
861 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
862 }
863 }
864 return $icon;
865 }
866
867 /**
868 * Returns title for the shortcut icon
869 *
870 * @param string $shortcutLabel Shortcut label
871 * @param string $moduleName Backend module name (key)
872 * @param string $parentModuleName Parent module label
873 * @return string Title for the shortcut icon
874 */
875 protected function getShortcutIconTitle($shortcutLabel, $moduleName, $parentModuleName = '')
876 {
877 $languageService = $this->getLanguageService();
878 if (substr($moduleName, 0, 5) == 'xMOD_') {
879 $title = substr($moduleName, 5);
880 } else {
881 $splitModuleName = explode('_', $moduleName);
882 $title = $languageService->moduleLabels['tabs'][$splitModuleName[0] . '_tab'];
883 if (count($splitModuleName) > 1) {
884 $title .= '>' . $languageService->moduleLabels['tabs'][$moduleName . '_tab'];
885 }
886 }
887 if ($parentModuleName) {
888 $title .= ' (' . $parentModuleName . ')';
889 }
890 $title .= ': ' . $shortcutLabel;
891 return $title;
892 }
893
894 /**
895 * Return the ID of the page in the URL if found.
896 *
897 * @param string $url The URL of the current shortcut link
898 * @return string If a page ID was found, it is returned. Otherwise: 0
899 */
900 protected function getLinkedPageId($url)
901 {
902 return preg_replace('/.*[\\?&]id=([^&]+).*/', '$1', $url);
903 }
904
905 /**
906 * Position relative to others, live search should be very right
907 *
908 * @return int
909 */
910 public function getIndex()
911 {
912 return 20;
913 }
914
915 /**
916 * Returns the current BE user.
917 *
918 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
919 */
920 protected function getBackendUser()
921 {
922 return $GLOBALS['BE_USER'];
923 }
924
925 /**
926 * Returns current PageRenderer
927 *
928 * @return PageRenderer
929 */
930 protected function getPageRenderer()
931 {
932 return GeneralUtility::makeInstance(PageRenderer::class);
933 }
934
935 /**
936 * Returns LanguageService
937 *
938 * @return \TYPO3\CMS\Lang\LanguageService
939 */
940 protected function getLanguageService()
941 {
942 return $GLOBALS['LANG'];
943 }
944
945 /**
946 * Return DatabaseConnection
947 *
948 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
949 */
950 protected function getDatabaseConnection()
951 {
952 return $GLOBALS['TYPO3_DB'];
953 }
954 }