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