Passed
Push — master ( c506e6...529375 )
by
unknown
17:07
created

Clipboard::clLabel()   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
dl 0
loc 3
rs 10
c 0
b 0
f 0
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\Clipboard;
17
18
use TYPO3\CMS\Backend\Routing\UriBuilder;
19
use TYPO3\CMS\Backend\Utility\BackendUtility;
20
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
21
use TYPO3\CMS\Core\Database\ConnectionPool;
22
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23
use TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction;
24
use TYPO3\CMS\Core\Imaging\Icon;
25
use TYPO3\CMS\Core\Imaging\IconFactory;
26
use TYPO3\CMS\Core\Localization\LanguageService;
27
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
28
use TYPO3\CMS\Core\Resource\File;
29
use TYPO3\CMS\Core\Resource\Folder;
30
use TYPO3\CMS\Core\Resource\ProcessedFile;
31
use TYPO3\CMS\Core\Resource\ResourceFactory;
32
use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
33
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
34
use TYPO3\CMS\Core\Utility\GeneralUtility;
35
use TYPO3\CMS\Core\Utility\MathUtility;
36
use TYPO3\CMS\Core\Utility\PathUtility;
37
use TYPO3\CMS\Fluid\View\StandaloneView;
38
39
/**
40
 * TYPO3 clipboard for records and files
41
 *
42
 * @internal This class is a specific Backend implementation and is not considered part of the Public TYPO3 API.
43
 */
