Passed
Push — master ( 6c3a97...24f438 )
by
unknown
14:20
created

ShortcutButton::routeExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Backend\Template\Components\Buttons\Action;
17
18
use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
19
use TYPO3\CMS\Backend\Routing\Router;
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
22
use TYPO3\CMS\Backend\Template\Components\Buttons\ButtonInterface;
23
use TYPO3\CMS\Backend\Template\Components\Buttons\PositionInterface;
24
use TYPO3\CMS\Backend\Template\ModuleTemplate;
25
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
26
use TYPO3\CMS\Core\Imaging\Icon;
27
use TYPO3\CMS\Core\Imaging\IconFactory;
28
use TYPO3\CMS\Core\Localization\LanguageService;
29
use TYPO3\CMS\Core\Page\PageRenderer;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
32
/**
33
 * ShortcutButton
34
 *
35
 * Renders a shortcut button in the DocHeader which will be rendered
36
 * to the right position using button group "91".
37
 *
38
 * EXAMPLE USAGE TO ADD A SHORTCUT BUTTON:
39
 *
40
 * $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
41
 * $pageId = (int)($request->getQueryParams()['id'] ?? 0);
42
 * $myButton = $buttonBar->makeShortcutButton()
43
 *       ->setRouteIdentifier('web_view')
44
 *       ->setDisplayName('View page ' . $pageId)
45
 *       ->setArguments([
46
 *          'id' => $pageId
47
 *       ]);
48
 * $buttonBar->addButton($myButton);
49
 */
