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