44
class Clipboard
45
{
46
    /**
47
     * @var int
48
     */
49
    public $numberTabs = 3;
50
51
    /**
52
     * Clipboard data kept here
53
     *
54
     * Keys:
55
     * 'normal'
56
     * 'tab_[x]' where x is >=1 and denotes the pad-number
57
     * 'mode'	:	'copy' means copy-mode, default = moving ('cut')
58
     * 'el'	:	Array of elements:
59
     * DB: keys = '[tablename]|[uid]'	eg. 'tt_content:123'
60
     * DB: values = 1 (basically insignificant)
61
     * FILE: keys = '_FILE|[shortmd5 of path]'	eg. '_FILE|9ebc7e5c74'
62
     * FILE: values = The full filepath, eg. '/www/htdocs/typo3/32/dummy/fileadmin/sem1_3_examples/alternative_index.php'
63
     * or 'C:/www/htdocs/typo3/32/dummy/fileadmin/sem1_3_examples/alternative_index.php'
64
     *
65
     * 'current' pointer to current tab (among the above...)
66
     *
67
     * The virtual tablename '_FILE' will always indicate files/folders. When checking for elements from eg. 'all tables'
68
     * (by using an empty string) '_FILE' entries are excluded (so in effect only DB elements are counted)
69
     *
70
     * @var array
71
     */
72
    public $clipData = [];
73
74
    public bool $changed = false;
75
76
    /**
77
     * @var string
78
     */
79
    public $current = '';
80
81
    public bool $lockToNormal = false;
82
83
    /**
84
     * If set, clipboard is displaying files.
85
     */
86
    public bool $fileMode = false;
87
88
    protected IconFactory $iconFactory;
89
    protected UriBuilder $uriBuilder;
90
    protected ResourceFactory $resourceFactory;
91
92
    public function __construct(IconFactory $iconFactory, UriBuilder $uriBuilder, ResourceFactory $resourceFactory)
93
    {
94
        $this->iconFactory = $iconFactory;
95
        $this->uriBuilder = $uriBuilder;
96
        $this->resourceFactory = $resourceFactory;
97
    }
98
99
    /*****************************************
100
     *
101
     * Initialize
102
     *
103
     ****************************************/
104
    /**
105
     * Initialize the clipboard from the be_user session
106
     */
107
    public function initializeClipboard()
108
    {
109
        $userTsConfig = $this->getBackendUser()->getTSConfig();
110
        // Get data
111
        $clipData = $this->getBackendUser()->getModuleData('clipboard', !empty($userTsConfig['options.']['saveClipboard'])  ? '' : 'ses') ?: [];
112
        $clipData += ['normal' => []];
113
        $this->numberTabs = MathUtility::forceIntegerInRange((int)($userTsConfig['options.']['clipboardNumberPads'] ?? 3), 0, 20);
114
        // Resets/reinstates the clipboard pads
115
        $this->clipData['normal'] = is_array($clipData['normal']) ? $clipData['normal']: [];
116
        for ($a = 1; $a <= $this->numberTabs; $a++) {
117
            $index = 'tab_' . $a;
118
            $this->clipData[$index] = is_iterable($clipData[$index] ?? null) ? $clipData[$index] : [];
119
        }
120
        // Setting the current pad pointer ($this->current))
121
        $current = $clipData['current'] ?? '';
122
        $this->current = isset($this->clipData[$current]) ? $current : 'normal';
123
        $this->clipData['current'] = $this->current;
124
    }
125
126
    /**
127
     * Call this method after initialization if you want to lock the clipboard to operate on the normal pad only.
128
     * Trying to switch pad through ->setCmd will not work.
129
     * This is used by the clickmenu since it only allows operation on single elements at a time (that is the "normal" pad)
130
     */
131
    public function lockToNormal()
132
    {
133
        $this->lockToNormal = true;
134
        $this->current = 'normal';
135
    }
136
137
    /**
138
     * The array $cmd may hold various keys which notes some action to take.
139
     * Normally perform only one action at a time.
140
     * In scripts like db_list.php / filelist/mod1/index.php the GET-var CB is used to control the clipboard.
141
     *
142
     * Selecting / Deselecting elements
143
     * Array $cmd['el'] has keys = element-ident, value = element value (see description of clipData array in header)
144
     * Selecting elements for 'copy' should be done by simultaneously setting setCopyMode.
145
     *
146
     * @param array $cmd Array of actions, see function description
147
     */
148
    public function setCmd($cmd)
149
    {
150
        $cmd['el'] ??= [];
151
        $cmd['el'] = is_iterable($cmd['el']) ? $cmd['el'] : [];
152
        foreach ($cmd['el'] as $k => $v) {
153
            if ($this->current === 'normal') {
154
                unset($this->clipData['normal']);
155
            }
156
            if ($v) {
157
                $this->clipData[$this->current]['el'][$k] = $v;
158
            } else {
159
                $this->removeElement($k);
160
            }
161
            $this->changed = true;
162
        }
163
        // Change clipboard pad (if not locked to normal)
164
        if ($cmd['setP'] ?? false) {
165
            $this->setCurrentPad($cmd['setP']);
166
        }
167
        // Remove element	(value = item ident: DB; '[tablename]|[uid]'    FILE: '_FILE|[shortmd5 hash of path]'
168
        if ($cmd['remove'] ?? false) {
169
            $this->removeElement($cmd['remove']);
170
            $this->changed = true;
171
        }
172
        // Remove all on current pad (value = pad-ident)
173
        if ($cmd['removeAll'] ?? false) {
174
            $this->clipData[$cmd['removeAll']] = [];
175
            $this->changed = true;
176
        }
177
        // Set copy mode of the tab
178
        if (isset($cmd['setCopyMode'])) {
179
            $this->clipData[$this->current]['mode'] = $cmd['setCopyMode'] ? 'copy' : '';
180
            $this->changed = true;
181
        }
182
    }
183
184
    /**
185
     * Setting the current pad on clipboard
186
     *
187
     * @param string $padIdentifier Key in the array $this->clipData
188
     */
189
    public function setCurrentPad($padIdentifier)
190
    {
191
        // Change clipboard pad (if not locked to normal)
192
        if (!$this->lockToNormal && $this->current != $padIdentifier) {
193
            if (isset($this->clipData[$padIdentifier])) {
194
                $this->clipData['current'] = ($this->current = $padIdentifier);
195
            }
196
            if ($this->current !== 'normal' || !$this->isElements()) {
197
                $this->clipData[$this->current]['mode'] = '';
198
            }
199
            // Setting mode to default (move) if no items on it or if not 'normal'
200
            $this->changed = true;
201
        }
202
    }
203
204
    /**
205
     * Call this after initialization and setCmd in order to save the clipboard to the user session.
206
     * The function will check if the internal flag ->changed has been set and if so, save the clipboard. Else not.
207
     */
208
    public function endClipboard()
209
    {
210
        if ($this->changed) {
211
            $this->saveClipboard();
212
        }
213
        $this->changed = false;
214
    }
215
216
    /**
217
     * Cleans up an incoming element array $CBarr (Array selecting/deselecting elements)
218
     *
219
     * @param array $CBarr Element array from outside ("key" => "selected/deselected")
220
     * @param string $table The 'table which is allowed'. Must be set.
221
     * @param bool|int $removeDeselected Can be set in order to remove entries which are marked for deselection.
222
     * @return array Processed input $CBarr
223
     */
224
    public function cleanUpCBC($CBarr, $table, $removeDeselected = 0)
225
    {
226
        if (is_array($CBarr)) {
0 ignored issues
show
introduced by
The condition is_array($CBarr) is always true.
Loading history...
227
            foreach ($CBarr as $k => $v) {
228
                $p = explode('|', $k);
229
                if ((string)$p[0] != (string)$table || $removeDeselected && !$v) {
230
                    unset($CBarr[$k]);
231
                }
232
            }
233
        }
234
        return $CBarr;
235
    }
236
237
    /*****************************************
238
     *
239
     * Clipboard HTML renderings
240
     *
241
     ****************************************/
242
    /**
243
     * Prints the clipboard
244
     *
245
     * @return string HTML output
246
     * @throws \BadFunctionCallException
247
     */
248
    public function printClipboard()
249
    {
250
        $elementCount = count($this->elFromTable($this->fileMode ? '_FILE' : ''));
251
        $view = $this->getStandaloneView();
252
253
        // CopyMode Selector menu
254
        $view->assign('actionCopyModeUrl', GeneralUtility::linkThisScript());
255
        $view->assign('currentMode', $this->currentMode());
256
        $view->assign('elementCount', $elementCount);
257
258
        if ($elementCount) {
259
            $view->assign('removeAllUrl', GeneralUtility::linkThisScript(['CB' => ['removeAll' => $this->current]]));
260
        }
261
262
        // Print header and content for the NORMAL tab:
263
        $view->assign('current', $this->current);
264
        $tabArray = [];
265
        $tabArray['normal'] = [
266
            'id' => 'normal',
267
            'number' => 0,
268
            'url' => GeneralUtility::linkThisScript(['CB' => ['setP' => 'normal']]),
269
            'description' => 'labels.normal-description',
270
            'label' => 'labels.normal',
271
            'padding' => $this->padTitle('normal')
272
        ];
273
        if ($this->current === 'normal') {
274
            $tabArray['normal']['content'] = $this->getContentFromTab('normal');
275
        }
276
        // Print header and content for the NUMERIC tabs:
277
        for ($a = 1; $a <= $this->numberTabs; $a++) {
278
            $tabArray['tab_' . $a] = [
279
                'id' => 'tab_' . $a,
280
                'number' => $a,
281
                'url' => GeneralUtility::linkThisScript(['CB' => ['setP' => 'tab_' . $a]]),
282
                'description' => 'labels.cliptabs-description',
283
                'label' => 'labels.cliptabs-name',
284
                'padding' => $this->padTitle('tab_' . $a)
285
            ];
286
            if ($this->current === 'tab_' . $a) {
287
                $tabArray['tab_' . $a]['content'] = $this->getContentFromTab('tab_' . $a);
288
            }
289
        }
290
        $view->assign('clipboardHeader', BackendUtility::wrapInHelp('xMOD_csh_corebe', 'list_clipboard', $this->clLabel('buttons.clipboard')));
291
        $view->assign('tabArray', $tabArray);
292
        return $view->render();
293
    }
294
295
    /**
296
     * Print the content on a pad. Called from ->printClipboard()
297
     *
298
     * @internal
299
     * @param string $pad Pad reference
300
     * @return array Array with table rows for the clipboard.
301
     */
302
    public function getContentFromTab($pad)
303
    {
304
        $lines = [];
305
        if (is_array($this->clipData[$pad]['el'] ?? false)) {
306
            foreach ($this->clipData[$pad]['el'] as $k => $v) {
307
                if ($v) {
308
                    [$table, $uid] = explode('|', $k);
309
                    // Rendering files/directories on the clipboard
310
                    if ($table === '_FILE') {
311
                        $fileObject = $this->resourceFactory->retrieveFileOrFolderObject($v);
312
                        if ($fileObject) {
313
                            $thumb = [];
314
                            $folder = $fileObject instanceof Folder;
315
                            $size = $folder ? '' : '(' . GeneralUtility::formatSize((int)$fileObject->getSize()) . 'bytes)';
316
                            /** @var File $fileObject */
317
                            if (!$folder && $fileObject->isImage()) {
318
                                $processedFile = $fileObject->process(
319
                                    ProcessedFile::CONTEXT_IMAGEPREVIEW,
320
                                    [
321
                                        'width' => 64,
322
                                        'height' => 64,
323
                                    ]
324
                                );
325
326
                                $thumb = '<img src="' . htmlspecialchars(PathUtility::getAbsoluteWebPath($processedFile->getPublicUrl() ?? '')) . '" ' .
327
                                    'width="' . htmlspecialchars($processedFile->getProperty('width')) . '" ' .
0 ignored issues
show
Bug introduced by
It seems like $processedFile->getProperty('width') can also be of type null; however, parameter $string of htmlspecialchars() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

327
                                    'width="' . htmlspecialchars(/** @scrutinizer ignore-type */ $processedFile->getProperty('width')) . '" ' .
Loading history...
328
                                    'height="' . htmlspecialchars($processedFile->getProperty('height')) . '" ' .
329
                                    'title="' . htmlspecialchars($processedFile->getName()) . '" alt="" />';
330
                            }
331
                            $lines[] = [
332
                                'icon' => '<span title="' . htmlspecialchars($fileObject->getName() . ' ' . $size) . '">' . $this->iconFactory->getIconForResource(
333
                                    $fileObject,
334
                                    Icon::SIZE_SMALL
335
                                )->render() . '</span>',
336
                                'title' => $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs(
337
                                    $fileObject->getName(),
338
                                    $this->getBackendUser()->uc['titleLen']
339
                                )), $fileObject->getName()),
340
                                'thumb' => $thumb,
341
                                'infoDataDispatch' => [
342
                                    'action' => 'TYPO3.InfoWindow.showItem',
343
                                    'args' => GeneralUtility::jsonEncodeForHtmlAttribute([$table, $v], false),
344
                                ],
345
                                'removeLink' => $this->removeUrl('_FILE', GeneralUtility::shortMD5($v))
346
                            ];
347
                        } else {
348
                            // If the file did not exist (or is illegal) then it is removed from the clipboard immediately:
349
                            unset($this->clipData[$pad]['el'][$k]);
350
                            $this->changed = true;
351
                        }
352
                    } else {
353
                        // Rendering records:
354
                        $rec = BackendUtility::getRecordWSOL($table, (int)$uid);
355
                        if (is_array($rec)) {
356
                            $lines[] = [
357
                                'icon' => $this->linkItemText($this->iconFactory->getIconForRecord(
358
                                    $table,
359
                                    $rec,
360
                                    Icon::SIZE_SMALL
361
                                )->render(), $rec, $table),
362
                                'title' => $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle(
363
                                    $table,
364
                                    $rec
365
                                ), $this->getBackendUser()->uc['titleLen'])), $rec, $table),
366
                                'infoDataDispatch' => [
367
                                    'action' => 'TYPO3.InfoWindow.showItem',
368
                                    'args' => GeneralUtility::jsonEncodeForHtmlAttribute([$table, (int)$uid], false),
369
                                ],
370
                                'removeLink' => $this->removeUrl($table, $uid)
371
                            ];
372
373
                            $localizationData = $this->getLocalizations($table, $rec);
374
                            if (!empty($localizationData)) {
375
                                $lines = array_merge($lines, $localizationData);
376
                            }
377
                        } else {
378
                            unset($this->clipData[$pad]['el'][$k]);
379
                            $this->changed = true;
380
                        }
381
                    }