50
class ShortcutButton implements ButtonInterface, PositionInterface
51
{
52
    /**
53
     * @var string The route identifier of the shortcut
54
     */
55
    protected string $routeIdentifier = '';
56
57
    /**
58
     * @var string
59
     * @deprecated since v11, will be removed in v12
60
     */
61
    protected $moduleName = '';
62
63
    /**
64
     * @var string
65
     */
66
    protected $displayName = '';
67
68
    /**
69
     * @var array List of parameter/value pairs relevant for this shortcut
70
     */
71
    protected $arguments = [];
72
73
    /**
74
     * @var array
75
     * @deprecated since v11, will be removed in v12
76
     */
77
    protected $setVariables = [];
78
79
    /**
80
     * @var array
81
     * @deprecated since v11, will be removed in v12
82
     */
83
    protected $getVariables = [];
84
85
    /**
86
     * @var bool
87
     */
88
    protected bool $copyUrlToClipboard = true;
89
90
    /**
91
     * Gets the route identifier for the shortcut.
92
     *
93
     * @return string
94
     */
95
    public function getRouteIdentifier(): string
96
    {
97
        return $this->routeIdentifier;
98
    }
99
100
    /**
101
     * Sets the route identifier for the shortcut.
102
     *
103
     * @param string $routeIdentifier
104
     * @return ShortcutButton
105
     */
106
    public function setRouteIdentifier(string $routeIdentifier): self
107
    {
108
        $this->routeIdentifier = $routeIdentifier;
109
        return $this;
110
    }
111
112
    /**
113
     * Gets the name of the module.
114
     *
115
     * @return string
116
     * @deprecated since v11, will be removed in v12
117
     */
118
    public function getModuleName()
119
    {
120
        trigger_error('Method getModuleName() is deprecated and will be removed in v12. Use getRouteIdentifier() instead.', E_USER_DEPRECATED);
121
        return $this->moduleName;
122
    }
123
124
    /**
125
     * Sets the name of the module.
126
     *
127
     * @param string $moduleName
128
     * @return ShortcutButton
129
     * @deprecated since v11, will be removed in v12
130
     */
131
    public function setModuleName($moduleName)
132
    {
133
        trigger_error('Method setModuleName() is deprecated and will be removed in v12. Use setRouteIdentifier() instead.', E_USER_DEPRECATED);
134
        $this->moduleName = $moduleName;
135
        return $this;
136
    }
137
138
    /**
139
     * Gets the display name of the module.
140
     *
141
     * @return string
142
     */
143
    public function getDisplayName()
144
    {
145
        return $this->displayName;
146
    }
147
148
    /**
149
     * Sets the display name of the module.
150
     *
151
     * @param string $displayName
152
     * @return ShortcutButton
153
     */
154
    public function setDisplayName($displayName)
155
    {
156
        $this->displayName = $displayName;
157
        return $this;
158
    }
159
160
    /**
161
     * @param array $arguments
162
     * @return $this
163
     */
164
    public function setArguments(array $arguments): self
165
    {
166
        $this->arguments = $arguments;
167
        return $this;
168
    }
169
170
    /**
171
     * Gets the SET variables.
172
     *
173
     * @return array
174
     * @deprecated since v11, will be removed in v12
175
     */
176
    public function getSetVariables()
177
    {
178
        trigger_error('Method getSetVariables() is deprecated and will be removed in v12. Please use ShortcutButton->setArguments() instead.', E_USER_DEPRECATED);
179
        return $this->setVariables;
180
    }
181
182
    /**
183
     * Sets the SET variables.
184
     *
185
     * @param array $setVariables
186
     * @return ShortcutButton
187
     * @deprecated since v11, will be removed in v12. Deprecation logged by ModuleTemplate->makeShortcutIcon()
188
     */
189
    public function setSetVariables(array $setVariables)
190
    {
191
        $this->setVariables = $setVariables;
192
        return $this;
193
    }
194
195
    /**
196
     * Gets the GET variables.
197
     *
198
     * @return array
199
     * @deprecated since v11, will be removed in v12
200
     */
201
    public function getGetVariables()
202
    {
203
        trigger_error('Method getGetVariables() is deprecated and will be removed in v12. Please use ShortcutButton->setArguments() instead.', E_USER_DEPRECATED);
204
        return $this->getVariables;
205
    }
206
207
    /**
208
     * Sets the GET variables.
209
     *
210
     * @param array $getVariables
211
     * @return ShortcutButton
212
     * @deprecated since v11, will be removed in v12. Deprecation logged by ModuleTemplate->makeShortcutIcon()
213
     */
214
    public function setGetVariables(array $getVariables)
215
    {
216
        $this->getVariables = $getVariables;
217
        return $this;
218
    }
219
220
    /**
221
     * Defines whether the shortcut button should be extended to also
222
     * allow copying the current URL to the operating systems' clipboard.
223
     *
224
     * @param bool $copyUrlToClipboard
225
     * @return $this
226
     */
227
    public function setCopyUrlToClipboard(bool $copyUrlToClipboard): self
228
    {
229
        $this->copyUrlToClipboard = $copyUrlToClipboard;
230
        return $this;
231
    }
232
233
    /**
234
     * Gets the button position.
235
     *
236
     * @return string
237
     */
238
    public function getPosition()
239
    {
240
        return ButtonBar::BUTTON_POSITION_RIGHT;
241
    }
242
243
    /**
244
     * Gets the button group.
245
     *
246
     * @return int
247
     */
248
    public function getGroup()
249
    {
250
        return 91;
251
    }
252
253
    /**
254
     * Gets the type of the button
255
     *
256
     * @return string
257
     */
258
    public function getType()
259
    {
260
        return static::class;
261
    }
262
263
    /**
264
     * Determines whether the button shall be rendered.
265
     *
266
     * @return bool
267
     */
268
    public function isValid()
269
    {
270
        return $this->moduleName !== '' || $this->routeIdentifier !== '' || (string)($this->arguments['route'] ?? '') !== '';
271
    }
272
273
    /**
274
     * Renders the button
275
     *
276
     * @return string
277
     */
278
    public function __toString()
279
    {
280
        return $this->render();
281
    }
282
283
    /**
284
     * Renders the button
285
     *
286
     * @return string
287
     */
288
    public function render()
289
    {
290
        if ($this->getBackendUser()->mayMakeShortcut()) {
291
            if ($this->displayName === '') {
292
                trigger_error('Creating a shortcut button without a display name is deprecated and fallbacks will be removed in v12. Please use ShortcutButton->setDisplayName() to set a display name.', E_USER_DEPRECATED);
293
            }
294
            if (!empty($this->routeIdentifier) || !empty($this->arguments)) {
295
                $shortcutMarkup = $this->createShortcutMarkup();
296
            } else {
297
                // @deprecated since v11, the else branch will be removed in v12. Deprecation thrown by makeShortcutIcon() below
298
                if (empty($this->getVariables)) {
299
                    $this->getVariables = ['id', 'route'];
300
                }
301
                $moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
302
                $shortcutMarkup = $moduleTemplate->makeShortcutIcon(
303
                    implode(',', $this->getVariables),
304
                    implode(',', $this->setVariables),
305
                    $this->moduleName,
306
                    '',
307
                    $this->displayName
308
                );
309
            }
310
        } else {
311
            $shortcutMarkup = '';
312
        }
313
        return $shortcutMarkup;
314
    }
315
316
    protected function createShortcutMarkup(): string
317
    {
318
        $routeIdentifier = $this->routeIdentifier;
319
        $arguments = $this->arguments;
320
        $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
321
322
        if (strpos($routeIdentifier, '/') !== false) {
323
            trigger_error('Automatic fallback for the route path is deprecated and will be removed in v12.', E_USER_DEPRECATED);
324
            $routeIdentifier = $this->getRouteIdentifierByRoutePath($routeIdentifier);
325
        }
326
327
        if ($routeIdentifier === '' && $this->moduleName !== '') {
328
            trigger_error('Using ShortcutButton::$moduleNname is deprecated and will be removed in v12. Use ShortcutButton::$routeIdentifier instead.', E_USER_DEPRECATED);
329
            $routeIdentifier = $this->getRouteIdentifierByModuleName($this->moduleName);
330
        }
331
332
        if (isset($arguments['route'])) {
333
            trigger_error('Using route as an argument is deprecated and will be removed in v12. Set the route identifier with ShortcutButton::setRouteIdentifier() instead.', E_USER_DEPRECATED);
334
            if ($routeIdentifier === '' && is_string($arguments['route'])) {
335
                $routeIdentifier = $this->getRouteIdentifierByRoutePath($arguments['route']);
336
            }
337
            unset($arguments['route']);
338
        }
339
340
        // No route found so no shortcut button will be rendered
341
        if ($routeIdentifier === '' || !$this->routeExists($routeIdentifier)) {
342
            return '';
343
        }
344
345
        // returnUrl will not longer be stored in the database
346
        unset($arguments['returnUrl']);
347
348
        // Encode arguments to be stored in the database
349
        $encodedArguments = json_encode($arguments) ?: '';
350
351
        $confirmationText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark');
352
        $alreadyBookmarkedText = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.alreadyBookmarked');
353
354
        if (!$this->copyUrlToClipboard) {
355
            return GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $encodedArguments)
356
                ? '<button type="button" class="active btn btn-default btn-sm" title="' . htmlspecialchars($alreadyBookmarkedText) . '">'
357
                    . $iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render()
358
                    . '</button>'
359
                : '<button type="button" class="btn btn-default btn-sm" title="' . htmlspecialchars($confirmationText) . '" onclick="' . htmlspecialchars($this->getOnClick($routeIdentifier, $encodedArguments, $confirmationText)) . '">'
360
                    . $iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render()
361
                    . '</button>';
362
        }
363
364
        $menuItems = [];
365
366
        if (GeneralUtility::makeInstance(ShortcutRepository::class)->shortcutExists($routeIdentifier, $encodedArguments)) {
367
            $menuItems[] =
368
                '<li>' .
369
                    '<button type="button" class="dropdown-item btn btn-link disabled">' .
370
                        $iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render() . ' ' .
371
                        htmlspecialchars($alreadyBookmarkedText) .
372
                    '</button>' .
373
                '</li>';
374
        } else {
375
            $menuItems[] = '
376
                <li>' .
377
                    '<button type="button" class="dropdown-item btn btn-link" onclick="' . htmlspecialchars($this->getOnClick($routeIdentifier, $encodedArguments, $confirmationText)) . '">' .
378
                        $iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . ' ' .
379
                        htmlspecialchars($confirmationText) .
380
                    '</button>' .
381
                '</li>';
382
        }
383
384
        $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
385
        $pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/CopyToClipboard');
386
        $pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_copytoclipboard.xlf');
387
388
        $currentUrl = (string)GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute(
389
            $routeIdentifier,
390
            $arguments,
391
            UriBuilder::SHAREABLE_URL
392
        );
393
394
        $menuItems[] = '
395
            <li>
396
                <typo3-copy-to-clipboard text="' . htmlspecialchars($currentUrl) . '">
397
                    <button type="button" class="dropdown-item btn btn-link">' .
398
                        $iconFactory->getIcon('actions-link', Icon::SIZE_SMALL)->render() . ' ' .
399
                        htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.copyCurrentUrl')) .
400
                    '</button>' .
401
                '</typo3-copy-to-clipboard>' .
402
            '</li>';
403
404
        return '
405
            <button type="button" class="btn btn-default btn-sm" id="dropdownShortcutMenu" data-bs-toggle="dropdown" aria-expanded="false" title="' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.share')) . '">' .
406
                $iconFactory->getIcon('share-alt', Icon::SIZE_SMALL)->render() .
407
            '</button>' .
408
            '<ul class="dropdown-menu" aria-labelledby="dropdownShortcutMenu">' .
409
                implode(LF, $menuItems) .
410
            '</ul>';
411
    }
