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