382
                }
383
            }
384
        }
385
        $this->endClipboard();
386
        return $lines;
387
    }
388
389
    /**
390
     * Returns true if the clipboard contains elements
391
     *
392
     * @return bool
393
     */
394
    public function hasElements()
395
    {
396
        foreach ($this->clipData as $data) {
397
            if (isset($data['el']) && is_array($data['el']) && !empty($data['el'])) {
398
                return true;
399
            }
400
        }
401
        return false;
402
    }
403
404
    /**
405
     * Gets all localizations of the current record.
406
     *
407
     * @param string $table The table
408
     * @param array $parentRec The current record
409
     * @return array HTML table rows
410
     */
411
    public function getLocalizations($table, $parentRec)
412
    {
413
        $lines = [];
414
        $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
415
        $workspaceId = (int)$this->getBackendUser()->workspace;
416
417
        if (BackendUtility::isTableLocalizable($table)) {
418
            $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
419
            $queryBuilder->getRestrictions()
420
                ->removeAll()
421
                ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
422
423
            $queryBuilder
424
                ->select('*')
425
                ->from($table)
426
                ->where(
427
                    $queryBuilder->expr()->eq(
428
                        $tcaCtrl['transOrigPointerField'],
429
                        $queryBuilder->createNamedParameter($parentRec['uid'], \PDO::PARAM_INT)
430
                    ),
431
                    $queryBuilder->expr()->neq(
432
                        $tcaCtrl['languageField'],
433
                        $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
434
                    ),
435
                    $queryBuilder->expr()->gt(
436
                        'pid',
437
                        $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
438
                    )
439
                )
440
                ->orderBy($tcaCtrl['languageField']);
441
442
            if (BackendUtility::isTableWorkspaceEnabled($table)) {
443
                $queryBuilder->getRestrictions()->add(
444
                    GeneralUtility::makeInstance(WorkspaceRestriction::class, $workspaceId)
445
                );
446
            }
447
            $rows = $queryBuilder->execute()->fetchAll();
448
            if (is_array($rows)) {
449
                foreach ($rows as $rec) {
450
                    $lines[] = [
451
                        'icon' => $this->iconFactory->getIconForRecord($table, $rec, Icon::SIZE_SMALL)->render(),
452
                        'title' => htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $rec), $this->getBackendUser()->uc['titleLen']))
453
                    ];
454
                }
