[BUGFIX] Use correct icon for mounting as tree root in context menu
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / ClickMenu / ClickMenu.php
1 <?php
2 namespace TYPO3\CMS\Backend\ClickMenu;
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\Clipboard\Clipboard;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Imaging\Icon;
21 use TYPO3\CMS\Core\Imaging\IconFactory;
22 use TYPO3\CMS\Core\Resource\Folder;
23 use TYPO3\CMS\Core\Resource\ResourceFactory;
24 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation;
25 use TYPO3\CMS\Core\Type\Bitmask\Permission;
26 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
27 use TYPO3\CMS\Core\Utility\GeneralUtility;
28 use TYPO3\CMS\Lang\LanguageService;
29
30 /**
31 * Class for generating the click menu
32 * @internal
33 */
34 class ClickMenu
35 {
36 /**
37 * Defines if the click menu is first level or second.
38 * Second means the click menu is triggered from another menu.
39 *
40 * @var int
41 */
42 public $cmLevel = 0;
43
44 /**
45 * Clipboard array (submitted by eg. pressing the paste button)
46 *
47 * @var bool
48 */
49 public $CB;
50
51 /**
52 * If set, the calling document should be in the listframe of a frameset.
53 *
54 * @var bool
55 */
56 public $listFrame = false;
57
58 /**
59 * If set, the menu is about database records, not files. (set if part 2 [1] of the item-var is NOT blank)
60 *
61 * @var bool
62 */
63 public $isDBmenu = false;
64
65 /**
66 * Stores the parts of the input $item string, splitted by "|":
67 * [0] = table/file, [1] = uid/blank, [2] = flag: If set, listFrame,
68 * If "2" then "content frame" is forced [3] = ("+" prefix = disable
69 * all by default, enable these. Default is to disable) Items key list
70 *
71 * @var array
72 */
73 public $iParts = [];
74
75 /**
76 * Contains list of keywords of items to disable in the menu
77 *
78 * @var array
79 */
80 public $disabledItems = [];
81
82 /**
83 * If TRUE, Show icons on the left.
84 *
85 * @var bool
86 */
87 public $leftIcons = false;
88
89 /**
90 * Array of classes to be used for user processing of the menu content.
91 * This is for the API of adding items to the menu from outside.
92 *
93 * @var array
94 */
95 public $extClassArray = [];
96
97 /**
98 * Set, when edit icon is drawn.
99 *
100 * @var bool
101 */
102 public $editPageIconSet = false;
103
104 /**
105 * Set to TRUE, if editing of the element is OK.
106 *
107 * @var bool
108 */
109 public $editOK = false;
110
111 /**
112 * The current record
113 *
114 * @var array
115 */
116 public $rec = [];
117
118 /**
119 * Clipboard set from the outside
120 * Declared as public for now, should become protected
121 * soon-ish
122 * @var Clipboard;
123 */
124 public $clipObj;
125
126 /**
127 * The current page record
128 * @var array
129 */
130 protected $pageinfo;
131
132 /**
133 * Language Service property. Used to access localized labels
134 *
135 * @var LanguageService
136 */
137 protected $languageService;
138
139 /**
140 * @var BackendUserAuthentication
141 */
142 protected $backendUser;
143
144 /**
145 * @var IconFactory
146 */
147 protected $iconFactory;
148
149 /**
150 * @param LanguageService $languageService Language Service to inject
151 * @param BackendUserAuthentication $backendUser
152 */
153 public function __construct(LanguageService $languageService = null, BackendUserAuthentication $backendUser = null)
154 {
155 $this->languageService = $languageService ?: $GLOBALS['LANG'];
156 $this->backendUser = $backendUser ?: $GLOBALS['BE_USER'];
157 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
158 }
159
160 /**
161 * Initialize click menu
162 *
163 * @return array the array to be returned as JSON including all click menu items
164 */
165 public function init()
166 {
167 $CMcontent = '';
168 // Setting GPvars:
169 $this->cmLevel = (int)GeneralUtility::_GP('cmLevel');
170 $this->CB = GeneralUtility::_GP('CB');
171
172 // Deal with Drag&Drop context menus
173 if ((string)GeneralUtility::_GP('dragDrop') !== '') {
174 return $this->printDragDropClickMenu(GeneralUtility::_GP('dragDrop'), GeneralUtility::_GP('srcId'), GeneralUtility::_GP('dstId'));
175 }
176 // Can be set differently as well
177 $this->iParts[0] = GeneralUtility::_GP('table');
178 $this->iParts[1] = GeneralUtility::_GP('uid');
179 $this->iParts[2] = GeneralUtility::_GP('listFr');
180 $this->iParts[3] = GeneralUtility::_GP('enDisItems');
181 // Setting flags:
182 if ($this->iParts[2]) {
183 $this->listFrame = true;
184 }
185 if (isset($this->iParts[1]) && $this->iParts[1] !== '') {
186 $this->isDBmenu = true;
187 }
188 $TSkey = ($this->isDBmenu ? 'page' : 'folder') . ($this->listFrame ? 'List' : 'Tree');
189 $this->disabledItems = GeneralUtility::trimExplode(',', $this->backendUser->getTSConfigVal('options.contextMenu.' . $TSkey . '.disableItems'), true);
190 $this->leftIcons = (bool)$this->backendUser->getTSConfigVal('options.contextMenu.options.leftIcons');
191 // &cmLevel flag detected (2nd level menu)
192 if (!$this->cmLevel) {
193 // Make 1st level clickmenu:
194 if ($this->isDBmenu) {
195 $CMcontent = $this->printDBClickMenu($this->iParts[0], $this->iParts[1]);
196 } else {
197 $CMcontent = $this->printFileClickMenu($this->iParts[0]);
198 }
199 } else {
200 // Make 2nd level clickmenu (only for DBmenus)
201 if ($this->isDBmenu) {
202 $CMcontent = $this->printNewDBLevel($this->iParts[0], $this->iParts[1]);
203 }
204 }
205 // Return clickmenu content:
206 return $CMcontent;
207 }
208
209 /***************************************
210 *
211 * DATABASE
212 *
213 ***************************************/
214 /**
215 * Make 1st level clickmenu:
216 *
217 * @param string $table Table name
218 * @param int $uid UID for the current record.
219 * @return array the array to be returned as JSON
220 */
221 public function printDBClickMenu($table, $uid)
222 {
223 $uid = (int)$uid;
224 // Get record:
225 $this->rec = BackendUtility::getRecordWSOL($table, $uid);
226 $menuItems = [];
227 $root = 0;
228 $DBmount = false;
229 // Rootlevel
230 if ($table === 'pages' && $uid === 0) {
231 $root = 1;
232 }
233 // DB mount
234 if ($table === 'pages' && in_array($uid, $this->backendUser->returnWebmounts())) {
235 $DBmount = true;
236 }
237 // Used to hide cut,copy icons for l10n-records
238 $l10nOverlay = false;
239 // Should only be performed for overlay-records within the same table
240 if (BackendUtility::isTableLocalizable($table) && !isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable'])) {
241 $l10nOverlay = (int)$this->rec[$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']] != 0;
242 }
243 // If record found (or root), go ahead and fill the $menuItems array which will contain data for the elements to render.
244 if (is_array($this->rec) || $root) {
245 // Get permissions
246 $lCP = $this->backendUser->calcPerms(BackendUtility::getRecord('pages', $table === 'pages' ? (int)$this->rec['uid'] : (int)$this->rec['pid']));
247 // View
248 if (!in_array('view', $this->disabledItems, true)) {
249 if ($table === 'pages') {
250 $menuItems['view'] = $this->DB_view($uid);
251 }
252 if ($table === 'tt_content') {
253 $ws_rec = BackendUtility::getRecordWSOL($table, (int)$this->rec['uid']);
254 $menuItems['view'] = $this->DB_view((int)$ws_rec['pid']);
255 }
256 }
257 // Edit:
258 if (!$root && ($this->backendUser->isPSet($lCP, $table, 'edit') || $this->backendUser->isPSet($lCP, $table, 'editcontent'))) {
259 if (!in_array('edit', $this->disabledItems, true)) {
260 $menuItems['edit'] = $this->DB_edit($table, $uid);
261 }
262 $this->editOK = true;
263 }
264 // New:
265 if (!in_array('new', $this->disabledItems, true) && $this->backendUser->isPSet($lCP, $table, 'new')) {
266 $menuItems['new'] = $this->DB_new($table, $uid);
267 }
268 // Info:
269 if (!in_array('info', $this->disabledItems, true) && !$root) {
270 $menuItems['info'] = $this->DB_info($table, $uid);
271 }
272 $menuItems['spacer1'] = 'spacer';
273 // Copy:
274 if (!in_array('copy', $this->disabledItems, true) && !$root && !$DBmount && !$l10nOverlay) {
275 $menuItems['copy'] = $this->DB_copycut($table, $uid, 'copy');
276 }
277 // Cut:
278 if (!in_array('cut', $this->disabledItems, true) && !$root && !$DBmount && !$l10nOverlay && $this->editOK) {
279 $menuItems['cut'] = $this->DB_copycut($table, $uid, 'cut');
280 }
281 // Paste:
282 $elFromAllTables = count($this->clipObj->elFromTable(''));
283 if (!in_array('paste', $this->disabledItems, true) && $elFromAllTables) {
284 $selItem = $this->clipObj->getSelectedRecord();
285 $elInfo = [
286 GeneralUtility::fixed_lgd_cs($selItem['_RECORD_TITLE'], $this->backendUser->uc['titleLen']),
287 $root ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] : GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $this->rec), $this->backendUser->uc['titleLen']),
288 $this->clipObj->currentMode()
289 ];
290 if ($table === 'pages' && $lCP & Permission::PAGE_NEW) {
291 if ($elFromAllTables) {
292 $menuItems['pasteinto'] = $this->DB_paste('', $uid, 'into', $elInfo);
293 }
294 }
295 $elFromTable = count($this->clipObj->elFromTable($table));
296 if (!$root && !$DBmount && $elFromTable && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
297 $menuItems['pasteafter'] = $this->DB_paste($table, -$uid, 'after', $elInfo);
298 }
299 }
300
301 // Delete:
302 $elInfo = [GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $this->rec), $this->backendUser->uc['titleLen'])];
303 if (!in_array('delete', $this->disabledItems, true) && !$root && !$DBmount && $this->backendUser->isPSet($lCP, $table, 'delete')) {
304 $menuItems['spacer2'] = 'spacer';
305 $menuItems['delete'] = $this->DB_delete($table, $uid, $elInfo);
306 }
307 if (!in_array('history', $this->disabledItems, true)) {
308 $menuItems['history'] = $this->DB_history($table, $uid);
309 }
310
311 $localItems = [];
312 if (!$this->cmLevel && !in_array('moreoptions', $this->disabledItems, true)) {
313 // Creating menu items here:
314 if ($this->editOK) {
315 $localItems['spacer3'] = 'spacer';
316 $localItems['moreoptions'] = $this->linkItem(
317 $this->label('more') . '&nbsp;&nbsp;<span class="fa fa-caret-right"></span>',
318 '<span class="fa fa-fw"></span>',
319 'TYPO3.ClickMenu.fetch(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript() . '&cmLevel=1&subname=moreoptions') . ');return false;',
320 false,
321 true
322 );
323 $menuItemHideUnhideAllowed = false;
324 $hiddenField = '';
325 // Check if column for disabled is defined
326 if (isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'])) {
327 $hiddenField = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']['disabled'];
328 if (
329 $hiddenField !== '' && !empty($GLOBALS['TCA'][$table]['columns'][$hiddenField]['exclude'])
330 && $this->backendUser->check('non_exclude_fields', $table . ':' . $hiddenField)
331 ) {
332 $menuItemHideUnhideAllowed = true;
333 }
334 }
335 if ($menuItemHideUnhideAllowed && !in_array('hide', $this->disabledItems, true)) {
336 $localItems['hide'] = $this->DB_hideUnhide($table, $this->rec, $hiddenField);
337 }
338 $anyEnableColumnsFieldAllowed = false;
339 // Check if columns are defined
340 if (isset($GLOBALS['TCA'][$table]['ctrl']['enablecolumns'])) {
341 $columnsToCheck = $GLOBALS['TCA'][$table]['ctrl']['enablecolumns'];
342 if ($table === 'pages' && !empty($columnsToCheck)) {
343 $columnsToCheck[] = 'extendToSubpages';
344 }
345 foreach ($columnsToCheck as $currentColumn) {
346 if (
347 !empty($GLOBALS['TCA'][$table]['columns'][$currentColumn]['exclude'])
348 && $this->backendUser->check('non_exclude_fields', $table . ':' . $currentColumn)
349 ) {
350 $anyEnableColumnsFieldAllowed = true;
351 }
352 }
353 }
354 if ($anyEnableColumnsFieldAllowed && !in_array('edit_access', $this->disabledItems, true)) {
355 $localItems['edit_access'] = $this->DB_editAccess($table, $uid);
356 }
357 if ($table === 'pages' && $this->editPageIconSet && !in_array('edit_pageproperties', $this->disabledItems, true)) {
358 $localItems['edit_pageproperties'] = $this->DB_editPageProperties($uid);
359 }
360 }
361 // Find delete element among the input menu items and insert the local items just before that:
362 $c = 0;
363 $deleteFound = false;
364 foreach ($menuItems as $key => $value) {
365 $c++;
366 if ($key === 'delete') {
367 $deleteFound = true;
368 break;
369 }
370 }
371 if ($deleteFound) {
372 // .. subtract two... (delete item + its spacer element...)
373 $c -= 2;
374 // and insert the items just before the delete element.
375 array_splice($menuItems, $c, 0, $localItems);
376 } else {
377 $menuItems = array_merge($menuItems, $localItems);
378 }
379 }
380 }
381 // Adding external elements to the menuItems array
382 $menuItems = $this->processingByExtClassArray($menuItems, $table, $uid);
383 // Processing by external functions?
384 $menuItems = $this->externalProcessingOfDBMenuItems($menuItems);
385 if (!is_array($this->rec)) {
386 $this->rec = [];
387 }
388
389 // Return the printed elements:
390 return $this->printItems($menuItems);
391 }
392
393 /**
394 * Make 2nd level clickmenu (only for DBmenus)
395 *
396 * @param string $table Table name
397 * @param int $uid UID for the current record.
398 * @return array the array to be returned as JSON
399 */
400 public function printNewDBLevel($table, $uid)
401 {
402 $localItems = [];
403 $uid = (int)$uid;
404 // Setting internal record to the table/uid :
405 $this->rec = BackendUtility::getRecordWSOL($table, $uid);
406 $menuItems = [];
407 $root = 0;
408 // Rootlevel
409 if ($table === 'pages' && $uid === 0) {
410 $root = 1;
411 }
412 // If record was found, check permissions and get menu items.
413 if (is_array($this->rec) || $root) {
414 $lCP = $this->backendUser->calcPerms(BackendUtility::getRecord('pages', $table === 'pages' ? (int)$this->rec['uid'] : (int)$this->rec['pid']));
415 // Edit:
416 if (!$root && ($this->backendUser->isPSet($lCP, $table, 'edit') || $this->backendUser->isPSet($lCP, $table, 'editcontent'))) {
417 $this->editOK = true;
418 }
419 $menuItems = $this->processingByExtClassArray($menuItems, $table, $uid);
420 }
421
422 $subname = GeneralUtility::_GP('subname');
423 if ($subname === 'moreoptions') {
424 // If the page can be edited, then show this:
425 if ($this->editOK) {
426 if (($table === 'pages' || $table === 'tt_content') && !in_array('move_wizard', $this->disabledItems, true)) {
427 $localItems['move_wizard'] = $this->DB_moveWizard($table, $uid, $this->rec);
428 }
429 if (($table === 'pages' || $table === 'tt_content') && !in_array('new_wizard', $this->disabledItems, true)) {
430 $localItems['new_wizard'] = $this->DB_newWizard($table, $uid, $this->rec);
431 }
432 if ($table === 'pages' && !in_array('perms', $this->disabledItems, true) && $this->backendUser->check('modules', 'system_BeuserTxPermission')) {
433 $localItems['perms'] = $this->DB_perms($table, $uid, $this->rec);
434 }
435 if (!in_array('db_list', $this->disabledItems, true) && $this->backendUser->check('modules', 'web_list')) {
436 $localItems['db_list'] = $this->DB_db_list($table, $uid, $this->rec);
437 }
438 }
439 // Temporary mount point item:
440 if ($table === 'pages') {
441 $localItems['temp_mount_point'] = $this->DB_tempMountPoint($uid);
442 }
443 // Merge the locally created items into the current menu items passed to this function.
444 $menuItems = array_merge($menuItems, $localItems);
445 }
446
447 // Return the printed elements:
448 if (!is_array($menuItems)) {
449 $menuItems = [];
450 }
451 return $this->printItems($menuItems);
452 }
453
454 /**
455 * Processing the $menuItems array (for extension classes) (DATABASE RECORDS)
456 *
457 * @param array $menuItems Array for manipulation.
458 * @return array Processed $menuItems array
459 */
460 public function externalProcessingOfDBMenuItems($menuItems)
461 {
462 return $menuItems;
463 }
464
465 /**
466 * Processing the $menuItems array by external classes (typ. adding items)
467 *
468 * @param array $menuItems Array for manipulation.
469 * @param string $table Table name
470 * @param int $uid UID for the current record.
471 * @return array Processed $menuItems array
472 */
473 public function processingByExtClassArray($menuItems, $table, $uid)
474 {
475 if (is_array($this->extClassArray)) {
476 foreach ($this->extClassArray as $conf) {
477 $obj = GeneralUtility::makeInstance($conf['name']);
478 $menuItems = $obj->main($this, $menuItems, $table, $uid);
479 }
480 }
481 return $menuItems;
482 }
483
484 /**
485 * Returning JavaScript for the onClick event linking to the input URL.
486 *
487 * @param string $url The URL relative to TYPO3_mainDir
488 * @param string $retUrl The return_url-parameter
489 * @param bool $hideCM If set, the "hideCM()" will be called
490 * @param string $overrideLoc If set, gives alternative location to load in (for example top frame or somewhere else)
491 * @return string JavaScript for an onClick event.
492 */
493 public function urlRefForCM($url, $retUrl = '', $hideCM = true, $overrideLoc = '')
494 {
495 $loc = 'top.content.list_frame';
496 return ($overrideLoc ? 'var docRef=' . $overrideLoc : 'var docRef=(top.content.list_frame)?top.content.list_frame:' . $loc)
497 . '; docRef.location.href=' . GeneralUtility::quoteJSvalue($url) . ($retUrl ? '+' . GeneralUtility::quoteJSvalue('&' . $retUrl . '=') . '+top.rawurlencode('
498 . $this->frameLocation('docRef.document') . '.pathname+' . $this->frameLocation('docRef.document') . '.search)' : '')
499 . ';';
500 }
501
502 /**
503 * Adding CM element for Clipboard "copy" and "cut"
504 *
505 * @param string $table Table name
506 * @param int $uid UID for the current record.
507 * @param string $type Type: "copy" or "cut
508 * @return array Item array, element in $menuItems
509 * @internal
510 */
511 public function DB_copycut($table, $uid, $type)
512 {
513 $isSel = '';
514 if ($this->clipObj->current === 'normal') {
515 $isSel = $this->clipObj->isSelected($table, $uid);
516 }
517 $addParam = [];
518 if ($this->listFrame) {
519 $addParam['reloadListFrame'] = 1;
520 }
521 $icon = $this->iconFactory->getIcon('actions-edit-' . $type . ($isSel === $type ? '-release' : ''), Icon::SIZE_SMALL)->render();
522 return $this->linkItem(
523 $this->label($type),
524 $icon,
525 'TYPO3.ClickMenu.fetch(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlDB($table, $uid, ($type === 'copy' ? 1 : 0), ($isSel == $type), $addParam)) . ');return false;'
526 );
527 }
528
529 /**
530 * Adding CM element for Clipboard "paste into"/"paste after"
531 * NOTICE: $table and $uid should follow the special syntax for paste, see clipboard-class :: pasteUrl();
532 *
533 * @param string $table Table name
534 * @param int $uid UID for the current record. NOTICE: Special syntax!
535 * @param string $type Type: "into" or "after
536 * @param array $elInfo Contains instructions about whether to copy or cut an element.
537 * @return array Item array, element in $menuItems
538 * @see \TYPO3\CMS\Backend\Clipboard\Clipboard::pasteUrl()
539 * @internal
540 */
541 public function DB_paste($table, $uid, $type, $elInfo)
542 {
543 $loc = 'top.content.list_frame';
544 $jsCode = $loc . '.location.href='
545 . GeneralUtility::quoteJSvalue($this->clipObj->pasteUrl($table, $uid, 0) . '&redirect=')
546 . ' + top.rawurlencode(' . $this->frameLocation($loc . '.document') . '.pathname+'
547 . $this->frameLocation($loc . '.document') . '.search);';
548
549 if ($this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
550 $title = $this->languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:clip_paste');
551 $lllKey = ($elInfo[2] === 'copy' ? 'copy' : 'move') . '_' . $type;
552 $confirmMessage = sprintf(
553 $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.' . $lllKey),
554 $elInfo[0],
555 $elInfo[1]
556 );
557 $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
558 . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
559 . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
560 . $jsCode
561 . '} top.TYPO3.Modal.dismiss(); });';
562 }
563 $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
564 return $this->linkItem(
565 $this->label('paste' . $type),
566 $this->iconFactory->getIcon('actions-document-paste-' . $type, Icon::SIZE_SMALL)->render(),
567 $editOnClick . 'return false;'
568 );
569 }
570
571 /**
572 * Adding CM element for Info
573 *
574 * @param string $table Table name
575 * @param int $uid UID for the current record.
576 * @return array Item array, element in $menuItems
577 * @internal
578 */
579 public function DB_info($table, $uid)
580 {
581 return $this->linkItem(
582 $this->label('info'),
583 $this->iconFactory->getIcon('actions-document-info', Icon::SIZE_SMALL)->render(),
584 'top.launchView(' . GeneralUtility::quoteJSvalue($table) . ', ' . GeneralUtility::quoteJSvalue($uid) . ');'
585 );
586 }
587
588 /**
589 * Adding CM element for History
590 *
591 * @param string $table Table name
592 * @param int $uid UID for the current record.
593 * @return array Item array, element in $menuItems
594 * @internal
595 */
596 public function DB_history($table, $uid)
597 {
598 $url = BackendUtility::getModuleUrl('record_history', ['element' => $table . ':' . $uid]);
599 return $this->linkItem(
600 htmlspecialchars($this->languageService->getLL('CM_history')),
601 $this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL)->render(),
602 $this->urlRefForCM($url, 'returnUrl')
603 );
604 }
605
606 /**
607 * Adding CM element for Permission setting
608 *
609 * @param string $table Table name
610 * @param int $uid UID for the current record.
611 * @param array $rec The "pages" record with "perms_*" fields inside.
612 * @return array Item array, element in $menuItems
613 * @internal
614 */
615 public function DB_perms($table, $uid, $rec)
616 {
617 if (!ExtensionManagementUtility::isLoaded('beuser')) {
618 return '';
619 }
620
621 $parameters = [
622 'id' => $uid,
623 ];
624
625 if ($rec['perms_userid'] == $this->backendUser->user['uid'] || $this->backendUser->isAdmin()) {
626 $parameters['return_id'] = $uid;
627 $parameters['edit'] = '1';
628 }
629
630 $url = BackendUtility::getModuleUrl('system_BeuserTxPermission', $parameters);
631 return $this->linkItem(
632 htmlspecialchars($this->languageService->getLL('CM_perms')),
633 $this->iconFactory->getIcon('status-status-locked', Icon::SIZE_SMALL)->render(),
634 $this->urlRefForCM($url)
635 );
636 }
637
638 /**
639 * Adding CM element for DBlist
640 *
641 * @param string $table Table name
642 * @param int $uid UID for the current record.
643 * @param array $rec Record of the element (needs "pid" field if not pages-record)
644 * @return array Item array, element in $menuItems
645 * @internal
646 */
647 public function DB_db_list($table, $uid, $rec)
648 {
649 $urlParams = [];
650 $urlParams['id'] = $table === 'pages' ? $uid : $rec['pid'];
651 $urlParams['table'] = $table === 'pages' ? '' : $table;
652 $url = BackendUtility::getModuleUrl('web_list', $urlParams, '', true);
653 return $this->linkItem(
654 htmlspecialchars($this->languageService->getLL('CM_db_list')),
655 $this->iconFactory->getIcon('actions-system-list-open', Icon::SIZE_SMALL)->render(),
656 'top.nextLoadModuleUrl=' . GeneralUtility::quoteJSvalue($url) . ';top.goToModule(\'web_list\', 1);'
657 );
658 }
659
660 /**
661 * Adding CM element for Moving wizard
662 *
663 * @param string $table Table name
664 * @param int $uid UID for the current record.
665 * @param array $rec Record. Needed for tt-content elements which will have the sys_language_uid sent
666 * @return array Item array, element in $menuItems
667 * @internal
668 */
669 public function DB_moveWizard($table, $uid, $rec)
670 {
671 // Hardcoded field for tt_content elements.
672 $url = BackendUtility::getModuleUrl('move_element') . '&table=' . $table . '&uid=' . $uid;
673 $url .= ($table === 'tt_content' ? '&sys_language_uid=' . (int)$rec['sys_language_uid'] : '');
674 return $this->linkItem(
675 htmlspecialchars($this->languageService->getLL('CM_moveWizard' . ($table === 'pages' ? '_page' : ''))),
676 $this->iconFactory->getIcon('actions-' . ($table === 'pages' ? 'page' : 'document') . '-move', Icon::SIZE_SMALL)->render(),
677 $this->urlRefForCM($url, 'returnUrl')
678 );
679 }
680
681 /**
682 * Adding CM element for Create new wizard (either BackendUtility::getModuleUrl('db_new') or BackendUtility::getModuleUrl('new_content_element') or custom wizard)
683 *
684 * @param string $table Table name
685 * @param int $uid UID for the current record.
686 * @param array $rec Record.
687 * @return array Item array, element in $menuItems
688 * @internal
689 */
690 public function DB_newWizard($table, $uid, $rec)
691 {
692 if ($table === 'pages') {
693 $url = BackendUtility::getModuleUrl('db_new', ['id' => $uid, 'pagesOnly' => 1]);
694 } else {
695 // If mod.newContentElementWizard.override is set, use a custom module instead
696 $tsConfig = BackendUtility::getModTSconfig((int)$this->pageinfo['uid'], 'mod');
697 $newContentWizardModuleName = isset($tsConfig['properties']['newContentElementWizard.']['override'])
698 ? $tsConfig['properties']['newContentElementWizard.']['override']
699 : 'new_content_element';
700 $url = BackendUtility::getModuleUrl($newContentWizardModuleName, ['id' => $rec['pid'], 'sys_language_uid' => (int)$rec['sys_language_uid']]);
701 }
702 return $this->linkItem(
703 htmlspecialchars($this->languageService->getLL('CM_newWizard')),
704 $this->iconFactory->getIcon(($table === 'pages' ? 'actions-page-new' : 'actions-document-new'), Icon::SIZE_SMALL)->render(),
705 $this->urlRefForCM($url, 'returnUrl')
706 );
707 }
708
709 /**
710 * Adding CM element for Editing of the access related fields of a table (disable, starttime, endtime, fe_groups)
711 *
712 * @param string $table Table name
713 * @param int $uid UID for the current record.
714 * @return array Item array, element in $menuItems
715 * @internal
716 */
717 public function DB_editAccess($table, $uid)
718 {
719 $url = BackendUtility::getModuleUrl('record_edit', [
720 'columnsOnly' => (implode(',', $GLOBALS['TCA'][$table]['ctrl']['enablecolumns']) . ($table === 'pages' ? ',extendToSubpages' : '')),
721 'edit[' . $table . '][' . $uid . ']' => 'edit'
722 ]);
723 return $this->linkItem(
724 htmlspecialchars($this->languageService->getLL('CM_editAccess')),
725 $this->iconFactory->getIcon('actions-document-edit-access', Icon::SIZE_SMALL)->render(),
726 $this->urlRefForCM($url, 'returnUrl'),
727 1
728 );
729 }
730
731 /**
732 * Adding CM element for edit page properties
733 *
734 * @param int $uid page uid to edit (PID)
735 * @return array Item array, element in $menuItems
736 * @internal
737 */
738 public function DB_editPageProperties($uid)
739 {
740 $url = BackendUtility::getModuleUrl('record_edit', [
741 'edit[pages][' . $uid . ']' => 'edit'
742 ]);
743 return $this->linkItem(
744 htmlspecialchars($this->languageService->getLL('CM_editPageProperties')),
745 $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render(),
746 $this->urlRefForCM($url, 'returnUrl'),
747 1
748 );
749 }
750
751 /**
752 * Adding CM element for regular editing of the element!
753 *
754 * @param string $table Table name
755 * @param int $uid UID for the current record.
756 * @return array Item array, element in $menuItems
757 * @internal
758 */
759 public function DB_edit($table, $uid)
760 {
761 // If another module was specified, replace the default Page module with the new one
762 $newPageModule = trim($this->backendUser->getTSConfigVal('options.overridePageModule'));
763 $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
764 $loc = 'top.content.list_frame';
765 $theIcon = $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render();
766
767 $link = BackendUtility::getModuleUrl('record_edit', [
768 'edit[' . $table . '][' . $uid . ']' => 'edit'
769 ]);
770
771 if ($this->iParts[0] === 'pages' && $this->iParts[1] && $this->backendUser->check('modules', $pageModule)) {
772 $this->editPageIconSet = true;
773 }
774 $editOnClick = 'if(' . $loc . '){' . $loc . '.location.href=' . GeneralUtility::quoteJSvalue($link . '&returnUrl=') . '+top.rawurlencode(' . $this->frameLocation(($loc . '.document')) . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search);}';
775 return $this->linkItem($this->label('edit'), $theIcon, $editOnClick . ';');
776 }
777
778 /**
779 * Adding CM element for regular Create new element
780 *
781 * @param string $table Table name
782 * @param int $uid UID for the current record.
783 * @return array Item array, element in $menuItems
784 * @internal
785 */
786 public function DB_new($table, $uid)
787 {
788 $frame = 'top.content.list_frame';
789 $location = $this->frameLocation($frame . '.document');
790 $module = $this->listFrame
791 ? GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('record_edit', ['edit[' . $table . '][-' . $uid . ']' => 'new']) . '&returnUrl=') . '+top.rawurlencode(' . $location . '.pathname+' . $location . '.search)'
792 : GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('db_new', ['id' => (int)$uid]));
793 $editOnClick = 'if(' . $frame . '){' . $frame . '.location.href=' . $module . ';}';
794 $icon = $this->iconFactory->getIcon('actions-' . ($table === 'pages' ? 'page' : 'document') . '-new', Icon::SIZE_SMALL)->render();
795 return $this->linkItem($this->label('new'), $icon, $editOnClick);
796 }
797
798 /**
799 * Adding CM element for Delete
800 *
801 * @param string $table Table name
802 * @param int $uid UID for the current record.
803 * @param array $elInfo Label for including in the confirmation message, EXT:lang/locallang_core.xlf:mess.delete
804 * @return array Item array, element in $menuItems
805 * @internal
806 */
807 public function DB_delete($table, $uid, $elInfo)
808 {
809 $loc = 'top.content.list_frame';
810 $jsCode = $loc . '.location.href='
811 . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&redirect=')
812 . '+top.rawurlencode(' . $this->frameLocation($loc . '.document') . '.pathname+'
813 . $this->frameLocation($loc . '.document') . '.search)+'
814 . GeneralUtility::quoteJSvalue(
815 '&cmd[' . $table . '][' . $uid . '][delete]=1&prErr=1&vC=' . $this->backendUser->veriCode()
816 );
817
818 if ($table === 'pages') {
819 $jsCode .= 'top.nav.refresh.defer(500, top.nav);';
820 }
821
822 if ($this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
823 $title = $this->languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:delete');
824 $confirmMessage = sprintf(
825 $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.delete'),
826 $elInfo[0]
827 );
828 $confirmMessage .= BackendUtility::referenceCount(
829 $table,
830 $uid,
831 ' ' . $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.referencesToRecord')
832 );
833 $confirmMessage .= BackendUtility::translationCount(
834 $table,
835 $uid,
836 ' ' . $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.translationsOfRecord')
837 );
838 $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
839 . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
840 . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
841 . $jsCode
842 . '} top.TYPO3.Modal.dismiss(); });';
843 }
844 $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
845 return $this->linkItem(
846 $this->label('delete'),
847 $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(),
848 $editOnClick . 'return false;'
849 );
850 }
851
852 /**
853 * Adding CM element for View Page
854 *
855 * @param int $id Page uid (PID)
856 * @param string $anchor Anchor, if any
857 * @return array Item array, element in $menuItems
858 * @internal
859 */
860 public function DB_view($id, $anchor = '')
861 {
862 $icon = $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render();
863 return $this->linkItem($this->label('view'), $icon, BackendUtility::viewOnClick($id, '', null, $anchor) . ';');
864 }
865
866 /**
867 * Adding element for setting temporary mount point.
868 *
869 * @param int $page_id Page uid (PID)
870 * @return array Item array, element in $menuItems
871 * @internal
872 */
873 public function DB_tempMountPoint($page_id)
874 {
875 return $this->linkItem(
876 $this->label('tempMountPoint'),
877 $this->iconFactory->getIcon('actions-pagetree-mountroot', Icon::SIZE_SMALL)->render(),
878 'if (top.content.nav_frame) {
879 var node = top.TYPO3.Backend.NavigationContainer.PageTree.getSelected();
880 if (node === null) {
881 return false;
882 }
883
884 var useNode = {
885 attributes: {
886 nodeData: {
887 id: ' . (int)$page_id . '
888 }
889 }
890 };
891
892 node.ownerTree.commandProvider.mountAsTreeRoot(useNode, node.ownerTree);
893 }
894 ');
895 }
896
897 /**
898 * Adding CM element for hide/unhide of the input record
899 *
900 * @param string $table Table name
901 * @param array $rec Record array
902 * @param string $hideField Name of the hide field
903 * @return array Item array, element in $menuItems
904 * @internal
905 */
906 public function DB_hideUnhide($table, $rec, $hideField)
907 {
908 return $this->DB_changeFlag($table, $rec, $hideField, $this->label(($rec[$hideField] ? 'un' : '') . 'hide'));
909 }
910
911 /**
912 * Adding CM element for a flag field of the input record
913 *
914 * @param string $table Table name
915 * @param array $rec Record array
916 * @param string $flagField Name of the flag field
917 * @param string $title Menu item Title
918 * @return array Item array, element in $menuItems
919 */
920 public function DB_changeFlag($table, $rec, $flagField, $title)
921 {
922 $uid = $rec['_ORIG_uid'] ?: $rec['uid'];
923 $loc = 'top.content.list_frame';
924 $editOnClick = 'if(' . $loc . '){' . $loc . '.location.href=' .
925 GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&redirect=') . '+top.rawurlencode(' .
926 $this->frameLocation($loc . '.document') . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
927 GeneralUtility::quoteJSvalue(
928 '&data[' . $table . '][' . $uid . '][' . $flagField . ']=' . ($rec[$flagField] ? 0 : 1) . '&prErr=1&vC=' . $this->backendUser->veriCode()
929 ) . ';};';
930 if ($table === 'pages') {
931 $editOnClick .= 'top.nav.refresh.defer(500, top.nav);';
932 }
933 return $this->linkItem(
934 $title,
935 $this->iconFactory->getIcon('actions-edit-' . ($rec[$flagField] ? 'un' : '') . 'hide', Icon::SIZE_SMALL)->render(),
936 $editOnClick . 'return false;',
937 1
938 );
939 }
940
941 /***************************************
942 *
943 * FILE
944 *
945 ***************************************/
946 /**
947 * Make 1st level clickmenu:
948 *
949 * @param string $combinedIdentifier The combined identifier
950 * @return string HTML content
951 * @see \TYPO3\CMS\Core\Resource\ResourceFactory::retrieveFileOrFolderObject()
952 */
953 public function printFileClickMenu($combinedIdentifier)
954 {
955 $identifier = '';
956 $menuItems = [];
957 $combinedIdentifier = rawurldecode($combinedIdentifier);
958 $fileObject = ResourceFactory::getInstance()
959 ->retrieveFileOrFolderObject($combinedIdentifier);
960 if ($fileObject) {
961 $folder = false;
962 $isStorageRoot = false;
963 $isOnline = true;
964 $userMayViewStorage = false;
965 $userMayEditStorage = false;
966 $identifier = $fileObject->getCombinedIdentifier();
967 if ($fileObject instanceof Folder) {
968 $folder = true;
969 if ($fileObject->getIdentifier() === $fileObject->getStorage()->getRootLevelFolder()->getIdentifier()) {
970 $isStorageRoot = true;
971 if ($this->backendUser->check('tables_select', 'sys_file_storage')) {
972 $userMayViewStorage = true;
973 }
974 if ($this->backendUser->check('tables_modify', 'sys_file_storage')) {
975 $userMayEditStorage = true;
976 }
977 }
978 if (!$fileObject->getStorage()->isOnline()) {
979 $isOnline = false;
980 }
981 }
982 // Hide
983 if (!in_array('hide', $this->disabledItems, true) && $isStorageRoot && $userMayEditStorage) {
984 $record = BackendUtility::getRecord('sys_file_storage', $fileObject->getStorage()->getUid());
985 $menuItems['hide'] = $this->DB_changeFlag(
986 'sys_file_storage',
987 $record,
988 'is_online',
989 $this->label($record['is_online'] ? 'offline' : 'online')
990 );
991 }
992 // Edit
993 if (!in_array('edit', $this->disabledItems, true) && $fileObject->checkActionPermission('write')) {
994 if (!$folder && !$isStorageRoot && $fileObject->isIndexed() && $this->backendUser->check('tables_modify', 'sys_file_metadata')) {
995 $metaData = $fileObject->_getMetaData();
996 $menuItems['edit2'] = $this->DB_edit('sys_file_metadata', $metaData['uid']);
997 }
998 if (!$folder && GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], $fileObject->getExtension()) && $fileObject->checkActionPermission('write')) {
999 $menuItems['edit'] = $this->FILE_launch($identifier, 'file_edit', 'editcontent', 'actions-page-open');
1000 } elseif ($isStorageRoot && $userMayEditStorage) {
1001 $menuItems['edit'] = $this->DB_edit('sys_file_storage', $fileObject->getStorage()->getUid());
1002 }
1003 }
1004 // Rename
1005 if (!in_array('rename', $this->disabledItems, true) && !$isStorageRoot && $fileObject->checkActionPermission('rename')) {
1006 $menuItems['rename'] = $this->FILE_launch($identifier, 'file_rename', 'rename', 'actions-edit-rename');
1007 }
1008 // Upload
1009 if (!in_array('upload', $this->disabledItems, true) && $folder && $isOnline && $fileObject->checkActionPermission('write')) {
1010 $menuItems['upload'] = $this->FILE_launch($identifier, 'file_upload', 'upload', 'actions-edit-upload');
1011 }
1012 // New
1013 if (!in_array('new', $this->disabledItems, true) && $folder && $isOnline && $fileObject->checkActionPermission('write')) {
1014 $menuItems['new'] = $this->FILE_launch($identifier, 'file_newfolder', 'new', 'actions-document-new');
1015 }
1016 // Info
1017 if (!in_array('info', $this->disabledItems, true) && $fileObject->checkActionPermission('read')) {
1018 if ($isStorageRoot && $userMayViewStorage) {
1019 $menuItems['info'] = $this->DB_info('sys_file_storage', $fileObject->getStorage()->getUid());
1020 } elseif (!$folder) {
1021 $menuItems['info'] = $this->fileInfo($identifier);
1022 }
1023 }
1024 $menuItems[] = 'spacer';
1025 // Copy:
1026 if (!in_array('copy', $this->disabledItems, true) && !$isStorageRoot && $fileObject->checkActionPermission('read')) {
1027 $menuItems['copy'] = $this->FILE_copycut($identifier, 'copy');
1028 }
1029 // Cut:
1030 if (!in_array('cut', $this->disabledItems, true) && !$isStorageRoot && $fileObject->checkActionPermission('move')) {
1031 $menuItems['cut'] = $this->FILE_copycut($identifier, 'cut');
1032 }
1033 // Paste:
1034 $elFromAllTables = count($this->clipObj->elFromTable('_FILE'));
1035 if (!in_array('paste', $this->disabledItems, true) && $elFromAllTables && $folder && $fileObject->checkActionPermission('write')) {
1036 $elArr = $this->clipObj->elFromTable('_FILE');
1037 $selItem = reset($elArr);
1038 $clickedFileOrFolder = ResourceFactory::getInstance()->retrieveFileOrFolderObject($combinedIdentifier);
1039 $fileOrFolderInClipBoard = ResourceFactory::getInstance()->retrieveFileOrFolderObject($selItem);
1040 $elInfo = [
1041 $fileOrFolderInClipBoard->getName(),
1042 $clickedFileOrFolder->getName(),
1043 $this->clipObj->currentMode()
1044 ];
1045 if (!$fileOrFolderInClipBoard instanceof Folder || !$fileOrFolderInClipBoard->getStorage()->isWithinFolder($fileOrFolderInClipBoard, $clickedFileOrFolder)) {
1046 $menuItems['pasteinto'] = $this->FILE_paste($identifier, $selItem, $elInfo);
1047 }
1048 }
1049 $menuItems[] = 'spacer';
1050 // Delete:
1051 if (!in_array('delete', $this->disabledItems, true) && $fileObject->checkActionPermission('delete')) {
1052 if ($isStorageRoot && $userMayEditStorage) {
1053 $elInfo = [GeneralUtility::fixed_lgd_cs($fileObject->getStorage()->getName(), $this->backendUser->uc['titleLen'])];
1054 $menuItems['delete'] = $this->DB_delete('sys_file_storage', $fileObject->getStorage()->getUid(), $elInfo);
1055 } elseif (!$isStorageRoot) {
1056 $menuItems['delete'] = $this->FILE_delete($identifier);
1057 }
1058 }
1059 }
1060 // Adding external elements to the menuItems array
1061 $menuItems = $this->processingByExtClassArray($menuItems, $identifier, 0);
1062 // Processing by external functions?
1063 $menuItems = $this->externalProcessingOfFileMenuItems($menuItems);
1064 // Return the printed elements:
1065 return $this->printItems($menuItems);
1066 }
1067
1068 /**
1069 * Processing the $menuItems array (for extension classes) (FILES)
1070 *
1071 * @param array $menuItems Array for manipulation.
1072 * @return array Processed $menuItems array
1073 */
1074 public function externalProcessingOfFileMenuItems($menuItems)
1075 {
1076 return $menuItems;
1077 }
1078
1079 /**
1080 * Multi-function for adding an entry to the $menuItems array
1081 *
1082 * @param string $path Path to the file/directory (target)
1083 * @param string $moduleName Script (deprecated) or module name (e.g. file_edit) to pass &target= to
1084 * @param string $type "type" is the code which fetches the correct label for the element from "cm.
1085 * @param string $iconName
1086 * @param bool $noReturnUrl If set, the return URL parameter will not be set in the link
1087 * @return array Item array, element in $menuItems
1088 * @internal
1089 */
1090 public function FILE_launch($path, $moduleName, $type, $iconName, $noReturnUrl = false)
1091 {
1092 $loc = 'top.content.list_frame';
1093 $scriptUrl = BackendUtility::getModuleUrl($moduleName);
1094
1095 $editOnClick = 'if(' . $loc . '){' . $loc . '.location.href=' . GeneralUtility::quoteJSvalue($scriptUrl . '&target=' . rawurlencode($path)) . ($noReturnUrl ? '' : '+\'&returnUrl=\'+top.rawurlencode(' . $this->frameLocation($loc . '.document') . '.pathname+' . $this->frameLocation($loc . '.document') . '.search)') . ';}';
1096 return $this->linkItem(
1097 $this->label($type),
1098 $this->iconFactory->getIcon($iconName, Icon::SIZE_SMALL)->render(),
1099 $editOnClick . 'top.nav.refresh();'
1100 );
1101 }
1102
1103 /**
1104 * Returns element for copy or cut of files.
1105 *
1106 * @param string $path Path to the file/directory (target)
1107 * @param string $type Type: "copy" or "cut
1108 * @return array Item array, element in $menuItems
1109 * @internal
1110 */
1111 public function FILE_copycut($path, $type)
1112 {
1113 $isSel = '';
1114 // Pseudo table name for use in the clipboard.
1115 $table = '_FILE';
1116 $uid = GeneralUtility::shortMD5($path);
1117 if ($this->clipObj->current === 'normal') {
1118 $isSel = $this->clipObj->isSelected($table, $uid);
1119 }
1120 $addParam = [];
1121 if ($this->listFrame) {
1122 $addParam['reloadListFrame'] = 1;
1123 }
1124 return $this->linkItem(
1125 $this->label($type),
1126 $this->iconFactory->getIcon('actions-edit-' . $type . ($isSel === $type ? '-release' : ''), Icon::SIZE_SMALL)->render(),
1127 'TYPO3.ClickMenu.fetch(' . GeneralUtility::quoteJSvalue($this->clipObj->selUrlFile($path, ($type === 'copy' ? 1 : 0), ($isSel == $type), $addParam)) . ');return false;'
1128 );
1129 }
1130
1131 /**
1132 * Creates element for deleting of target
1133 *
1134 * @param string $path Path to the file/directory (target)
1135 * @return array Item array, element in $menuItems
1136 * @internal
1137 */
1138 public function FILE_delete($path)
1139 {
1140 $loc = 'top.content.list_frame';
1141 $jsCode = $loc . '.location.href='
1142 . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_file') . '&redirect=')
1143 . '+top.rawurlencode(' . $this->frameLocation(($loc . '.document'))
1144 . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
1145 GeneralUtility::quoteJSvalue(
1146 '&file[delete][0][data]=' . rawurlencode($path) . '&vC=' . $this->backendUser->veriCode()
1147 );
1148 if ($this->backendUser->jsConfirmation(JsConfirmation::DELETE)) {
1149 $fileOrFolderObject = ResourceFactory::getInstance()->retrieveFileOrFolderObject($path);
1150 $title = $this->languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:delete');
1151 $confirmMessage = sprintf(
1152 $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.delete'),
1153 $fileOrFolderObject->getName()
1154 );
1155 if ($fileOrFolderObject instanceof Folder) {
1156 $confirmMessage .= BackendUtility::referenceCount('_FILE', $fileOrFolderObject->getIdentifier(), ' ' . $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.referencesToFolder'));
1157 } else {
1158 $confirmMessage .= BackendUtility::referenceCount('sys_file', $fileOrFolderObject->getUid(), ' ' . $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:labels.referencesToFile'));
1159 }
1160 $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
1161 . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
1162 . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
1163 . $jsCode
1164 . '} top.TYPO3.Modal.dismiss(); });';
1165 }
1166 $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
1167 return $this->linkItem(
1168 $this->label('delete'),
1169 $this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL)->render(),
1170 $editOnClick . 'return false;'
1171 );
1172 }
1173
1174 /**
1175 * Creates element for pasting files.
1176 *
1177 * @param string $path Path to the file/directory (target)
1178 * @param string $target target - NOT USED.
1179 * @param array $elInfo Various values for the labels.
1180 * @return array Item array, element in $menuItems
1181 * @internal
1182 */
1183 public function FILE_paste($path, $target, $elInfo)
1184 {
1185 $loc = 'top.content.list_frame';
1186
1187 $jsCode = $loc . '.location.href='
1188 . GeneralUtility::quoteJSvalue($this->clipObj->pasteUrl('_FILE', $path, 0) . '&redirect=')
1189 . '+top.rawurlencode(' . $this->frameLocation($loc . '.document')
1190 . '.pathname+' . $this->frameLocation($loc . '.document') . '.search); top.nav.refresh();';
1191
1192 if ($this->backendUser->jsConfirmation(JsConfirmation::COPY_MOVE_PASTE)) {
1193 $title = $this->languageService->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:clip_paste');
1194
1195 $confirmMessage = sprintf(
1196 $this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:mess.'
1197 . ($elInfo[2] === 'copy' ? 'copy' : 'move') . '_into'),
1198 $elInfo[0],
1199 $elInfo[1]
1200 );
1201
1202 $jsCode = 'top.TYPO3.Modal.confirm(' . GeneralUtility::quoteJSvalue($title) . ', '
1203 . GeneralUtility::quoteJSvalue($confirmMessage) . ')'
1204 . '.on(\'button.clicked\', function(e) { if (e.target.name === \'ok\') {'
1205 . $jsCode
1206 . '} top.TYPO3.Modal.dismiss(); });';
1207 }
1208 $editOnClick = 'if(' . $loc . ') { ' . $jsCode . ' }';
1209 return $this->linkItem(
1210 $this->label('pasteinto'),
1211 $this->iconFactory->getIcon('actions-document-paste-into', Icon::SIZE_SMALL)->render(),
1212 $editOnClick . 'return false;'
1213 );
1214 }
1215
1216 /**
1217 * Adding ClickMenu element for file info
1218 *
1219 * @param string $identifier The combined identifier of the file.
1220 * @return array Item array, element in $menuItems
1221 */
1222 protected function fileInfo($identifier)
1223 {
1224 return $this->DB_info('_FILE', $identifier);
1225 }
1226
1227 /***************************************
1228 *
1229 * DRAG AND DROP
1230 *
1231 ***************************************/
1232 /**
1233 * Make 1st level clickmenu:
1234 *
1235 * @param string $table The absolute path
1236 * @param int $srcId UID for the current record.
1237 * @param int $dstId Destination ID
1238 * @return array the array to be returned as JSON
1239 */
1240 public function printDragDropClickMenu($table, $srcId, $dstId)
1241 {
1242 $menuItems = [];
1243 // If the drag and drop menu should apply to PAGES use this set of menu items
1244 if ($table === 'pages') {
1245 // Move Into:
1246 $menuItems['movePage_into'] = $this->dragDrop_copymovepage($srcId, $dstId, 'move', 'into');
1247 // Move After:
1248 $menuItems['movePage_after'] = $this->dragDrop_copymovepage($srcId, $dstId, 'move', 'after');
1249 // Copy Into:
1250 $menuItems['copyPage_into'] = $this->dragDrop_copymovepage($srcId, $dstId, 'copy', 'into');
1251 // Copy After:
1252 $menuItems['copyPage_after'] = $this->dragDrop_copymovepage($srcId, $dstId, 'copy', 'after');
1253 }
1254 // If the drag and drop menu should apply to FOLDERS use this set of menu items
1255 if ($table === 'folders') {
1256 // Move Into:
1257 $menuItems['moveFolder_into'] = $this->dragDrop_copymovefolder($srcId, $dstId, 'move');
1258 // Copy Into:
1259 $menuItems['copyFolder_into'] = $this->dragDrop_copymovefolder($srcId, $dstId, 'copy');
1260 }
1261 // Adding external elements to the menuItems array
1262 $menuItems = $this->processingByExtClassArray($menuItems, 'dragDrop_' . $table, $srcId);
1263 // to extend this, you need to apply a Context Menu to a "virtual" table called "dragDrop_pages" or similar
1264 // Processing by external functions?
1265 $menuItems = $this->externalProcessingOfDBMenuItems($menuItems);
1266 // Return the printed elements:
1267 return $this->printItems($menuItems);
1268 }
1269
1270 /**
1271 * Processing the $menuItems array (for extension classes) (DRAG'N DROP)
1272 *
1273 * @param array $menuItems Array for manipulation.
1274 * @return array Processed $menuItems array
1275 */
1276 public function externalProcessingOfDragDropMenuItems($menuItems)
1277 {
1278 return $menuItems;
1279 }
1280
1281 /**
1282 * Adding CM element for Copying/Moving a Page Into/After from a drag & drop action
1283 *
1284 * @param int $srcUid source UID code for the record to modify
1285 * @param int $dstUid destination UID code for the record to modify
1286 * @param string $action Action code: either "move" or "copy
1287 * @param string $into Parameter code: either "into" or "after
1288 * @return array Item array, element in $menuItems
1289 * @internal
1290 */
1291 public function dragDrop_copymovepage($srcUid, $dstUid, $action, $into)
1292 {
1293 $negativeSign = $into === 'into' ? '' : '-';
1294 $loc = 'top.content.list_frame';
1295 $editOnClick = 'if(' . $loc . '){' . $loc . '.document.location=' .
1296 GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&redirect=') . '+top.rawurlencode(' .
1297 $this->frameLocation(($loc . '.document')) . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
1298 GeneralUtility::quoteJSvalue(
1299 '&cmd[pages][' . $srcUid . '][' . $action . ']=' . $negativeSign . $dstUid . '&prErr=1&vC=' .
1300 $this->backendUser->veriCode()
1301 ) . ';};top.nav.refresh();';
1302 return $this->linkItem(
1303 $this->label($action . 'Page_' . $into),
1304 $this->iconFactory->getIcon('actions-document-paste-' . $into, Icon::SIZE_SMALL)->render(),
1305 $editOnClick . 'return false;'
1306 );
1307 }
1308
1309 /**
1310 * Adding CM element for Copying/Moving a Folder Into from a drag & drop action
1311 *
1312 * @param string $srcPath source path for the record to modify
1313 * @param string $dstPath destination path for the records to modify
1314 * @param string $action Action code: either "move" or "copy
1315 * @return array Item array, element in $menuItems
1316 * @internal
1317 */
1318 public function dragDrop_copymovefolder($srcPath, $dstPath, $action)
1319 {
1320 $loc = 'top.content.list_frame';
1321 $editOnClick = 'if(' . $loc . '){' . $loc . '.document.location=' .
1322 GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_file') . '&redirect=') . '+top.rawurlencode(' .
1323 $this->frameLocation(($loc . '.document')) . '.pathname+' . $this->frameLocation(($loc . '.document')) . '.search)+' .
1324 GeneralUtility::quoteJSvalue(
1325 '&file[' . $action . '][0][data]=' . $srcPath . '&file[' . $action . '][0][target]=' . $dstPath . '&prErr=1&vC=' .
1326 $this->backendUser->veriCode()
1327 ) . ';};top.nav.refresh();';
1328 return $this->linkItem(
1329 $this->label($action . 'Folder_into'),
1330 $this->iconFactory->getIcon('apps-pagetree-drag-move-into', Icon::SIZE_SMALL)->render(),
1331 $editOnClick . 'return false;'
1332 );
1333 }
1334
1335 /***************************************
1336 *
1337 * COMMON
1338 *
1339 **************************************/
1340 /**
1341 * Prints the items from input $menuItems array - as JS section for writing to the div-layers.
1342 *
1343 * @param array $menuItems Array
1344 * @return array the array to be returned via JSON
1345 */
1346 public function printItems($menuItems)
1347 {
1348 // Enable/Disable items
1349 $menuItems = $this->enableDisableItems($menuItems);
1350 // Clean up spacers
1351 $menuItems = $this->cleanUpSpacers($menuItems);
1352 // Adding JS part and return the content
1353 return $this->printLayerJScode($menuItems);
1354 }
1355
1356 /**
1357 * Create the JavaScript section
1358 *
1359 * @param array $menuItems The $menuItems array to print
1360 * @return array|null the items to print, and The JavaScript section which will print the content of the CM to the div-layer in the target frame.
1361 */
1362 public function printLayerJScode($menuItems)
1363 {
1364 // Clipboard must not be submitted - then it's probably a copy/cut situation.
1365 if ($this->isCMlayers()) {
1366 // Create the table displayed in the clickmenu layer:
1367 // Wrap the inner table in another table to create outer border:
1368 return [
1369 'items' => $this->menuItemsForClickMenu($menuItems),
1370 'level' => $this->cmLevel
1371 ];
1372 }
1373 }
1374
1375 /**
1376 * Traverses the menuItems and generates an output array for implosion in the CM div-layers table.
1377 *
1378 * @param array $menuItems Array
1379 * @return array array for implosion in the CM div-layers table.
1380 */
1381 public function menuItemsForClickMenu($menuItems)
1382 {
1383 $out = [];
1384 foreach ($menuItems as $cc => $i) {
1385 // MAKE horizontal spacer
1386 if (is_string($i) && $i === 'spacer') {
1387 $out[] = '<span class="list-group-item list-group-item-divider"></span>';
1388 } else {
1389 // Just make normal element:
1390 $onClick = $i[3];
1391 $onClick = preg_replace('/return[[:space:]]+hideCM\\(\\)[[:space:]]*;/i', '', $onClick);
1392 $onClick = preg_replace('/return[[:space:]]+false[[:space:]]*;/i', '', $onClick);
1393 $onClick = preg_replace('/hideCM\\(\\);/i', '', $onClick);
1394 if (!$i[5]) {
1395 $onClick .= 'TYPO3.ClickMenu.hideAll();';
1396 }
1397 $out[] = '
1398 <a href="javascript:;" class="list-group-item" onclick="' . htmlspecialchars($onClick) . '">
1399 <span class="list-group-item-icon">' . $i[2] . '</span> ' . $i[1] . '
1400 </a>';
1401 }
1402 }
1403 return $out;
1404 }
1405
1406 /**
1407 * Adds or inserts a menu item
1408 * Can be used to set the position of new menu entries within the list of existing menu entries. Has this syntax: [cmd]:[menu entry key],[cmd].... cmd can be "after", "before" or "top" (or blank/"bottom" which is default). If "after"/"before" then menu items will be inserted after/before the existing entry with [menu entry key] if found. "after-spacer" and "before-spacer" do the same, but inserts before or after an item and a spacer. If not found, the bottom of list. If "top" the items are inserted in the top of the list.
1409 *
1410 * @param array $menuItems Menu items array
1411 * @param array $newMenuItems Menu items array to insert
1412 * @param string $position Position command string. Has this syntax: [cmd]:[menu entry key],[cmd].... cmd can be "after", "before" or "top" (or blank/"bottom" which is default). If "after"/"before" then menu items will be inserted after/before the existing entry with [menu entry key] if found. "after-spacer" and "before-spacer" do the same, but inserts before or after an item and a spacer. If not found, the bottom of list. If "top" the items are inserted in the top of the list.
1413 * @return array Menu items array, processed.
1414 */
1415 public function addMenuItems($menuItems, $newMenuItems, $position = '')
1416 {
1417 if (is_array($newMenuItems)) {
1418 $pointer = 0;
1419 if ($position) {
1420 $posArr = GeneralUtility::trimExplode(',', $position, true);
1421 foreach ($posArr as $pos) {
1422 list($place, $menuEntry) = GeneralUtility::trimExplode(':', $pos, true);
1423 list($place, $placeExtra) = GeneralUtility::trimExplode('-', $place, true);
1424 // Bottom
1425 $pointer = count($menuItems);
1426 $found = false;
1427 if ($place) {
1428 switch (strtolower($place)) {
1429 case 'after':
1430 case 'before':
1431 if ($menuEntry) {
1432 $p = 1;
1433 reset($menuItems);
1434 while (true) {
1435 if ((string)key($menuItems) === $menuEntry) {
1436 $pointer = $p;
1437 $found = true;
1438 break;
1439 }
1440 if (!next($menuItems)) {
1441 break;
1442 }
1443 $p++;
1444 }
1445 if (!$found) {
1446 break;
1447 }
1448 if ($place === 'before') {
1449 $pointer--;
1450 if ($placeExtra === 'spacer' and prev($menuItems) === 'spacer') {
1451 $pointer--;
1452 }
1453 } elseif ($place === 'after') {
1454 if ($placeExtra === 'spacer' and next($menuItems) === 'spacer') {
1455 $pointer++;
1456 }
1457 }
1458 }
1459 break;
1460 default:
1461 if (strtolower($place) === 'top') {
1462 $pointer = 0;
1463 } else {
1464 $pointer = count($menuItems);
1465 }
1466 $found = true;
1467 }
1468 }
1469 if ($found) {
1470 break;
1471 }
1472 }
1473 }
1474 $pointer = max(0, $pointer);
1475 $menuItemsBefore = array_slice($menuItems, 0, $pointer ?: 0);
1476 $menuItemsAfter = array_slice($menuItems, $pointer);
1477 $menuItems = $menuItemsBefore + $newMenuItems + $menuItemsAfter;
1478 }
1479 return $menuItems;
1480 }
1481
1482 /**
1483 * Creating an array with various elements for the clickmenu entry
1484 *
1485 * @param string $str The label, htmlspecialchar'ed already
1486 * @param string $icon <img>-tag for the icon
1487 * @param string $onClick JavaScript onclick event for label/icon
1488 * @param int $onlyCM ==1 and the element will NOT appear in clickmenus in the topframe (unless clickmenu is totally unavailable)! ==2 and the item will NEVER appear in top frame. (This is mostly for "less important" options since the top frame is not capable of holding so many elements horizontally)
1489 * @param int $dontHide If set, the clickmenu layer will not hide itself onclick - used for secondary menus to appear...
1490 * @return array $menuItem entry with 6 numerical entries: [0] is the HTML for display of the element with link and icon an mouseover etc., [1]-[5] is simply the input params passed through!
1491 */
1492 public function linkItem($str, $icon, $onClick, $onlyCM = 0, $dontHide = 0)
1493 {
1494 return [
1495 '<a href="javascript:;" onclick="' . htmlspecialchars($onClick) . '">' . $str . $icon . '</a>',
1496 $str,
1497 $icon,
1498 $onClick,
1499 $onlyCM,
1500 $dontHide
1501 ];
1502 }
1503
1504 /**
1505 * Enabling / Disabling items based on list provided from GET var ($this->iParts[3])
1506 *
1507 * @param array $menuItems Menu items array
1508 * @return array Menu items array, processed.
1509 */
1510 public function enableDisableItems($menuItems)
1511 {
1512 if ($this->iParts[3]) {
1513 // Detect "only" mode: (only showing listed items)
1514 if ($this->iParts[3][0] === '+') {
1515 $this->iParts[3] = substr($this->iParts[3], 1);
1516 $only = true;
1517 } else {
1518 $only = false;
1519 }
1520 // Do filtering:
1521 // Transfer ONLY elements which are mentioned (or are spacers)
1522 if ($only) {
1523 $newMenuArray = [];
1524 foreach ($menuItems as $key => $value) {
1525 if (GeneralUtility::inList($this->iParts[3], $key) || is_string($value) && $value === 'spacer') {
1526 $newMenuArray[$key] = $value;
1527 }
1528 }
1529 $menuItems = $newMenuArray;
1530 } else {
1531 // Traverse all elements except those listed (just unsetting them):
1532 $elements = GeneralUtility::trimExplode(',', $this->iParts[3], true);
1533 foreach ($elements as $value) {
1534 unset($menuItems[$value]);
1535 }
1536 }
1537 }
1538 // Return processed menu items:
1539 return $menuItems;
1540 }
1541
1542 /**
1543 * Clean up spacers; Will remove any spacers in the start/end of menu items array plus any duplicates.
1544 *
1545 * @param array $menuItems Menu items array
1546 * @return array Menu items array, processed.
1547 */
1548 public function cleanUpSpacers($menuItems)
1549 {
1550 // Remove doubles:
1551 $prevItemWasSpacer = false;
1552 foreach ($menuItems as $key => $value) {
1553 if (is_string($value) && $value === 'spacer') {
1554 if ($prevItemWasSpacer) {
1555 unset($menuItems[$key]);
1556 }
1557 $prevItemWasSpacer = true;
1558 } else {
1559 $prevItemWasSpacer = false;
1560 }
1561 }
1562 // Remove first:
1563 reset($menuItems);
1564 $key = key($menuItems);
1565 $value = current($menuItems);
1566 if (is_string($value) && $value === 'spacer') {
1567 unset($menuItems[$key]);
1568 }
1569 // Remove last:
1570 end($menuItems);
1571 $key = key($menuItems);
1572 $value = current($menuItems);
1573 if (is_string($value) && $value === 'spacer') {
1574 unset($menuItems[$key]);
1575 }
1576 // Return processed menu items:
1577 return $menuItems;
1578 }
1579
1580 /**
1581 * Get label from locallang_core.xlf:cm.*
1582 *
1583 * @param string $label The "cm."-suffix to get.
1584 * @return string
1585 */
1586 public function label($label)
1587 {
1588 return htmlspecialchars($this->languageService->sL('LLL:EXT:lang/locallang_core.xlf:cm.' . $label));
1589 }
1590
1591 /**
1592 * Returns TRUE if there should be writing to the div-layers (commands sent to clipboard MUST NOT write to div-layers)
1593 *
1594 * @return bool
1595 */
1596 public function isCMlayers()
1597 {
1598 return !$this->CB;
1599 }
1600
1601 /**
1602 * Appends ".location" to input string
1603 *
1604 * @param string $str Input string, probably a JavaScript document reference
1605 * @return string
1606 */
1607 public function frameLocation($str)
1608 {
1609 return $str . '.location';
1610 }
1611 }