412
413
    /**
414
     * Map a given route path to its route identifier
415
     *
416
     * @param string $routePath
417
     * @return string
418
     * @deprecated Only for backwards compatibility. Can be removed in v12.
419
     */
420
    protected function getRouteIdentifierByRoutePath(string $routePath): string
421
    {
422
        foreach ($this->getRoutes() as $identifier => $route) {
423
            if ($route->getPath() === $routePath
424
                && (
425
                    $route->hasOption('moduleName')
426
                    || in_array($identifier, ['record_edit', 'file_edit', 'wizard_rte'], true)
427
                )
428
            ) {
429
                return $identifier;
430
            }
431
        }
432
433
        return '';
434
    }
435
436
    /**
437
     * Map a given module name to its route identifier by respecting some special cases
438
     *
439
     * @param string $moduleName
440
     * @return string
441
     * @deprecated Only for backwards compatibility. Can be removed in v12.
442
     */
443
    protected function getRouteIdentifierByModuleName(string $moduleName): string
444
    {
445
        $identifier = '';
446
447
        // Special case module names
448
        switch ($moduleName) {
449
            case 'xMOD_alt_doc.php':
450
                $identifier = 'record_edit';
451
                break;
452
            case 'file_edit':
453
            case 'wizard_rte':
454
                $identifier = $moduleName;
455
                break;
456
        }
457
458
        if ($identifier !== '') {
459
            return $identifier;
460
        }
461
462
        foreach ($this->getRoutes() as $identifier => $route) {
463
            if ($route->hasOption('moduleName') && $route->getOption('moduleName') === $moduleName) {
464
                return $identifier;
465
            }
466
        }
467
468
        return '';
469
    }