455
            }
456
        }
457
        return $lines;
458
    }
459
460
    /**
461
     * Warps title with number of elements if any.
462
     *
463
     * @param string  $pad Pad reference
464
     * @return string padding
465
     */
466
    public function padTitle($pad)
467
    {
468
        $el = count($this->elFromTable($this->fileMode ? '_FILE' : '', $pad));
469
        if ($el) {
470
            return ' (' . ($pad === 'normal' ? (($this->clipData['normal']['mode'] ?? '') === 'copy' ? $this->clLabel('cm.copy') : $this->clLabel('cm.cut')) : htmlspecialchars((string)$el)) . ')';
471
        }
472
        return '';
473
    }
474
475
    /**
476
     * Wraps the title of the items listed in link-tags. The items will link to the page/folder where they originate from
477
     *
478
     * @param string $str Title of element - must be htmlspecialchar'ed on beforehand.
479
     * @param mixed $rec If array, a record is expected. If string, its a path
480
     * @param string $table Table name
481
     * @return string
482
     */
483
    public function linkItemText($str, $rec, $table = '')
484
    {
485
        if (is_array($rec) && $table) {
486
            if ($this->fileMode) {
487
                $str = '<span class="text-muted">' . $str . '</span>';
488
            } else {
489
                $str = '<a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('web_list', ['id' => $rec['pid']])) . '">' . $str . '</a>';
490
            }
491
        } elseif (file_exists($rec)) {
492
            if (!$this->fileMode) {
493
                $str = '<span class="text-muted">' . $str . '</span>';
494
            } elseif (ExtensionManagementUtility::isLoaded('filelist')) {
495
                $str = '<a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('file_list', ['id' => PathUtility::dirname($rec)])) . '">' . $str . '</a>';
496
            }
497
        }
498
        return $str;
499
    }
