[BUGFIX] Allow shortcuts to edit module
[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 && $moduleName !== 'xMOD_alt_doc.php'
288 ) {
289 // Nice hack to check if the user has access to this module
290 // - otherwise the translation label would not have been loaded :-)
291 continue;
292 }
293 if (!$backendUser->isAdmin()) {
294 $pageId = $this->getLinkedPageId($row['url']);
295 if (MathUtility::canBeInterpretedAsInteger($pageId)) {
296 // Check for webmount access
297 if (!$backendUser->isInWebMount($pageId)) {
298 continue;
299 }
300 // Check for record access
301 $pageRow = BackendUtility::getRecord('pages', $pageId);
302 if (!$backendUser->doesUserHaveAccess($pageRow, ($perms = 1))) {
303 continue;
304 }
305 }
306 }
307 $moduleParts = explode('_', $moduleName);
308 $shortcutGroup = (int)$row['sc_group'];
309 if ($shortcutGroup && $lastGroup !== $shortcutGroup && $shortcutGroup !== self::SUPERGLOBAL_GROUP) {
310 $shortcut['groupLabel'] = $this->getShortcutGroupLabel($shortcutGroup);
311 }
312 $lastGroup = $shortcutGroup;
313
314 if ($row['description']) {
315 $shortcut['label'] = $row['description'];
316 } else {
317 $shortcut['label'] = GeneralUtility::fixed_lgd_cs(rawurldecode($queryParts['query']), 150);
318 }
319 $shortcut['group'] = $shortcutGroup;
320 $shortcut['icon'] = $this->getShortcutIcon($row, $shortcut);
321 $shortcut['iconTitle'] = $this->getShortcutIconTitle($shortcut['label'], $row['module_name'], $row['M_module_name']);
322 $shortcut['action'] = 'jump(' . GeneralUtility::quoteJSvalue($this->getTokenUrl($row['url'])) . ',' . GeneralUtility::quoteJSvalue($moduleName) . ',' . GeneralUtility::quoteJSvalue($moduleParts[0]) . ', ' . (int)$pageId . ');';
323
324 $shortcuts[] = $shortcut;
325 }
326 return $shortcuts;
327 }
328
329 /**
330 * Adds the correct token, if the url is an index.php script
331 *
332 * @param string $url
333 * @return string
334 */
335 protected function getTokenUrl($url)
336 {
337 $parsedUrl = parse_url($url);
338 parse_str($parsedUrl['query'], $parameters);
339
340 // parse the returnUrl and replace the module token of it
341 if (isset($parameters['returnUrl'])) {
342 $parsedReturnUrl = parse_url($parameters['returnUrl']);
343 parse_str($parsedReturnUrl['query'], $returnUrlParameters);
344 if (strpos($parsedReturnUrl['path'], 'index.php') !== false && isset($returnUrlParameters['M'])) {
345 $module = $returnUrlParameters['M'];
346 $returnUrl = BackendUtility::getModuleUrl($module, $returnUrlParameters);
347 $parameters['returnUrl'] = $returnUrl;
348 $url = $parsedUrl['path'] . '?' . http_build_query($parameters);
349 }
350 }
351
352 if (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['M'])) {
353 $module = $parameters['M'];
354 $url = BackendUtility::getModuleUrl($module, $parameters);
355 } elseif (strpos($parsedUrl['path'], 'index.php') !== false && isset($parameters['route'])) {
356 $routePath = $parameters['route'];
357 /** @var \TYPO3\CMS\Backend\Routing\Router $router */
358 $router = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\Router::class);
359 try {
360 $route = $router->match($routePath);
361 if ($route) {
362 $routeIdentifier = $route->getOption('_identifier');
363 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
364 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
365 unset($parameters['route']);
366 $url = (string)$uriBuilder->buildUriFromRoute($routeIdentifier, $parameters);
367 }
368 } catch (\TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException $e) {
369 $url = '';
370 }
371 }
372 return $url;
373 }
374
375 /**
376 * Gets shortcuts for a specific group
377 *
378 * @param int $groupId Group Id
379 * @return array Array of shortcuts that matched the group
380 */
381 protected function getShortcutsByGroup($groupId)
382 {
383 $shortcuts = [];
384 foreach ($this->shortcuts as $shortcut) {
385 if ($shortcut['group'] == $groupId) {
386 $shortcuts[] = $shortcut;
387 }
388 }
389 return $shortcuts;
390 }
391
392 /**
393 * Gets a shortcut by its uid
394 *
395 * @param int $shortcutId Shortcut id to get the complete shortcut for
396 * @return mixed An array containing the shortcut's data on success or FALSE on failure
397 */
398 protected function getShortcutById($shortcutId)
399 {
400 $returnShortcut = false;
401 foreach ($this->shortcuts as $shortcut) {
402 if ($shortcut['raw']['uid'] == (int)$shortcutId) {
403 $returnShortcut = $shortcut;
404 continue;
405 }
406 }
407 return $returnShortcut;
408 }
409
410 /**
411 * Gets the available shortcut groups from default groups, user TSConfig, and global groups
412 *
413 * @return array
414 */
415 protected function initShortcutGroups()
416 {
417 $languageService = $this->getLanguageService();
418 $backendUser = $this->getBackendUser();
419 // Groups from TSConfig
420 $bookmarkGroups = $backendUser->getTSConfigProp('options.bookmarkGroups');
421 if (is_array($bookmarkGroups) && !empty($bookmarkGroups)) {
422 foreach ($bookmarkGroups as $groupId => $label) {
423 if (!empty($label)) {
424 $this->shortcutGroups[$groupId] = (string)$label;
425 } elseif ($backendUser->isAdmin()) {
426 unset($this->shortcutGroups[$groupId]);
427 }
428 }
429 }
430 // Generate global groups, all global groups have negative IDs.
431 if (!empty($this->shortcutGroups)) {
432 $groups = $this->shortcutGroups;
433 foreach ($groups as $groupId => $groupLabel) {
434 $this->shortcutGroups[$groupId * -1] = $groupLabel;
435 }
436 }
437 // Group -100 is kind of superglobal and can't be changed.
438 $this->shortcutGroups[self::SUPERGLOBAL_GROUP] = 1;
439 // Add labels
440 foreach ($this->shortcutGroups as $groupId => $groupLabel) {
441 $groupId = (int)$groupId;
442 $label = $groupLabel;
443 if ($groupLabel == '1') {
444 $label = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:bookmark_group_' . abs($groupId), true);
445 if (empty($label)) {
446 // Fallback label
447 $label = $languageService->getLL('bookmark_group', true) . ' ' . abs($groupId);
448 }
449 }
450 if ($groupId < 0) {
451 // Global group
452 $label = $languageService->sL('LLL:EXT:lang/locallang_misc.xlf:bookmark_global', true) . ': ' . (!empty($label) ? $label : abs($groupId));
453 if ($groupId === self::SUPERGLOBAL_GROUP) {
454 $label = $languageService->getLL('bookmark_global', true) . ': ' . $languageService->getLL('bookmark_all', true);
455 }
456 }
457 $this->shortcutGroups[$groupId] = $label;
458 }
459 return $this->shortcutGroups;
460 }
461
462 /**
463 * Fetches the available shortcut groups, renders a form so it can be saved later on, usually called via AJAX
464 *
465 * @param ServerRequestInterface $request
466 * @param ResponseInterface $response
467 * @return ResponseInterface the full HTML for the form
468 */
469 public function editFormAction(ServerRequestInterface $request, ResponseInterface $response)
470 {
471 $parsedBody = $request->getParsedBody();
472 $queryParams = $request->getQueryParams();
473
474 $selectedShortcutId = (int)(isset($parsedBody['shortcutId']) ? $parsedBody['shortcutId'] : $queryParams['shortcutId']);
475 $selectedShortcutGroupId = (int)(isset($parsedBody['shortcutGroup']) ? $parsedBody['shortcutGroup'] : $queryParams['shortcutGroup']);
476 $selectedShortcut = $this->getShortcutById($selectedShortcutId);
477
478 $shortcutGroups = $this->shortcutGroups;
479 if (!$this->getBackendUser()->isAdmin()) {
480 foreach ($shortcutGroups as $groupId => $groupName) {
481 if ((int)$groupId < 0) {
482 unset($shortcutGroups[$groupId]);
483 }
484 }
485 }
486
487 // build the form
488 $content = '
489 <form class="shortcut-form" role="form">
490 <div class="form-group">
491 <input type="text" class="form-control" name="shortcut-title" value="' . htmlspecialchars($selectedShortcut['label']) . '">
492 </div>';
493
494 $content .= '
495 <div class="form-group">
496 <select class="form-control" name="shortcut-group">';
497 foreach ($shortcutGroups as $shortcutGroupId => $shortcutGroupTitle) {
498 $content .= '<option value="' . (int)$shortcutGroupId . '"' . ($selectedShortcutGroupId == $shortcutGroupId ? ' selected="selected"' : '') . '>' . htmlspecialchars($shortcutGroupTitle) . '</option>';
499 }
500 $content .= '
501 </select>
502 </div>
503 <input type="button" class="btn btn-default shortcut-form-cancel" value="Cancel">
504 <input type="button" class="btn btn-success shortcut-form-save" value="Save">
505 </form>';
506
507 $response->getBody()->write($content);
508 $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
509 return $response;
510 }
511
512 /**
513 * Deletes a shortcut through an AJAX call
514 *
515 * @param ServerRequestInterface $request
516 * @param ResponseInterface $response
517 * @return ResponseInterface
518 */
519 public function removeShortcutAction(ServerRequestInterface $request, ResponseInterface $response)
520 {
521 $parsedBody = $request->getParsedBody();
522 $queryParams = $request->getQueryParams();
523
524 $databaseConnection = $this->getDatabaseConnection();
525 $shortcutId = (int)(isset($parsedBody['shortcutId']) ? $parsedBody['shortcutId'] : $queryParams['shortcutId']);
526 $fullShortcut = $this->getShortcutById($shortcutId);
527 $success = false;
528 if ($fullShortcut['raw']['userid'] == $this->getBackendUser()->user['uid']) {
529 $databaseConnection->exec_DELETEquery('sys_be_shortcuts', 'uid = ' . $shortcutId);
530 if ($databaseConnection->sql_affected_rows() === 1) {
531 $success = true;
532 }
533 }
534 $response->getBody()->write(json_encode(['success' => $success]));
535 return $response;
536 }
537
538 /**
539 * Creates a shortcut through an AJAX call
540 *
541 * @param ServerRequestInterface $request
542 * @param ResponseInterface $response
543 * @return ResponseInterface
544 */
545 public function createShortcutAction(ServerRequestInterface $request, ResponseInterface $response)
546 {
547 $languageService = $this->getLanguageService();
548 $parsedBody = $request->getParsedBody();
549 $queryParams = $request->getQueryParams();
550
551 // Default name
552 $shortcutName = 'Shortcut';
553 $shortcutNamePrepend = '';
554 $url = isset($parsedBody['url']) ? $parsedBody['url'] : $queryParams['url'];
555
556 // Use given display name
557 if (!empty($parsedBody['displayName'])) {
558 $shortcutName = $parsedBody['displayName'];
559 }
560
561 // Determine shortcut type
562 $url = rawurldecode($url);
563 $queryParts = parse_url($url);
564 $queryParameters = GeneralUtility::explodeUrl2Array($queryParts['query'], true);
565
566 // Proceed only if no scheme is defined, as URL is expected to be relative
567 if (empty($queryParts['scheme'])) {
568 if (is_array($queryParameters['edit'])) {
569 $shortcut['table'] = key($queryParameters['edit']);
570 $shortcut['recordid'] = key($queryParameters['edit'][$shortcut['table']]);
571 $shortcut['pid'] = BackendUtility::getRecord($shortcut['table'], $shortcut['recordid'])['pid'];
572 if ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] == 'edit') {
573 $shortcut['type'] = 'edit';
574 $shortcutNamePrepend = $languageService->getLL('shortcut_edit', true);
575 } elseif ($queryParameters['edit'][$shortcut['table']][$shortcut['recordid']] == 'new') {
576 $shortcut['type'] = 'new';
577 $shortcutNamePrepend = $languageService->getLL('shortcut_create', true);
578 }
579 } else {
580 $shortcut['type'] = 'other';
581 $shortcut['table'] = '';
582 $shortcut['recordid'] = 0;
583 }
584
585 // Check if given id is a combined identifier
586 if (!empty($queryParameters['id']) && preg_match('/^[0-9]+:/', $queryParameters['id'])) {
587 try {
588 $resourceFactory = ResourceFactory::getInstance();
589 $resource = $resourceFactory->getObjectFromCombinedIdentifier($queryParameters['id']);
590 $shortcutName = trim($shortcutNamePrepend . ' ' . $resource->getName());
591 } catch (ResourceDoesNotExistException $e) {
592 }
593 } else {
594 // Lookup the title of this page and use it as default description
595 $pageId = (int)($shortcut['pid'] ?: ($shortcut['recordid'] ?: $this->getLinkedPageId($url)));
596 $page = false;
597 if ($pageId) {
598 $page = BackendUtility::getRecord('pages', $pageId);
599 }
600 if (!empty($page)) {
601 // Set the name to the title of the page
602 if ($shortcut['type'] === 'other') {
603 if (empty($shortcutName)) {
604 $shortcutName = $page['title'];
605 } else {
606 $shortcutName .= ' (' . $page['title'] . ')';
607 }
608 } else {
609 $shortcutName = $shortcutNamePrepend . ' ' .
610 $languageService->sL($GLOBALS['TCA'][$shortcut['table']]['ctrl']['title']) .
611 ' (' . $page['title'] . ')';
612 }
613 } elseif ($shortcut['table'] !== '' && $shortcut['type'] !== 'other') {
614 $shortcutName = $shortcutNamePrepend . ' ' .
615 $languageService->sL($GLOBALS['TCA'][$shortcut['table']]['ctrl']['title']);
616 }
617 }
618
619 return $this->tryAddingTheShortcut($response, $url, $shortcutName);
620 }
621 }
622
623 /**
624 * Try to adding a shortcut
625 *
626 * @param ResponseInterface $response
627 * @param string $url
628 * @param string $shortcutName
629 * @return ResponseInterface
630 */
631 protected function tryAddingTheShortcut(ResponseInterface $response, $url, $shortcutName)
632 {
633 $module = GeneralUtility::_POST('module');
634 $shortcutCreated = 'failed';
635
636 if (!empty($module) && !empty($url)) {
637 $shortcutCreated = 'alreadyExists';
638
639 if (!BackendUtility::shortcutExists($url)) {
640 $shortcutCreated = $this->addShortcut($url, $shortcutName, $module);
641 }
642 }
643
644 $response->getBody()->write($shortcutCreated);
645 $response = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
646 return $response;
647 }
648
649 /**
650 * Add a shortcut now with some user stuffs
651 *
652 * @param string $url
653 * @param string $shortcutName
654 * @param string $module
655 *
656 * @return string
657 */
658 protected function addShortcut($url, $shortcutName, $module)
659 {
660 // Shorts
661 $db = $this->getDatabaseConnection();
662 $lS = $this->getLanguageService();
663
664 if ($shortcutName === 'Shortcut' && !empty($lS->moduleLabels['labels'][$module . '_tablabel'])) {
665 $shortcutName = $lS->moduleLabels['labels'][$module . '_tablabel'];
666 }
667
668 $motherModule = GeneralUtility::_POST('motherModName');
669 $fieldValues = [
670 'userid' => $this->getBackendUser()->user['uid'],
671 'module_name' => $module . '|' . $motherModule,
672 'url' => $url,
673 'description' => $shortcutName,
674 'sorting' => $GLOBALS['EXEC_TIME']
675 ];
676
677 $db->exec_INSERTquery('sys_be_shortcuts', $fieldValues);
678
679 $shortcutCreated = 'failed';
680 if ($db->sql_affected_rows() === 1) {
681 $shortcutCreated = 'success';
682 }
683
684 return $shortcutCreated;
685 }
686
687 /**
688 * Exists already a shortcut entry for this TYPO3 url?
689 *
690 * @param string $url
691 *
692 * @return bool
693 */
694 protected function shortcutExists($url)
695 {
696 $statement = $this->getDatabaseConnection()->prepare_SELECTquery(
697 'uid',
698 'sys_be_shortcuts',
699 'userid = :userid AND url = :url'
700 );
701
702 $statement->bindValues([
703 ':userid' => $this->getBackendUser()->user['uid'],
704 ':url' => $url
705 ]);
706
707 $statement->execute();
708 $rows = $statement->fetch(PreparedStatement::FETCH_ASSOC);
709 $statement->free();
710
711 return !empty($rows);
712 }
713
714 /**
715 * Gets called when a shortcut is changed, checks whether the user has
716 * permissions to do so and saves the changes if everything is ok
717 *
718 * @param ServerRequestInterface $request
719 * @param ResponseInterface $response
720 * @return ResponseInterface
721 */
722 public function saveFormAction(ServerRequestInterface $request, ResponseInterface $response)
723 {
724 $parsedBody = $request->getParsedBody();
725 $queryParams = $request->getQueryParams();
726
727 $databaseConnection = $this->getDatabaseConnection();
728 $backendUser = $this->getBackendUser();
729 $shortcutId = (int)(isset($parsedBody['shortcutId']) ? $parsedBody['shortcutId'] : $queryParams['shortcutId']);
730 $shortcutName = strip_tags(isset($parsedBody['shortcutTitle']) ? $parsedBody['shortcutTitle'] : $queryParams['shortcutTitle']);
731 $shortcutGroupId = (int)(isset($parsedBody['shortcutGroup']) ? $parsedBody['shortcutGroup'] : $queryParams['shortcutGroup']);
732 // Users can only modify their own shortcuts (except admins)
733 $addUserWhere = !$backendUser->isAdmin() ? ' AND userid=' . (int)$backendUser->user['uid'] : '';
734 $fieldValues = [
735 'description' => $shortcutName,
736 'sc_group' => $shortcutGroupId
737 ];
738 if ($fieldValues['sc_group'] < 0 && !$backendUser->isAdmin()) {
739 $fieldValues['sc_group'] = 0;
740 }
741 $databaseConnection->exec_UPDATEquery('sys_be_shortcuts', 'uid=' . $shortcutId . $addUserWhere, $fieldValues);
742 $affectedRows = $databaseConnection->sql_affected_rows();
743 if ($affectedRows == 1) {
744 $response->getBody()->write($shortcutName);
745 } else {
746 $response->getBody()->write('failed');
747 }
748 return $response->withHeader('Content-Type', 'text/html; charset=utf-8');
749 }
750
751 /**
752 * Gets the label for a shortcut group
753 *
754 * @param int $groupId A shortcut group id
755 * @return string The shortcut group label, can be an empty string if no group was found for the id
756 */
757 protected function getShortcutGroupLabel($groupId)
758 {
759 return isset($this->shortcutGroups[$groupId]) ? $this->shortcutGroups[$groupId] : '';
760 }
761
762 /**
763 * Gets a list of global groups, shortcuts in these groups are available to all users
764 *
765 * @return array Array of global groups
766 */
767 protected function getGlobalShortcutGroups()
768 {
769 $globalGroups = [];
770 foreach ($this->shortcutGroups as $groupId => $groupLabel) {
771 if ($groupId < 0) {
772 $globalGroups[$groupId] = $groupLabel;
773 }
774 }
775 return $globalGroups;
776 }
777
778 /**
779 * runs through the available shortcuts an collects their groups
780 *
781 * @return array Array of groups which have shortcuts
782 */
783 protected function getGroupsFromShortcuts()
784 {
785 $groups = [];
786 foreach ($this->shortcuts as $shortcut) {
787 $groups[$shortcut['group']] = $this->shortcutGroups[$shortcut['group']];
788 }
789 return array_unique($groups);
790 }
791
792 /**
793 * Gets the icon for the shortcut
794 *
795 * @param array $row
796 * @param array $shortcut
797 * @return string Shortcut icon as img tag
798 */
799 protected function getShortcutIcon($row, $shortcut)
800 {
801 $databaseConnection = $this->getDatabaseConnection();
802 $languageService = $this->getLanguageService();
803 $titleAttribute = $languageService->sL('LLL:EXT:lang/locallang_core.xlf:toolbarItems.shortcut', true);
804 switch ($row['module_name']) {
805 case 'xMOD_alt_doc.php':
806 $table = $shortcut['table'];
807 $recordid = $shortcut['recordid'];
808 $icon = '';
809 if ($shortcut['type'] == 'edit') {
810 // Creating the list of fields to include in the SQL query:
811 $selectFields = $this->fieldArray;
812 $selectFields[] = 'uid';
813 $selectFields[] = 'pid';
814 if ($table == 'pages') {
815 $selectFields[] = 'module';
816 $selectFields[] = 'extendToSubpages';
817 $selectFields[] = 'doktype';
818 }
819 if (is_array($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
820 $selectFields = array_merge($selectFields, $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']);
821 }
822 if ($GLOBALS['TCA'][$table]['ctrl']['type']) {
823 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['type'];
824 }
825 if ($GLOBALS['TCA'][$table]['ctrl']['typeicon_column']) {
826 $selectFields[] = $GLOBALS['TCA'][$table]['ctrl']['typeicon_column'];
827 }
828 if ($GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
829 $selectFields[] = 't3ver_state';
830 }
831 // Unique list!
832 $selectFields = array_unique($selectFields);
833 $permissionClause = $table === 'pages' && $this->perms_clause ? ' AND ' . $this->perms_clause : '';
834 $sqlQueryParts = [
835 'SELECT' => implode(',', $selectFields),
836 'FROM' => $table,
837 'WHERE' => 'uid IN (' . $recordid . ') ' . $permissionClause . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table)
838 ];
839 $result = $databaseConnection->exec_SELECT_queryArray($sqlQueryParts);
840 $row = $databaseConnection->sql_fetch_assoc($result);
841 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIconForRecord($table, (array)$row, Icon::SIZE_SMALL)->render() . '</span>';
842 } elseif ($shortcut['type'] == 'new') {
843 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIconForRecord($table, [], Icon::SIZE_SMALL)->render() . '</span>';
844 }
845 break;
846 case 'file_edit':
847 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIcon('mimetypes-text-html', Icon::SIZE_SMALL)->render() . '</span>';
848 break;
849 case 'wizard_rte':
850 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIcon('mimetypes-word', Icon::SIZE_SMALL)->render() . '</span>';
851 break;
852 default:
853 if ($languageService->moduleLabels['tabs_images'][$row['module_name'] . '_tab']) {
854 $icon = $languageService->moduleLabels['tabs_images'][$row['module_name'] . '_tab'];
855 // Change icon of fileadmin references - otherwise it doesn't differ with Web->List
856 $icon = str_replace('mod/file/list/list.gif', 'mod/file/file.gif', $icon);
857 if (GeneralUtility::isAbsPath($icon)) {
858 $icon = '../' . PathUtility::stripPathSitePrefix($icon);
859 }
860 // @todo: hardcoded width as we don't have a way to address module icons with an API yet.
861 $icon = '<img src="' . htmlspecialchars($icon) . '" alt="' . $titleAttribute . '" width="16">';
862 } else {
863 $icon = '<span title="' . $titleAttribute . '">' . $this->iconFactory->getIcon('empty-empty', Icon::SIZE_SMALL)->render() . '</span>';
864 }
865 }
866 return $icon;
867 }
868
869 /**
870 * Returns title for the shortcut icon
871 *
872 * @param string $shortcutLabel Shortcut label
873 * @param string $moduleName Backend module name (key)
874 * @param string $parentModuleName Parent module label
875 * @return string Title for the shortcut icon
876 */
877 protected function getShortcutIconTitle($shortcutLabel, $moduleName, $parentModuleName = '')
878 {
879 $languageService = $this->getLanguageService();
880 if (substr($moduleName, 0, 5) == 'xMOD_') {
881 $title = substr($moduleName, 5);
882 } else {
883 $splitModuleName = explode('_', $moduleName);
884 $title = $languageService->moduleLabels['tabs'][$splitModuleName[0] . '_tab'];
885 if (count($splitModuleName) > 1) {
886 $title .= '>' . $languageService->moduleLabels['tabs'][$moduleName . '_tab'];
887 }
888 }
889 if ($parentModuleName) {
890 $title .= ' (' . $parentModuleName . ')';
891 }
892 $title .= ': ' . $shortcutLabel;
893 return $title;
894 }
895
896 /**
897 * Return the ID of the page in the URL if found.
898 *
899 * @param string $url The URL of the current shortcut link
900 * @return string If a page ID was found, it is returned. Otherwise: 0
901 */
902 protected function getLinkedPageId($url)
903 {
904 return preg_replace('/.*[\\?&]id=([^&]+).*/', '$1', $url);
905 }
906
907 /**
908 * Position relative to others, live search should be very right
909 *
910 * @return int
911 */
912 public function getIndex()
913 {
914 return 20;
915 }
916
917 /**
918 * Returns the current BE user.
919 *
920 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
921 */
922 protected function getBackendUser()
923 {
924 return $GLOBALS['BE_USER'];
925 }
926
927 /**
928 * Returns current PageRenderer
929 *
930 * @return PageRenderer
931 */
932 protected function getPageRenderer()
933 {
934 return GeneralUtility::makeInstance(PageRenderer::class);
935 }
936
937 /**
938 * Returns LanguageService
939 *
940 * @return \TYPO3\CMS\Lang\LanguageService
941 */
942 protected function getLanguageService()
943 {
944 return $GLOBALS['LANG'];
945 }
946
947 /**
948 * Return DatabaseConnection
949 *
950 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
951 */
952 protected function getDatabaseConnection()
953 {
954 return $GLOBALS['TYPO3_DB'];
955 }
956 }