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