500
501
    /**
502
     * Returns the select-url for database elements
503
     *
504
     * @param string $table Table name
505
     * @param int $uid Uid of record
506
     * @param bool|int $copy If set, copymode will be enabled
507
     * @param bool|int $deselect If set, the link will deselect, otherwise select.
508
     * @param array $baseArray The base array of GET vars to be sent in addition. Notice that current GET vars WILL automatically be included.
509
     * @return string URL linking to the current script but with the CB array set to select the element with table/uid
510
     */
511
    public function selUrlDB($table, $uid, $copy = 0, $deselect = 0, $baseArray = [])
512
    {
513
        $CB = ['el' => [rawurlencode($table . '|' . $uid) => $deselect ? 0 : 1]];
514
        if ($copy) {
515
            $CB['setCopyMode'] = 1;
516
        }
517
        $baseArray['CB'] = $CB;
518
        return GeneralUtility::linkThisScript($baseArray);
519
    }
520
521
    /**
522
     * Returns the select-url for files
523
     *
524
     * @param string $path Filepath
525
     * @param bool $copy If set, copymode will be enabled
526
     * @param bool $deselect If set, the link will deselect, otherwise select.
527
     * @return string URL linking to the current script but with the CB array set to select the path
528
     */
529
    public function selUrlFile($path, $copy = false, $deselect = false)
530
    {
531
        $CB = [
532
            'el' => [
533
                rawurlencode('_FILE|' . GeneralUtility::shortMD5($path)) => $deselect ? '' : $path
534
            ]
535
        ];
536
        if ($copy) {
537
            $CB['setCopyMode'] = 1;
538
        }
539
        return GeneralUtility::linkThisScript(['CB' => $CB]);
540
    }
541
542
    /**
543
     * pasteUrl of the element (database and file)
544
     * For the meaning of $table and $uid, please read from ->makePasteCmdArray!!!
545
     * The URL will point to tce_file or tce_db depending in $table
546
     *
547
     * @param string $table Tablename (_FILE for files)
548
     * @param mixed $uid "destination": can be positive or negative indicating how the paste is done (paste into / paste after)
549
     * @param bool $setRedirect If set, then the redirect URL will point back to the current script, but with CB reset.
550
     * @param array|null $update Additional key/value pairs which should get set in the moved/copied record (via DataHandler)
551
     * @return string
552
     */
553
    public function pasteUrl($table, $uid, $setRedirect = true, array $update = null)
554
    {
555
        $urlParameters = [
556
            'CB[paste]' => $table . '|' . $uid,
557
            'CB[pad]' => $this->current
558
        ];
559
        if ($setRedirect) {
560
            $urlParameters['redirect'] = GeneralUtility::linkThisScript(['CB' => '']);
561
        }
562
        if (is_array($update)) {
563
            $urlParameters['CB[update]'] = $update;
564
        }
565
        return (string)$this->uriBuilder->buildUriFromRoute($table === '_FILE' ? 'tce_file' : 'tce_db', $urlParameters);
566
    }