470
471
    /**
472
     * Return the markup for the onclick attribute of the "add shortcut" button
473
     *
474
     * @param string $routeIdentifier
475
     * @param string $encodedArguments
476
     * @param string $confirmationText
477
     * @return string
478
     */
479
    protected function getOnClick(string $routeIdentifier, string $encodedArguments, string $confirmationText): string
480
    {
481
        return 'top.TYPO3.ShortcutMenu.createShortcut('
482
            . GeneralUtility::quoteJSvalue($routeIdentifier)
483
            . ', ' . GeneralUtility::quoteJSvalue($encodedArguments)
484
            . ', ' . GeneralUtility::quoteJSvalue($this->displayName)
485
            . ', ' . GeneralUtility::quoteJSvalue($confirmationText)
486
            . ', this);return false;';
487
    }
488
489
    protected function routeExists(string $routeIdentifier): bool
490
    {
491
        return (bool)($this->getRoutes()[$routeIdentifier] ?? false);
492
    }
493
494
    protected function getRoutes(): iterable
495
    {
496
        return GeneralUtility::makeInstance(Router::class)->getRoutes();
497
    }
498
499
    protected function getBackendUser(): BackendUserAuthentication
500
    {
501
        return $GLOBALS['BE_USER'];
502
    }
503
504
    protected function getLanguageService(): LanguageService
505
    {
506
        return $GLOBALS['LANG'];
507
    }
508
}
509