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