567
568
    /**
569
     * Returns the remove-url (file and db)
570
     * for file $table='_FILE' and $uid = shortmd5 hash of path
571
     *
572
     * @param string $table Tablename
573
     * @param string $uid Uid integer/shortmd5 hash
574
     * @return string URL
575
     */
576
    public function removeUrl($table, $uid)
577
    {
578
        return GeneralUtility::linkThisScript(['CB' => ['remove' => $table . '|' . $uid]]);
579
    }
580
581
    /**
582
     * Returns confirm JavaScript message
583
     *
584
     * @param string $table Table name
585
     * @param mixed $rec For records its an array, for files its a string (path)
586
     * @param string $type Type-code
587
     * @param array $clElements Array of selected elements
588
     * @param string $columnLabel Name of the content column
589
     * @return string the text for a confirm message
590
     */
591
    public function confirmMsgText($table, $rec, $type, $clElements, $columnLabel = '')
592
    {
593
        if ($this->getBackendUser()->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
594
            $labelKey = 'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:mess.' . ($this->currentMode() === 'copy' ? 'copy' : 'move') . ($this->current === 'normal' ? '' : 'cb') . '_' . $type;
595
            $msg = $this->getLanguageService()->sL($labelKey . ($columnLabel ? '_colPos' : ''));
596
            if ($table === '_FILE') {
597
                $thisRecTitle = PathUtility::basename($rec);
598
                if ($this->current === 'normal') {
599
                    $selItem = reset($clElements);
600
                    $selRecTitle = PathUtility::basename($selItem);
601
                } else {
602
                    $selRecTitle = count($clElements);
603
                }
604
            } else {
605
                $thisRecTitle = $table === 'pages' && !is_array($rec) ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] : BackendUtility::getRecordTitle($table, $rec);
606
                if ($this->current === 'normal') {
607
                    $selItem = $this->getSelectedRecord();
608
                    $selRecTitle = $selItem['_RECORD_TITLE'];
609
                } else {
610
                    $selRecTitle = count($clElements);
611
                }
612
            }
613
            // @TODO
614
            // This can get removed as soon as the "_colPos" label is translated
615
            // into all available locallang languages.
616
            if (!$msg && $columnLabel) {
617
                $thisRecTitle .= ' | ' . $columnLabel;
618
                $msg = $this->getLanguageService()->sL($labelKey);
619
            }
620
621
            // Message
622
            $conf = sprintf(
623
                $msg,
624
                GeneralUtility::fixed_lgd_cs($selRecTitle, 30),
625
                GeneralUtility::fixed_lgd_cs($thisRecTitle, 30),
626
                GeneralUtility::fixed_lgd_cs($columnLabel, 30)
627
            );
628
        } else {
629
            $conf = '';
630
        }
631
        return $conf;
632
    }
633
634
    /**
635
     * Clipboard label - getting from "EXT:core/Resources/Private/Language/locallang_core.xlf:"
636
     *
637
     * @param string $key Label Key
638
     * @return string
639
     */
640
    public function clLabel($key)
641
    {
642
        return htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:' . $key));
643
    }
644
645
    /*****************************************
646
     *
647
     * Helper functions
648
     *
649
     ****************************************/
650
    /**
651
     * Removes element on clipboard
652
     *
653
     * @param string $el Key of element in ->clipData array
654
     */
655
    public function removeElement($el)
656
    {
657
        unset($this->clipData[$this->current]['el'][$el]);
658
        $this->changed = true;
659
    }
660
661
    /**
662
     * Saves the clipboard, no questions asked.
663
     * Use ->endClipboard normally (as it checks if changes has been done so saving is necessary)
664
     *
665
     * @internal
666
     */
667
    public function saveClipboard()
668
    {
669
        $this->getBackendUser()->pushModuleData('clipboard', $this->clipData);
670
    }
671
672
    /**
673
     * Returns the current mode, 'copy' or 'cut'
674
     *
675
     * @return string "copy" or "cut
676
     */
677
    public function currentMode()
678
    {
679
        return ($this->clipData[$this->current]['mode'] ?? '') === 'copy' ? 'copy' : 'cut';
680
    }
681
682
    /**
683
     * This traverses the elements on the current clipboard pane
684
     * and unsets elements which does not exist anymore or are disabled.
685
     */
686
    public function cleanCurrent()
