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