b3dce7788c2dac02646825a0018439d88fc7a631
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Clipboard / Clipboard.php
1 <?php
2 namespace TYPO3\CMS\Backend\Clipboard;
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 TYPO3\CMS\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Imaging\Icon;
19 use TYPO3\CMS\Core\Imaging\IconFactory;
20 use TYPO3\CMS\Core\Resource\ResourceFactory;
21 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25
26 /**
27 * TYPO3 clipboard for records and files
28 */
29 class Clipboard
30 {
31 /**
32 * @var int
33 */
34 public $numberTabs = 3;
35
36 /**
37 * Clipboard data kept here
38 *
39 * Keys:
40 * 'normal'
41 * 'tab_[x]' where x is >=1 and denotes the pad-number
42 * 'mode' : 'copy' means copy-mode, default = moving ('cut')
43 * 'el' : Array of elements:
44 * DB: keys = '[tablename]|[uid]' eg. 'tt_content:123'
45 * DB: values = 1 (basically insignificant)
46 * FILE: keys = '_FILE|[shortmd5 of path]' eg. '_FILE|9ebc7e5c74'
47 * FILE: values = The full filepath, eg. '/www/htdocs/typo3/32/dummy/fileadmin/sem1_3_examples/alternative_index.php'
48 * or 'C:/www/htdocs/typo3/32/dummy/fileadmin/sem1_3_examples/alternative_index.php'
49 *
50 * 'current' pointer to current tab (among the above...)
51 *
52 * The virtual tablename '_FILE' will always indicate files/folders. When checking for elements from eg. 'all tables'
53 * (by using an empty string) '_FILE' entries are excluded (so in effect only DB elements are counted)
54 *
55 * @var array
56 */
57 public $clipData = array();
58
59 /**
60 * @var int
61 */
62 public $changed = 0;
63
64 /**
65 * @var string
66 */
67 public $current = '';
68
69 /**
70 * @var int
71 */
72 public $lockToNormal = 0;
73
74 /**
75 * If set, clipboard is displaying files.
76 *
77 * @var int
78 */
79 public $fileMode = 0;
80
81 /**
82 * @var IconFactory
83 */
84 protected $iconFactory;
85
86 /**
87 * Construct
88 */
89 public function __construct()
90 {
91 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
92 }
93
94 /*****************************************
95 *
96 * Initialize
97 *
98 ****************************************/
99 /**
100 * Initialize the clipboard from the be_user session
101 *
102 * @return void
103 */
104 public function initializeClipboard()
105 {
106 // Get data
107 $clipData = $this->getBackendUser()->getModuleData('clipboard', $this->getBackendUser()->getTSConfigVal('options.saveClipboard') ? '' : 'ses');
108 // NumberTabs
109 $clNP = $this->getBackendUser()->getTSConfigVal('options.clipboardNumberPads');
110 if (MathUtility::canBeInterpretedAsInteger($clNP) && $clNP >= 0) {
111 $this->numberTabs = MathUtility::forceIntegerInRange($clNP, 0, 20);
112 }
113 // Resets/reinstates the clipboard pads
114 $this->clipData['normal'] = is_array($clipData['normal']) ? $clipData['normal'] : array();
115 for ($a = 1; $a <= $this->numberTabs; $a++) {
116 $this->clipData['tab_' . $a] = is_array($clipData['tab_' . $a]) ? $clipData['tab_' . $a] : array();
117 }
118 // Setting the current pad pointer ($this->current))
119 $this->clipData['current'] = ($this->current = isset($this->clipData[$clipData['current']]) ? $clipData['current'] : 'normal');
120 }
121
122 /**
123 * Call this method after initialization if you want to lock the clipboard to operate on the normal pad only.
124 * Trying to switch pad through ->setCmd will not work.
125 * This is used by the clickmenu since it only allows operation on single elements at a time (that is the "normal" pad)
126 *
127 * @return void
128 */
129 public function lockToNormal()
130 {
131 $this->lockToNormal = 1;
132 $this->current = 'normal';
133 }
134
135 /**
136 * The array $cmd may hold various keys which notes some action to take.
137 * Normally perform only one action at a time.
138 * In scripts like db_list.php / filelist/mod1/index.php the GET-var CB is used to control the clipboard.
139 *
140 * Selecting / Deselecting elements
141 * Array $cmd['el'] has keys = element-ident, value = element value (see description of clipData array in header)
142 * Selecting elements for 'copy' should be done by simultaneously setting setCopyMode.
143 *
144 * @param array $cmd Array of actions, see function description
145 * @return void
146 */
147 public function setCmd($cmd)
148 {
149 if (is_array($cmd['el'])) {
150 foreach ($cmd['el'] as $k => $v) {
151 if ($this->current == 'normal') {
152 unset($this->clipData['normal']);
153 }
154 if ($v) {
155 $this->clipData[$this->current]['el'][$k] = $v;
156 } else {
157 $this->removeElement($k);
158 }
159 $this->changed = 1;
160 }
161 }
162 // Change clipboard pad (if not locked to normal)
163 if ($cmd['setP']) {
164 $this->setCurrentPad($cmd['setP']);
165 }
166 // Remove element (value = item ident: DB; '[tablename]|[uid]' FILE: '_FILE|[shortmd5 hash of path]'
167 if ($cmd['remove']) {
168 $this->removeElement($cmd['remove']);
169 $this->changed = 1;
170 }
171 // Remove all on current pad (value = pad-ident)
172 if ($cmd['removeAll']) {
173 $this->clipData[$cmd['removeAll']] = array();
174 $this->changed = 1;
175 }
176 // Set copy mode of the tab
177 if (isset($cmd['setCopyMode'])) {
178 $this->clipData[$this->current]['mode'] = $this->isElements() ? ($cmd['setCopyMode'] ? 'copy' : '') : '';
179 $this->changed = 1;
180 }
181 }
182
183 /**
184 * Setting the current pad on clipboard
185 *
186 * @param string $padIdent Key in the array $this->clipData
187 * @return void
188 */
189 public function setCurrentPad($padIdent)
190 {
191 // Change clipboard pad (if not locked to normal)
192 if (!$this->lockToNormal && $this->current != $padIdent) {
193 if (isset($this->clipData[$padIdent])) {
194 $this->clipData['current'] = ($this->current = $padIdent);
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 = 1;
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 * @return void
209 */
210 public function endClipboard()
211 {
212 if ($this->changed) {
213 $this->saveClipboard();
214 }
215 $this->changed = 0;
216 }
217
218 /**
219 * Cleans up an incoming element array $CBarr (Array selecting/deselecting elements)
220 *
221 * @param array $CBarr Element array from outside ("key" => "selected/deselected")
222 * @param string $table The 'table which is allowed'. Must be set.
223 * @param bool|int $removeDeselected Can be set in order to remove entries which are marked for deselection.
224 * @return array Processed input $CBarr
225 */
226 public function cleanUpCBC($CBarr, $table, $removeDeselected = 0)
227 {
228 if (is_array($CBarr)) {
229 foreach ($CBarr as $k => $v) {
230 $p = explode('|', $k);
231 if ((string)$p[0] != (string)$table || $removeDeselected && !$v) {
232 unset($CBarr[$k]);
233 }
234 }
235 }
236 return $CBarr;
237 }
238
239 /*****************************************
240 *
241 * Clipboard HTML renderings
242 *
243 ****************************************/
244 /**
245 * Prints the clipboard
246 *
247 * @return string HTML output
248 */
249 public function printClipboard()
250 {
251 $out = array();
252 $elementCount = count($this->elFromTable($this->fileMode ? '_FILE' : ''));
253 // Button/menu header:
254 $removeAllUrl = GeneralUtility::linkThisScript(array('CB' => array('removeAll' => $this->current)));
255 // Copymode Selector menu
256 $copymodeUrl = GeneralUtility::linkThisScript();
257 $moveLabel = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_misc.xlf:moveElements'));
258 $copyLabel = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_misc.xlf:copyElements'));
259
260 $copymodeSelector = '
261 <div class="btn-group">
262 <button class="btn btn-default dropdown-toggle" type="button" id="copymodeSelector" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
263 ' . ($this->currentMode() === 'copy' ? $copyLabel : $moveLabel) . '
264 <span class="caret"></span>
265 </button>
266 <ul class="dropdown-menu" aria-labelledby="copymodeSelector">
267 <li><a href="#" onclick="document.getElementById(\'clipboard_form\').method=\'POST\'; document.getElementById(\'clipboard_form\').action=' . htmlspecialchars(GeneralUtility::quoteJSvalue($copymodeUrl . '&CB[setCopyMode]=')) . '; document.getElementById(\'clipboard_form\').submit(); return true;">' . $moveLabel . '</a></li>
268 <li><a href="#" onclick="document.getElementById(\'clipboard_form\').method=\'POST\'; document.getElementById(\'clipboard_form\').action=' . htmlspecialchars(GeneralUtility::quoteJSvalue($copymodeUrl . '&CB[setCopyMode]=1')) . '; document.getElementById(\'clipboard_form\').submit(); return true;">' . $copyLabel . '</a></li>
269 </ul>
270 </div>
271 ';
272
273 // Selector menu + clear button
274 $optionArray = array();
275 // Import / Export link:
276 if ($elementCount && ExtensionManagementUtility::isLoaded('impexp')) {
277 $url = BackendUtility::getModuleUrl('xMOD_tximpexp', $this->exportClipElementParameters());
278 $optionArray[] = '<li><a href="#" onclick="' . htmlspecialchars(('window.location.href=' . GeneralUtility::quoteJSvalue($url) . ';')) . '">' . $this->clLabel('export', 'rm') . '</a></li>';
279 }
280 // Edit:
281 if (!$this->fileMode && $elementCount) {
282 $optionArray[] = '<li><a href="#" onclick="' . htmlspecialchars(('window.location.href=' . GeneralUtility::quoteJSvalue($this->editUrl() . '&returnUrl=') . '+top.rawurlencode(window.location.href);')) . '">' . $this->clLabel('edit', 'rm') . '</a></li>';
283 }
284
285 $deleteLink = '';
286 $menuSelector = '';
287 if ($elementCount) {
288 // Delete:
289 $deleteLink = '<a class="btn btn-danger" href="' . htmlspecialchars($removeAllUrl) . '#clip_head" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:buttons.clear', true) . '">' . $this->iconFactory->getIcon('actions-document-close', Icon::SIZE_SMALL)->render() . '</a>';
290 if ($this->getBackendUser()->jsConfirmation(JsConfirmation::DELETE)) {
291 $js = '
292 if (confirm(' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:mess.deleteClip'), $elementCount)) . ')){
293 window.location.href=' . GeneralUtility::quoteJSvalue($this->deleteUrl(0, ($this->fileMode ? 1 : 0)) . '&redirect=') . '+top.rawurlencode(window.location.href);
294 }
295 ';
296 } else {
297 $js = ' window.location.href=' . GeneralUtility::quoteJSvalue($this->deleteUrl(0, ($this->fileMode ? 1 : 0)) . '&redirect=') . '+top.rawurlencode(window.location.href); ';
298 }
299 $optionArray[] = '<li><a href="#" onclick="' . htmlspecialchars($js) . '">' . $this->clLabel('delete', 'rm') . '</a></li>';
300
301 // menuSelector
302 $menuSelector = '
303 <div class="btn-group">
304 <button class="btn btn-default dropdown-toggle" type="button" id="menuSelector" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
305 ' . $this->clLabel('menu', 'rm') . '
306 <span class="caret"></span>
307 </button>
308 <ul class="dropdown-menu" aria-labelledby="menuSelector">
309 ' . implode('', $optionArray) . '
310 </ul>
311 </div>
312 ';
313 }
314
315 $out[] = '
316 <tr>
317 <td colspan="2" nowrap="nowrap" width="95%">' . $copymodeSelector . ' ' . $menuSelector . '</td>
318 <td nowrap="nowrap" class="col-control">' . $deleteLink . '</td>
319 </tr>';
320
321 // Print header and content for the NORMAL tab:
322 // check for current item so it can be wrapped in strong tag
323 $current = ($this->current == 'normal');
324 $out[] = '
325 <tr>
326 <td colspan="3"><a href="' . htmlspecialchars(GeneralUtility::linkThisScript(array('CB' => array('setP' => 'normal')))) . '#clip_head" title="' . $this->clLabel('normal-description') . '">'
327 . '<span class="t3-icon fa ' . ($current ? 'fa-check-circle' : 'fa-circle-o') . '"></span>'
328 . $this->padTitleWrap($this->clLabel('normal'), 'normal', $current)
329 . '</a></td>
330 </tr>';
331 if ($this->current == 'normal') {
332 $out = array_merge($out, $this->printContentFromTab('normal'));
333 }
334 // Print header and content for the NUMERIC tabs:
335 for ($a = 1; $a <= $this->numberTabs; $a++) {
336 // check for current item so it can be wrapped in strong tag
337 $current = ($this->current == 'tab_' . $a);
338 $out[] = '
339 <tr>
340 <td colspan="3"><a href="' . htmlspecialchars(GeneralUtility::linkThisScript(array('CB' => array('setP' => ('tab_' . $a))))) . '#clip_head" title="' . $this->clLabel('cliptabs-description') . '">'
341 . '<span class="t3-icon fa ' . ($current ? 'fa-check-circle' : 'fa-circle-o') . '"></span>'
342 . $this->padTitleWrap(sprintf($this->clLabel('cliptabs-name'), $a), ('tab_' . $a), $current)
343 . '</a></td>
344 </tr>';
345 if ($this->current == 'tab_' . $a) {
346 $out = array_merge($out, $this->printContentFromTab('tab_' . $a));
347 }
348 }
349 // Wrap accumulated rows in a table:
350 $output = '<a name="clip_head"></a>
351
352 <!--
353 TYPO3 Clipboard:
354 -->
355 <div class="row">
356 <div class="col-sm-12">
357 <div class="panel panel-default">
358 <div class="panel-heading">' . BackendUtility::wrapInHelp('xMOD_csh_corebe', 'list_clipboard', $this->clLabel('clipboard', 'buttons')) . '</div>
359 <table class="table">
360 ' . implode('', $out) . '
361 </table>
362 </div>
363 </div>
364 </div>
365 ';
366 // Wrap in form tag:
367 $output = '<form action="" id="clipboard_form">' . $output . '</form>';
368 // Return the accumulated content:
369 return $output;
370 }
371
372 /**
373 * Print the content on a pad. Called from ->printClipboard()
374 *
375 * @access private
376 * @param string $pad Pad reference
377 * @return array Array with table rows for the clipboard.
378 */
379 public function printContentFromTab($pad)
380 {
381 $lines = array();
382 if (is_array($this->clipData[$pad]['el'])) {
383 foreach ($this->clipData[$pad]['el'] as $k => $v) {
384 if ($v) {
385 list($table, $uid) = explode('|', $k);
386 $bgColClass = $table == '_FILE' && $this->fileMode || $table != '_FILE' && !$this->fileMode ? 'bgColor4-20' : 'bgColor4';
387 // Rendering files/directories on the clipboard
388 if ($table == '_FILE') {
389 $fileObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($v);
390 if ($fileObject) {
391 $thumb = '';
392 $folder = $fileObject instanceof \TYPO3\CMS\Core\Resource\Folder;
393 $size = $folder ? '' : '(' . GeneralUtility::formatSize($fileObject->getSize()) . 'bytes)';
394 $icon = '<span title="' . htmlspecialchars($fileObject->getName() . ' ' . $size) . '">' . $this->iconFactory->getIconForResource($fileObject, Icon::SIZE_SMALL)->render() . '</span>';
395 if (!$folder && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $fileObject->getExtension())) {
396 $processedFile = $fileObject->process(\TYPO3\CMS\Core\Resource\ProcessedFile::CONTEXT_IMAGEPREVIEW, array());
397 if ($processedFile) {
398 $thumbUrl = $processedFile->getPublicUrl(true);
399 $thumb = '<br /><img src="' . htmlspecialchars($thumbUrl) . '" ' .
400 'width="' . $processedFile->getProperty('width') . '" ' .
401 'height="' . $processedFile->getProperty('height') . '" ' .
402 'title="' . htmlspecialchars($fileObject->getName()) . '" alt="" />';
403 }
404 }
405 $lines[] = '
406 <tr>
407 <td nowrap="nowrap" class="col-icon">' . $icon . '</td>
408 <td nowrap="nowrap" width="95%">' . $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs($fileObject->getName(), $this->getBackendUser()->uc['titleLen'])), $fileObject->getName()) . ($pad == 'normal' ? ' <strong>(' . ($this->clipData['normal']['mode'] == 'copy' ? $this->clLabel('copy', 'cm') : $this->clLabel('cut', 'cm')) . ')</strong>' : '') . '&nbsp;' . $thumb . '</td>
409 <td nowrap="nowrap" class="col-control">
410 <div class="btn-group">
411 <a class="btn btn-default" href="#" onclick="' . htmlspecialchars(('top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($v) . '); return false;')) . '"title="' . $this->clLabel('info', 'cm') . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>' . '<a class="btn btn-default" href="' . htmlspecialchars($this->removeUrl('_FILE', GeneralUtility::shortmd5($v))) . '#clip_head" title="' . $this->clLabel('removeItem') . '">' . $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render() . '</a>
412 </div>
413 </td>
414 </tr>';
415 } else {
416 // If the file did not exist (or is illegal) then it is removed from the clipboard immediately:
417 unset($this->clipData[$pad]['el'][$k]);
418 $this->changed = 1;
419 }
420 } else {
421 // Rendering records:
422 $rec = BackendUtility::getRecordWSOL($table, $uid);
423 if (is_array($rec)) {
424 $lines[] = '
425 <tr>
426 <td nowrap="nowrap" class="col-icon">' . $this->linkItemText($this->iconFactory->getIconForRecord($table, $rec, Icon::SIZE_SMALL)->render(), $rec, $table) . '</td>
427 <td nowrap="nowrap" width="95%">' . $this->linkItemText(htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $rec), $this->getBackendUser()->uc['titleLen'])), $rec, $table) . ($pad == 'normal' ? ' <strong>(' . ($this->clipData['normal']['mode'] == 'copy' ? $this->clLabel('copy', 'cm') : $this->clLabel('cut', 'cm')) . ')</strong>' : '') . '&nbsp;</td>
428 <td nowrap="nowrap" class="col-control">
429 <div class="btn-group">
430 <a class="btn btn-default" href="#" onclick="' . htmlspecialchars(('top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', \'' . (int)$uid . '\'); return false;')) . '" title="' . $this->clLabel('info', 'cm') . '">' . $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render() . '</a>' . '<a class="btn btn-default" href="' . htmlspecialchars($this->removeUrl($table, $uid)) . '#clip_head" title="' . $this->clLabel('removeItem') . '">' . $this->iconFactory->getIcon('actions-selection-delete', Icon::SIZE_SMALL)->render() . '</a>
431 </div>
432 </td>
433 </tr>';
434 $localizationData = $this->getLocalizations($table, $rec, $bgColClass, $pad);
435 if ($localizationData) {
436 $lines[] = $localizationData;
437 }
438 } else {
439 unset($this->clipData[$pad]['el'][$k]);
440 $this->changed = 1;
441 }
442 }
443 }
444 }
445 }
446 $this->endClipboard();
447 return $lines;
448 }
449
450 /**
451 * Returns true if the clipboard contains elements
452 *
453 * @return bool
454 */
455 public function hasElements()
456 {
457 foreach ($this->clipData as $data) {
458 if (isset($data['el']) && is_array($data['el']) && !empty($data['el'])) {
459 return true;
460 }
461 }
462
463 return false;
464 }
465
466 /**
467 * Gets all localizations of the current record.
468 *
469 * @param string $table The table
470 * @param array $parentRec The current record
471 * @param string $bgColClass Class for the background color of a column
472 * @param string $pad Pad reference
473 * @return string HTML table rows
474 */
475 public function getLocalizations($table, $parentRec, $bgColClass, $pad)
476 {
477 $lines = array();
478 $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
479 if ($table != 'pages' && BackendUtility::isTableLocalizable($table) && !$tcaCtrl['transOrigPointerTable']) {
480 $where = array();
481 $where[] = $tcaCtrl['transOrigPointerField'] . '=' . (int)$parentRec['uid'];
482 $where[] = $tcaCtrl['languageField'] . '<>0';
483 if (isset($tcaCtrl['delete']) && $tcaCtrl['delete']) {
484 $where[] = $tcaCtrl['delete'] . '=0';
485 }
486 if (isset($tcaCtrl['versioningWS']) && $tcaCtrl['versioningWS']) {
487 $where[] = 't3ver_wsid=' . $parentRec['t3ver_wsid'];
488 }
489 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows('*', $table, implode(' AND ', $where));
490 if (is_array($rows)) {
491 $modeData = '';
492 if ($pad == 'normal') {
493 $mode = $this->clipData['normal']['mode'] == 'copy' ? 'copy' : 'cut';
494 $modeData = ' <strong>(' . $this->clLabel($mode, 'cm') . ')</strong>';
495 }
496 foreach ($rows as $rec) {
497 $lines[] = '
498 <tr>
499 <td nowrap="nowrap" class="col-icon">' . $this->iconFactory->getIconForRecord($table, $rec, Icon::SIZE_SMALL)->render() . '</td>
500 <td nowrap="nowrap" width="95%">' . htmlspecialchars(GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $rec), $this->getBackendUser()->uc['titleLen'])) . $modeData . '</td>
501 <td nowrap="nowrap" class="col-control"></td>
502 </tr>';
503 }
504 }
505 }
506 return implode('', $lines);
507 }
508
509 /**
510 * Wraps title of pad in bold-tag and maybe the number of elements if any.
511 * Only applies bold-tag if the item is active
512 *
513 * @param string $str String (already htmlspecialchars()'ed)
514 * @param string $pad Pad reference
515 * @param bool $active is currently active
516 * @return string HTML output (htmlspecialchar'ed content inside of tags.)
517 */
518 public function padTitleWrap($str, $pad, $active)
519 {
520 $el = count($this->elFromTable($this->fileMode ? '_FILE' : '', $pad));
521 if ($el) {
522 $str .= ' (' . ($pad == 'normal' ? ($this->clipData['normal']['mode'] == 'copy' ? $this->clLabel('copy', 'cm') : $this->clLabel('cut', 'cm')) : htmlspecialchars($el)) . ')';
523 }
524 if ($active === true) {
525 return '<strong>' . $str . '</strong>';
526 } else {
527 return '<span class="text-muted">' . $str . '</span>';
528 }
529 }
530
531 /**
532 * Wraps the title of the items listed in link-tags. The items will link to the page/folder where they originate from
533 *
534 * @param string $str Title of element - must be htmlspecialchar'ed on beforehand.
535 * @param mixed $rec If array, a record is expected. If string, its a path
536 * @param string $table Table name
537 * @return string
538 */
539 public function linkItemText($str, $rec, $table = '')
540 {
541 if (is_array($rec) && $table) {
542 if ($this->fileMode) {
543 $str = '<span class="text-muted">' . $str . '</span>';
544 } else {
545 $str = '<a href="' . htmlspecialchars(BackendUtility::getModuleUrl('web_list', array('id' => $rec['pid']))) . '">' . $str . '</a>';
546 }
547 } elseif (file_exists($rec)) {
548 if (!$this->fileMode) {
549 $str = '<span class="text-muted">' . $str . '</span>';
550 } else {
551 if (ExtensionManagementUtility::isLoaded('filelist')) {
552 $str = '<a href="' . htmlspecialchars(BackendUtility::getModuleUrl('file_list', array('id' => dirname($rec)))) . '">' . $str . '</a>';
553 }
554 }
555 }
556 return $str;
557 }
558
559 /**
560 * Returns the select-url for database elements
561 *
562 * @param string $table Table name
563 * @param int $uid Uid of record
564 * @param bool|int $copy If set, copymode will be enabled
565 * @param bool|int $deselect If set, the link will deselect, otherwise select.
566 * @param array $baseArray The base array of GET vars to be sent in addition. Notice that current GET vars WILL automatically be included.
567 * @return string URL linking to the current script but with the CB array set to select the element with table/uid
568 */
569 public function selUrlDB($table, $uid, $copy = 0, $deselect = 0, $baseArray = array())
570 {
571 $CB = array('el' => array(rawurlencode($table . '|' . $uid) => $deselect ? 0 : 1));
572 if ($copy) {
573 $CB['setCopyMode'] = 1;
574 }
575 $baseArray['CB'] = $CB;
576 return GeneralUtility::linkThisScript($baseArray);
577 }
578
579 /**
580 * Returns the select-url for files
581 *
582 * @param string $path Filepath
583 * @param bool|int $copy If set, copymode will be enabled
584 * @param bool|int $deselect If set, the link will deselect, otherwise select.
585 * @param array $baseArray The base array of GET vars to be sent in addition. Notice that current GET vars WILL automatically be included.
586 * @return string URL linking to the current script but with the CB array set to select the path
587 */
588 public function selUrlFile($path, $copy = 0, $deselect = 0, $baseArray = array())
589 {
590 $CB = array('el' => array(rawurlencode('_FILE|' . GeneralUtility::shortmd5($path)) => $deselect ? '' : $path));
591 if ($copy) {
592 $CB['setCopyMode'] = 1;
593 }
594 $baseArray['CB'] = $CB;
595 return GeneralUtility::linkThisScript($baseArray);
596 }
597
598 /**
599 * pasteUrl of the element (database and file)
600 * For the meaning of $table and $uid, please read from ->makePasteCmdArray!!!
601 * The URL will point to tce_file or tce_db depending in $table
602 *
603 * @param string $table Tablename (_FILE for files)
604 * @param mixed $uid "destination": can be positive or negative indicating how the paste is done (paste into / paste after)
605 * @param bool $setRedirect If set, then the redirect URL will point back to the current script, but with CB reset.
606 * @param array|NULL $update Additional key/value pairs which should get set in the moved/copied record (via DataHandler)
607 * @return string
608 */
609 public function pasteUrl($table, $uid, $setRedirect = true, array $update = null)
610 {
611 $urlParameters = [
612 'vC' => $this->getBackendUser()->veriCode(),
613 'prErr' => 1,
614 'uPT' => 1,
615 'CB[paste]' => $table . '|' . $uid,
616 'CB[pad]' => $this->current
617 ];
618 if ($setRedirect) {
619 $urlParameters['redirect'] = GeneralUtility::linkThisScript(array('CB' => ''));
620 }
621 if (is_array($update)) {
622 $urlParameters['CB[update]'] = $update;
623 }
624 return BackendUtility::getModuleUrl($table === '_FILE' ? 'tce_file' : 'tce_db', $urlParameters);
625 }
626
627 /**
628 * deleteUrl for current pad
629 *
630 * @param bool|int $setRedirect If set, then the redirect URL will point back to the current script, but with CB reset.
631 * @param bool|int $file If set, then the URL will link to the tce_file.php script in the typo3/ dir.
632 * @return string
633 */
634 public function deleteUrl($setRedirect = 1, $file = 0)
635 {
636 $urlParameters = [
637 'vC' => $this->getBackendUser()->veriCode(),
638 'prErr' => 1,
639 'uPT' => 1,
640 'CB[delete]' => 1,
641 'CB[pad]' => $this->current
642 ];
643 if ($setRedirect) {
644 $urlParameters['redirect'] = GeneralUtility::linkThisScript(array('CB' => ''));
645 }
646 return BackendUtility::getModuleUrl($file ? 'tce_file' : 'tce_db', $urlParameters);
647 }
648
649 /**
650 * editUrl of all current elements
651 * ONLY database
652 * Links to FormEngine
653 *
654 * @return string The URL to FormEngine with parameters.
655 */
656 public function editUrl()
657 {
658 $parameters = array();
659 // All records
660 $elements = $this->elFromTable('');
661 foreach ($elements as $tP => $value) {
662 list($table, $uid) = explode('|', $tP);
663 $parameters['edit[' . $table . '][' . $uid . ']'] = 'edit';
664 }
665 return BackendUtility::getModuleUrl('record_edit', $parameters);
666 }
667
668 /**
669 * Returns the remove-url (file and db)
670 * for file $table='_FILE' and $uid = shortmd5 hash of path
671 *
672 * @param string $table Tablename
673 * @param string $uid Uid integer/shortmd5 hash
674 * @return string URL
675 */
676 public function removeUrl($table, $uid)
677 {
678 return GeneralUtility::linkThisScript(array('CB' => array('remove' => $table . '|' . $uid)));
679 }
680
681 /**
682 * Returns confirm JavaScript message
683 *
684 * @param string $table Table name
685 * @param mixed $rec For records its an array, for files its a string (path)
686 * @param string $type Type-code
687 * @param array $clElements Array of selected elements
688 * @param string $columnLabel Name of the content column
689 * @return string JavaScript "confirm" message
690 */
691 public function confirmMsg($table, $rec, $type, $clElements, $columnLabel = '')
692 {
693 if ($this->getBackendUser()->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
694 $labelKey = 'LLL:EXT:lang/locallang_core.xlf:mess.' . ($this->currentMode() == 'copy' ? 'copy' : 'move') . ($this->current == 'normal' ? '' : 'cb') . '_' . $type;
695 $msg = $this->getLanguageService()->sL($labelKey . ($columnLabel ? '_colPos': ''));
696 if ($table == '_FILE') {
697 $thisRecTitle = basename($rec);
698 if ($this->current == 'normal') {
699 $selItem = reset($clElements);
700 $selRecTitle = basename($selItem);
701 } else {
702 $selRecTitle = count($clElements);
703 }
704 } else {
705 $thisRecTitle = $table == 'pages' && !is_array($rec) ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] : BackendUtility::getRecordTitle($table, $rec);
706 if ($this->current == 'normal') {
707 $selItem = $this->getSelectedRecord();
708 $selRecTitle = $selItem['_RECORD_TITLE'];
709 } else {
710 $selRecTitle = count($clElements);
711 }
712 }
713 // @TODO
714 // This can get removed as soon as the "_colPos" label is translated
715 // into all available locallang languages.
716 if (!$msg && $columnLabel) {
717 $thisRecTitle .= ' | ' . $columnLabel;
718 $msg = $this->getLanguageService()->sL($labelKey);
719 }
720
721 // Message
722 $conf = 'confirm(' . GeneralUtility::quoteJSvalue(sprintf($msg, GeneralUtility::fixed_lgd_cs($selRecTitle, 30), GeneralUtility::fixed_lgd_cs($thisRecTitle, 30), GeneralUtility::fixed_lgd_cs($columnLabel, 30))) . ')';
723 } else {
724 $conf = '';
725 }
726 return $conf;
727 }
728
729 /**
730 * Clipboard label - getting from "EXT:lang/locallang_core.xlf:"
731 *
732 * @param string $key Label Key
733 * @param string $Akey Alternative key to "labels
734 * @return string
735 */
736 public function clLabel($key, $Akey = 'labels')
737 {
738 return htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:' . $Akey . '.' . $key));
739 }
740
741 /**
742 * Creates GET parameters for linking to the export module.
743 *
744 * @return array GET parameters for current clipboard content to be exported
745 */
746 protected function exportClipElementParameters()
747 {
748 // Init
749 $pad = $this->current;
750 $params = array();
751 $params['tx_impexp[action]'] = 'export';
752 // Traverse items:
753 if (is_array($this->clipData[$pad]['el'])) {
754 foreach ($this->clipData[$pad]['el'] as $k => $v) {
755 if ($v) {
756 list($table, $uid) = explode('|', $k);
757 // Rendering files/directories on the clipboard
758 if ($table == '_FILE') {
759 if (file_exists($v) && GeneralUtility::isAllowedAbsPath($v)) {
760 $params['tx_impexp[' . (is_dir($v) ? 'dir' : 'file') . '][]'] = $v;
761 }
762 } else {
763 // Rendering records:
764 $rec = BackendUtility::getRecord($table, $uid);
765 if (is_array($rec)) {
766 $params['tx_impexp[record][]'] = $table . ':' . $uid;
767 }
768 }
769 }
770 }
771 }
772 return $params;
773 }
774
775 /*****************************************
776 *
777 * Helper functions
778 *
779 ****************************************/
780 /**
781 * Removes element on clipboard
782 *
783 * @param string $el Key of element in ->clipData array
784 * @return void
785 */
786 public function removeElement($el)
787 {
788 unset($this->clipData[$this->current]['el'][$el]);
789 $this->changed = 1;
790 }
791
792 /**
793 * Saves the clipboard, no questions asked.
794 * Use ->endClipboard normally (as it checks if changes has been done so saving is necessary)
795 *
796 * @access private
797 * @return void
798 */
799 public function saveClipboard()
800 {
801 $this->getBackendUser()->pushModuleData('clipboard', $this->clipData);
802 }
803
804 /**
805 * Returns the current mode, 'copy' or 'cut'
806 *
807 * @return string "copy" or "cut
808 */
809 public function currentMode()
810 {
811 return $this->clipData[$this->current]['mode'] == 'copy' ? 'copy' : 'cut';
812 }
813
814 /**
815 * This traverses the elements on the current clipboard pane
816 * and unsets elements which does not exist anymore or are disabled.
817 *
818 * @return void
819 */
820 public function cleanCurrent()
821 {
822 if (is_array($this->clipData[$this->current]['el'])) {
823 foreach ($this->clipData[$this->current]['el'] as $k => $v) {
824 list($table, $uid) = explode('|', $k);
825 if ($table != '_FILE') {
826 if (!$v || !is_array(BackendUtility::getRecord($table, $uid, 'uid'))) {
827 unset($this->clipData[$this->current]['el'][$k]);
828 $this->changed = 1;
829 }
830 } else {
831 if (!$v) {
832 unset($this->clipData[$this->current]['el'][$k]);
833 $this->changed = 1;
834 } else {
835 try {
836 ResourceFactory::getInstance()->retrieveFileOrFolderObject($v);
837 } catch (\TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException $e) {
838 // The file has been deleted in the meantime, so just remove it silently
839 unset($this->clipData[$this->current]['el'][$k]);
840 }
841 }
842 }
843 }
844 }
845 }
846
847 /**
848 * Counts the number of elements from the table $matchTable. If $matchTable is blank, all tables (except '_FILE' of course) is counted.
849 *
850 * @param string $matchTable Table to match/count for.
851 * @param string $pad Can optionally be used to set another pad than the current.
852 * @return array Array with keys from the CB.
853 */
854 public function elFromTable($matchTable = '', $pad = '')
855 {
856 $pad = $pad ? $pad : $this->current;
857 $list = array();
858 if (is_array($this->clipData[$pad]['el'])) {
859 foreach ($this->clipData[$pad]['el'] as $k => $v) {
860 if ($v) {
861 list($table, $uid) = explode('|', $k);
862 if ($table != '_FILE') {
863 if ((!$matchTable || (string)$table == (string)$matchTable) && $GLOBALS['TCA'][$table]) {
864 $list[$k] = $pad == 'normal' ? $v : $uid;
865 }
866 } else {
867 if ((string)$table == (string)$matchTable) {
868 $list[$k] = $v;
869 }
870 }
871 }
872 }
873 }
874 return $list;
875 }
876
877 /**
878 * Verifies if the item $table/$uid is on the current pad.
879 * If the pad is "normal", the mode value is returned if the element existed. Thus you'll know if the item was copy or cut moded...
880 *
881 * @param string $table Table name, (_FILE for files...)
882 * @param int $uid Element uid (path for files)
883 * @return string
884 */
885 public function isSelected($table, $uid)
886 {
887 $k = $table . '|' . $uid;
888 return $this->clipData[$this->current]['el'][$k] ? ($this->current == 'normal' ? $this->currentMode() : 1) : '';
889 }
890
891 /**
892 * Returns item record $table,$uid if selected on current clipboard
893 * If table and uid is blank, the first element is returned.
894 * Makes sense only for DB records - not files!
895 *
896 * @param string $table Table name
897 * @param int|string $uid Element uid
898 * @return array Element record with extra field _RECORD_TITLE set to the title of the record
899 */
900 public function getSelectedRecord($table = '', $uid = '')
901 {
902 if (!$table && !$uid) {
903 $elArr = $this->elFromTable('');
904 reset($elArr);
905 list($table, $uid) = explode('|', key($elArr));
906 }
907 if ($this->isSelected($table, $uid)) {
908 $selRec = BackendUtility::getRecordWSOL($table, $uid);
909 $selRec['_RECORD_TITLE'] = BackendUtility::getRecordTitle($table, $selRec);
910 return $selRec;
911 }
912 return array();
913 }
914
915 /**
916 * Reports if the current pad has elements (does not check file/DB type OR if file/DBrecord exists or not. Only counting array)
917 *
918 * @return bool TRUE if elements exist.
919 */
920 public function isElements()
921 {
922 return is_array($this->clipData[$this->current]['el']) && !empty($this->clipData[$this->current]['el']);
923 }
924
925 /*****************************************
926 *
927 * FOR USE IN tce_db.php:
928 *
929 ****************************************/
930 /**
931 * Applies the proper paste configuration in the $cmd array send to tce_db.php.
932 * $ref is the target, see description below.
933 * The current pad is pasted
934 *
935 * $ref: [tablename]:[paste-uid].
936 * Tablename is the name of the table from which elements *on the current clipboard* is pasted with the 'pid' paste-uid.
937 * No tablename means that all items on the clipboard (non-files) are pasted. This requires paste-uid to be positive though.
938 * so 'tt_content:-3' means 'paste tt_content elements on the clipboard to AFTER tt_content:3 record
939 * 'tt_content:30' means 'paste tt_content elements on the clipboard into page with id 30
940 * ':30' means 'paste ALL database elements on the clipboard into page with id 30
941 * ':-30' not valid.
942 *
943 * @param string $ref [tablename]:[paste-uid], see description
944 * @param array $CMD Command-array
945 * @param NULL|array If additional values should get set in the copied/moved record this will be an array containing key=>value pairs
946 * @return array Modified Command-array
947 */
948 public function makePasteCmdArray($ref, $CMD, array $update = null)
949 {
950 list($pTable, $pUid) = explode('|', $ref);
951 $pUid = (int)$pUid;
952 // pUid must be set and if pTable is not set (that means paste ALL elements)
953 // the uid MUST be positive/zero (pointing to page id)
954 if ($pTable || $pUid >= 0) {
955 $elements = $this->elFromTable($pTable);
956 // So the order is preserved.
957 $elements = array_reverse($elements);
958 $mode = $this->currentMode() == 'copy' ? 'copy' : 'move';
959 // Traverse elements and make CMD array
960 foreach ($elements as $tP => $value) {
961 list($table, $uid) = explode('|', $tP);
962 if (!is_array($CMD[$table])) {
963 $CMD[$table] = array();
964 }
965 if (is_array($update)) {
966 $CMD[$table][$uid][$mode] = array(
967 'action' => 'paste',
968 'target' => $pUid,
969 'update' => $update,
970 );
971 } else {
972 $CMD[$table][$uid][$mode] = $pUid;
973 }
974 if ($mode == 'move') {
975 $this->removeElement($tP);
976 }
977 }
978 $this->endClipboard();
979 }
980 return $CMD;
981 }
982
983 /**
984 * Delete record entries in CMD array
985 *
986 * @param array $CMD Command-array
987 * @return array Modified Command-array
988 */
989 public function makeDeleteCmdArray($CMD)
990 {
991 // all records
992 $elements = $this->elFromTable('');
993 foreach ($elements as $tP => $value) {
994 list($table, $uid) = explode('|', $tP);
995 if (!is_array($CMD[$table])) {
996 $CMD[$table] = array();
997 }
998 $CMD[$table][$uid]['delete'] = 1;
999 $this->removeElement($tP);
1000 }
1001 $this->endClipboard();
1002 return $CMD;
1003 }
1004
1005 /*****************************************
1006 *
1007 * FOR USE IN tce_file.php:
1008 *
1009 ****************************************/
1010 /**
1011 * Applies the proper paste configuration in the $file array send to tce_file.php.
1012 * The current pad is pasted
1013 *
1014 * @param string $ref Reference to element (splitted by "|")
1015 * @param array $FILE Command-array
1016 * @return array Modified Command-array
1017 */
1018 public function makePasteCmdArray_file($ref, $FILE)
1019 {
1020 list($pTable, $pUid) = explode('|', $ref);
1021 $elements = $this->elFromTable('_FILE');
1022 $mode = $this->currentMode() == 'copy' ? 'copy' : 'move';
1023 // Traverse elements and make CMD array
1024 foreach ($elements as $tP => $path) {
1025 $FILE[$mode][] = array('data' => $path, 'target' => $pUid);
1026 if ($mode == 'move') {
1027 $this->removeElement($tP);
1028 }
1029 }
1030 $this->endClipboard();
1031 return $FILE;
1032 }
1033
1034 /**
1035 * Delete files in CMD array
1036 *
1037 * @param array $FILE Command-array
1038 * @return array Modified Command-array
1039 */
1040 public function makeDeleteCmdArray_file($FILE)
1041 {
1042 $elements = $this->elFromTable('_FILE');
1043 // Traverse elements and make CMD array
1044 foreach ($elements as $tP => $path) {
1045 $FILE['delete'][] = array('data' => $path);
1046 $this->removeElement($tP);
1047 }
1048 $this->endClipboard();
1049 return $FILE;
1050 }
1051
1052 /**
1053 * Returns LanguageService
1054 *
1055 * @return \TYPO3\CMS\Lang\LanguageService
1056 */
1057 protected function getLanguageService()
1058 {
1059 return $GLOBALS['LANG'];
1060 }
1061
1062 /**
1063 * Returns the current BE user.
1064 *
1065 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1066 */
1067 protected function getBackendUser()
1068 {
1069 return $GLOBALS['BE_USER'];
1070 }
1071
1072 /**
1073 * Return DatabaseConnection
1074 *
1075 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
1076 */
1077 protected function getDatabaseConnection()
1078 {
1079 return $GLOBALS['TYPO3_DB'];
1080 }
1081 }