687
    {
688
        if (is_array($this->clipData[$this->current]['el'] ?? false)) {
689
            foreach ($this->clipData[$this->current]['el'] as $k => $v) {
690
                [$table, $uid] = explode('|', $k);
691
                if ($table !== '_FILE') {
692
                    if (!$v || !is_array(BackendUtility::getRecord($table, (int)$uid, 'uid'))) {
693
                        unset($this->clipData[$this->current]['el'][$k]);
694
                        $this->changed = true;
695
                    }
696
                } else {
697
                    if (!$v) {
698
                        unset($this->clipData[$this->current]['el'][$k]);
699
                        $this->changed = true;
700
                    } else {
701
                        try {
702
                            $this->resourceFactory->retrieveFileOrFolderObject($v);
703
                        } catch (ResourceDoesNotExistException $e) {
704
                            // The file has been deleted in the meantime, so just remove it silently
705
                            unset($this->clipData[$this->current]['el'][$k]);
706
                        }
707
                    }
708
                }
709
            }
710
        }
711
    }
712
713
    /**
714
     * Counts the number of elements from the table $matchTable. If $matchTable is blank, all tables (except '_FILE' of course) is counted.
715
     *
716
     * @param string $matchTable Table to match/count for.
717
     * @param string $pad Can optionally be used to set another pad than the current.
718
     * @return array Array with keys from the CB.
719
     */
720
    public function elFromTable($matchTable = '', $pad = '')
721
    {
722
        $pad = $pad ?: $this->current;
723
        $list = [];
724
        if (is_array($this->clipData[$pad]['el'] ?? false)) {
725
            foreach ($this->clipData[$pad]['el'] as $k => $v) {
726
                if ($v) {
727
                    [$table, $uid] = explode('|', $k);
728
                    if ($table !== '_FILE') {
729
                        if ((!$matchTable || (string)$table == (string)$matchTable) && $GLOBALS['TCA'][$table]) {
730
                            $list[$k] = $pad === 'normal' ? $v : $uid;
731
                        }
732
                    } else {
733
                        if ((string)$table == (string)$matchTable) {
734
                            $list[$k] = $v;
735
                        }
736
                    }
737
                }
738
            }
739
        }
740
        return $list;
741
    }
742
743
    /**
744
     * Verifies if the item $table/$uid is on the current pad.
745
     * If the pad is "normal" and the element exists, the mode value is returned. Thus you'll know if the item was copied or cut.
746
     *
747
     * @param string $table Table name, (_FILE for files...)
748
     * @param int $uid Element uid (path for files)
749
     * @return string
750
     */
751
    public function isSelected($table, $uid)
752
    {
753
        $k = $table . '|' . $uid;
754
        return !empty($this->clipData[$this->current]['el'][$k]) ? ($this->current === 'normal' ? $this->currentMode() : 1) : '';
755
    }
756
757
    /**
758
     * Returns the first element on the current clipboard
759
     * Makes sense only for DB records - not files!
760
     *
761
     * @return array Element record with extra field _RECORD_TITLE set to the title of the record
762
     */
763
    public function getSelectedRecord()
764
    {
765
        $elArr = $this->elFromTable('');
766
        reset($elArr);
767
        [$table, $uid] = explode('|', (string)key($elArr));
768
        if ($this->isSelected($table, (int)$uid)) {
769
            $selRec = BackendUtility::getRecordWSOL($table, (int)$uid);
770
            $selRec['_RECORD_TITLE'] = BackendUtility::getRecordTitle($table, $selRec);
771
            return $selRec;
772
        }
773
        return [];
774
    }
775
776
    /**
777
     * Reports if the current pad has elements (does not check file/DB type OR if file/DBrecord exists or not. Only counting array)
778
     *
779
     * @return bool TRUE if elements exist.
780
     */
781
    public function isElements()
782
    {
783
        return is_array($this->clipData[$this->current]['el'] ?? null) && !empty($this->clipData[$this->current]['el']);
784
    }
785
786
    /**
787
     * Applies the proper paste configuration in the $cmd array send to SimpleDataHandlerController (tce_db route)
788
     * $ref is the target, see description below.
789
     * The current pad is pasted
790
     *
791
     * $ref: [tablename]:[paste-uid].
792
     * Tablename is the name of the table from which elements *on the current clipboard* is pasted with the 'pid' paste-uid.
793
     * No tablename means that all items on the clipboard (non-files) are pasted. This requires paste-uid to be positive though.
794
     * so 'tt_content:-3'	means 'paste tt_content elements on the clipboard to AFTER tt_content:3 record
795
     * 'tt_content:30'	means 'paste tt_content elements on the clipboard into page with id 30
796
     * ':30'	means 'paste ALL database elements on the clipboard into page with id 30
797
     * ':-30'	not valid.
798
     *
799
     * @param string $ref [tablename]:[paste-uid], see description
800
     * @param array $CMD Command-array
801
     * @param array|null $update If additional values should get set in the copied/moved record this will be an array containing key=>value pairs
802
     * @return array Modified Command-array
803
     */
804
    public function makePasteCmdArray($ref, $CMD, array $update = null)
805
    {
806
        [$pTable, $pUid] = explode('|', $ref);
807
        $pUid = (int)$pUid;
808
        // pUid must be set and if pTable is not set (that means paste ALL elements)
809
        // the uid MUST be positive/zero (pointing to page id)
810
        if ($pTable || $pUid >= 0) {
811
            $elements = $this->elFromTable($pTable);
812
            // So the order is preserved.
813
            $elements = array_reverse($elements);
814
            $mode = $this->currentMode() === 'copy' ? 'copy' : 'move';
815
            // Traverse elements and make CMD array
816
            foreach ($elements as $tP => $value) {
817
                [$table, $uid] = explode('|', $tP);
818
                if (!is_array($CMD[$table])) {
819
                    $CMD[$table] = [];
820
                }
821
                if (is_array($update)) {
822
                    $CMD[$table][$uid][$mode] = [
823
                        'action' => 'paste',
824
                        'target' => $pUid,
825
                        'update' => $update,
826
                    ];
827
                } else {
828
                    $CMD[$table][$uid][$mode] = $pUid;
829
                }
830
                if ($mode === 'move') {
831
                    $this->removeElement($tP);
832
                }
833
            }
834
            $this->endClipboard();
835
        }
836
        return $CMD;
837
    }
838
839
    /**
840
     * Delete record entries in CMD array
841
     *
842
     * @param array $CMD Command-array
843
     * @return array Modified Command-array
844
     */
845
    public function makeDeleteCmdArray($CMD)
846
    {
847
        // all records
848
        $elements = $this->elFromTable('');
849
        foreach ($elements as $tP => $value) {
850
            [$table, $uid] = explode('|', $tP);
851
            if (!is_array($CMD[$table])) {
852
                $CMD[$table] = [];
853
            }
854
            $CMD[$table][$uid]['delete'] = 1;
855
            $this->removeElement($tP);
856
        }
857
        $this->endClipboard();
858
        return $CMD;
859
    }
860
861
    /*****************************************
862
     *
863
     * FOR USE IN tce_file.php:
864
     *
865
     ****************************************/
866
    /**
867
     * Applies the proper paste configuration in the $file array send to tce_file.php.
868
     * The current pad is pasted
869
     *
870
     * @param string $ref Reference to element (splitted by "|")
871
     * @param array $FILE Command-array
872
     * @return array Modified Command-array
873
     */
874
    public function makePasteCmdArray_file($ref, $FILE)
875
    {
876
        $pUid = explode('|', $ref)[1];
877
        $elements = $this->elFromTable('_FILE');
878
        $mode = $this->currentMode() === 'copy' ? 'copy' : 'move';
879
        // Traverse elements and make CMD array
880
        foreach ($elements as $tP => $path) {
881
            $FILE[$mode][] = ['data' => $path, 'target' => $pUid];
882
            if ($mode === 'move') {
883
                $this->removeElement($tP);
884
            }
885
        }
886
        $this->endClipboard();
887
        return $FILE;
888
    }
889
890
    /**
891
     * Delete files in CMD array
892
     *
893
     * @param array $FILE Command-array
894
     * @return array Modified Command-array
895
     */
896
    public function makeDeleteCmdArray_file($FILE)
897
    {
898
        $elements = $this->elFromTable('_FILE');
899
        // Traverse elements and make CMD array
900
        foreach ($elements as $tP => $path) {
901
            $FILE['delete'][] = ['data' => $path];
902
            $this->removeElement($tP);
903
        }
904
        $this->endClipboard();
905
        return $FILE;
906
    }
907
908
    /**
909
     * Returns LanguageService
910
     *
911
     * @return LanguageService
912
     */
913
    protected function getLanguageService()
914
    {
915
        return $GLOBALS['LANG'];
916
    }
917
918
    /**
919
     * Returns the current BE user.
920
     *
921
     * @return BackendUserAuthentication
922
     */
923
    protected function getBackendUser()
924
    {
925
        return $GLOBALS['BE_USER'];
926
    }
927
928
    /**
929
     * returns a new standalone view, shorthand function
930
     */
931
    protected function getStandaloneView(): StandaloneView
932
    {
933
        $view = GeneralUtility::makeInstance(StandaloneView::class);
934
        $view->setLayoutRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Layouts')]);
935
        $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
936
        $view->setTemplateRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates')]);
937
938
        $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/Clipboard/Main.html'));
939
940
        $view->getRequest()->setControllerExtensionName('Backend');
941
        return $view;
942
    }
943
}
944