cb34d6bcbf66fa3b56b7a2a172310c56d3b7ae4c
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / PageLayoutController.php
1 <?php
2 namespace TYPO3\CMS\Backend\Controller;
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 Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
20 use TYPO3\CMS\Backend\Form\FormDataCompiler;
21 use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord;
22 use TYPO3\CMS\Backend\Form\FormResultCompiler;
23 use TYPO3\CMS\Backend\Form\NodeFactory;
24 use TYPO3\CMS\Backend\Module\ModuleLoader;
25 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
26 use TYPO3\CMS\Backend\Template\ModuleTemplate;
27 use TYPO3\CMS\Backend\Tree\View\ContentLayoutPagePositionMap;
28 use TYPO3\CMS\Backend\Utility\BackendUtility;
29 use TYPO3\CMS\Backend\View\BackendLayoutView;
30 use TYPO3\CMS\Backend\View\PageLayoutView;
31 use TYPO3\CMS\Core\DataHandling\DataHandler;
32 use TYPO3\CMS\Core\Imaging\Icon;
33 use TYPO3\CMS\Core\Imaging\IconFactory;
34 use TYPO3\CMS\Core\Messaging\FlashMessage;
35 use TYPO3\CMS\Core\Messaging\FlashMessageService;
36 use TYPO3\CMS\Core\Page\PageRenderer;
37 use TYPO3\CMS\Core\Type\Bitmask\Permission;
38 use TYPO3\CMS\Core\Utility\GeneralUtility;
39 use TYPO3\CMS\Core\Utility\HttpUtility;
40 use TYPO3\CMS\Core\Utility\MathUtility;
41 use TYPO3\CMS\Core\Versioning\VersionState;
42 use TYPO3\CMS\Fluid\View\StandaloneView;
43 use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
44 use TYPO3\CMS\Frontend\Page\PageRepository;
45 use TYPO3\CMS\Recordlist\RecordList;
46
47 /**
48 * Script Class for Web > Layout module
49 */
50 class PageLayoutController
51 {
52
53 /**
54 * Page Id for which to make the listing
55 *
56 * @var int
57 */
58 public $id;
59
60 /**
61 * Pointer - for browsing list of records.
62 *
63 * @var int
64 */
65 public $pointer;
66
67 /**
68 * Thumbnails or not
69 *
70 * @var string
71 */
72 public $imagemode;
73
74 /**
75 * Search-fields
76 *
77 * @var string
78 */
79 public $search_field;
80
81 /**
82 * Search-levels
83 *
84 * @var int
85 */
86 public $search_levels;
87
88 /**
89 * Show-limit
90 *
91 * @var int
92 */
93 public $showLimit;
94
95 /**
96 * Return URL
97 *
98 * @var string
99 */
100 public $returnUrl;
101
102 /**
103 * Clear-cache flag - if set, clears page cache for current id.
104 *
105 * @var bool
106 */
107 public $clear_cache;
108
109 /**
110 * PopView id - for opening a window with the page
111 *
112 * @var bool
113 */
114 public $popView;
115
116 /**
117 * QuickEdit: Variable, that tells quick edit what to show/edit etc.
118 * Format is [tablename]:[uid] with some exceptional values for both parameters (with special meanings).
119 *
120 * @var string
121 */
122 public $edit_record;
123
124 /**
125 * QuickEdit: If set, this variable tells quick edit that the last edited record had
126 * this value as UID and we should look up the new, real uid value in sys_log.
127 *
128 * @var string
129 */
130 public $new_unique_uid;
131
132 /**
133 * Page select perms clause
134 *
135 * @var string
136 */
137 public $perms_clause;
138
139 /**
140 * Module TSconfig
141 *
142 * @var array
143 */
144 public $modTSconfig;
145
146 /**
147 * Module shared TSconfig
148 *
149 * @var array
150 */
151 public $modSharedTSconfig;
152
153 /**
154 * Current ids page record
155 *
156 * @var array
157 */
158 public $pageinfo;
159
160 /**
161
162 * "Pseudo" Description -table name
163 *
164 * @var string
165 */
166 public $descrTable;
167
168 /**
169 * List of column-integers to edit. Is set from TSconfig, default is "1,0,2,3"
170 *
171 * @var string
172 */
173 public $colPosList;
174
175 /**
176 * Flag: If content can be edited or not.
177 *
178 * @var bool
179 */
180 public $EDIT_CONTENT;
181
182 /**
183 * Users permissions integer for this page.
184 *
185 * @var int
186 */
187 public $CALC_PERMS;
188
189 /**
190 * Currently selected language for editing content elements
191 *
192 * @var int
193 */
194 public $current_sys_language;
195
196 /**
197 * Module configuration
198 *
199 * @var array
200 */
201 public $MCONF = array();
202
203 /**
204 * Menu configuration
205 *
206 * @var array
207 */
208 public $MOD_MENU = array();
209
210 /**
211 * Module settings (session variable)
212 *
213 * @var array
214 */
215 public $MOD_SETTINGS = array();
216
217 /**
218 * Array of tables to be listed by the Web > Page module in addition to the default tables
219 *
220 * @var array
221 */
222 public $externalTables = array();
223
224 /**
225 * Module output accumulation
226 *
227 * @var string
228 */
229 public $content;
230
231 /**
232 * List of column-integers accessible to the current BE user.
233 * Is set from TSconfig, default is $colPosList
234 *
235 * @var string
236 */
237 public $activeColPosList;
238
239 /**
240 * @var array
241 */
242 protected $eRParts = array();
243
244 /**
245 * @var string
246 */
247 protected $editSelect;
248
249 /**
250 * @var bool
251 */
252 protected $deleteButton;
253
254 /**
255 * @var bool
256 */
257 protected $undoButton;
258
259 /**
260 * @var array
261 */
262 protected $undoButtonR;
263
264 /**
265 * @var string
266 */
267 protected $R_URI;
268
269 /**
270 * @var string
271 */
272 protected $closeUrl;
273
274 /**
275 * Caches the available languages in a colPos
276 *
277 * @var array
278 */
279 protected $languagesInColumnCache = array();
280
281 /**
282 * Caches the amount of content elements as a matrix
283 *
284 * @var array
285 * @internal
286 */
287 public $contentElementCache = array();
288
289 /**
290 * @var IconFactory
291 */
292 protected $iconFactory;
293
294 /**
295 * The name of the module
296 *
297 * @var string
298 */
299 protected $moduleName = 'web_layout';
300
301 /**
302 * @var ModuleTemplate
303 */
304 protected $moduleTemplate;
305
306 /**
307 * @var ButtonBar
308 */
309 protected $buttonBar;
310
311 /**
312 * Initializing the module
313 *
314 * @return void
315 */
316 public function init()
317 {
318 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
319 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
320 $this->buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
321 $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
322 // Setting module configuration / page select clause
323 $this->MCONF['name'] = $this->moduleName;
324 $this->perms_clause = $this->getBackendUser()->getPagePermsClause(1);
325 // Get session data
326 $sessionData = $this->getBackendUser()->getSessionData(RecordList::class);
327 $this->search_field = !empty($sessionData['search_field']) ? $sessionData['search_field'] : '';
328 // GPvars:
329 $this->id = (int)GeneralUtility::_GP('id');
330 $this->pointer = GeneralUtility::_GP('pointer');
331 $this->imagemode = GeneralUtility::_GP('imagemode');
332 $this->clear_cache = GeneralUtility::_GP('clear_cache');
333 $this->popView = GeneralUtility::_GP('popView');
334 $this->edit_record = GeneralUtility::_GP('edit_record');
335 $this->new_unique_uid = GeneralUtility::_GP('new_unique_uid');
336 $this->search_field = GeneralUtility::_GP('search_field');
337 $this->search_levels = GeneralUtility::_GP('search_levels');
338 $this->showLimit = GeneralUtility::_GP('showLimit');
339 $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
340 $this->externalTables = $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['cms']['db_layout']['addTables'];
341 $sessionData['search_field'] = $this->search_field;
342 // Store session data
343 $this->getBackendUser()->setAndSaveSessionData(RecordList::class, $sessionData);
344 // Load page info array:
345 $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
346 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
347 // Initialize menu
348 $this->menuConfig();
349 // Setting sys language from session var:
350 $this->current_sys_language = (int)$this->MOD_SETTINGS['language'];
351 // CSH / Descriptions:
352 $this->descrTable = '_MOD_' . $this->moduleName;
353 }
354
355 /**
356 * Initialize menu array
357 *
358 * @return void
359 */
360 public function menuConfig()
361 {
362 $lang = $this->getLanguageService();
363 // MENU-ITEMS:
364 $this->MOD_MENU = array(
365 'tt_content_showHidden' => '',
366 'function' => array(
367 0 => $lang->getLL('m_function_0'),
368 1 => $lang->getLL('m_function_1'),
369 2 => $lang->getLL('m_function_2')
370 ),
371 'language' => array(
372 0 => $lang->getLL('m_default')
373 )
374 );
375 // example settings:
376 // $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['cms']['db_layout']['addTables']['tx_myext'] =
377 // array ('default' => array(
378 // 'MENU' => 'LLL:EXT:tx_myext/locallang_db.xlf:menuDefault',
379 // 'fList' => 'title,description,image',
380 // 'icon' => TRUE));
381 if (is_array($this->externalTables)) {
382 foreach ($this->externalTables as $table => $tableSettings) {
383 // delete the default settings from above
384 if (is_array($this->MOD_MENU[$table])) {
385 unset($this->MOD_MENU[$table]);
386 }
387 if (is_array($tableSettings) && count($tableSettings) > 1) {
388 foreach ($tableSettings as $key => $settings) {
389 $this->MOD_MENU[$table][$key] = $lang->sL($settings['MENU']);
390 }
391 }
392 }
393 }
394 // First, select all pages_language_overlay records on the current page. Each represents a possibility for a language on the page. Add these to language selector.
395 $res = $this->exec_languageQuery($this->id);
396 while ($lRow = $this->getDatabaseConnection()->sql_fetch_assoc($res)) {
397 if ($this->getBackendUser()->checkLanguageAccess($lRow['uid'])) {
398 $this->MOD_MENU['language'][$lRow['uid']] = $lRow['hidden'] ? '(' . $lRow['title'] . ')' : $lRow['title'];
399 }
400 }
401 // Setting alternative default label:
402 if (($this->modSharedTSconfig['properties']['defaultLanguageLabel'] || $this->modTSconfig['properties']['defaultLanguageLabel']) && isset($this->MOD_MENU['language'][0])) {
403 $this->MOD_MENU['language'][0] = $this->modTSconfig['properties']['defaultLanguageLabel'] ? $this->modSharedTSconfig['properties']['defaultLanguageLabel'] : $this->modSharedTSconfig['properties']['defaultLanguageLabel'];
404 }
405 // Clean up settings
406 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName);
407 // For all elements to be shown in draft workspaces & to also show hidden elements by default if user hasn't disabled the option
408 if ($this->getBackendUser()->workspace != 0 || $this->MOD_SETTINGS['tt_content_showHidden'] !== '0') {
409 $this->MOD_SETTINGS['tt_content_showHidden'] = 1;
410 }
411 $this->makeActionMenu();
412 }
413
414 /**
415 * This creates the dropdown menu with the different actions this module is able to provide.
416 * For now they are Columns, Quick Edit and Languages.
417 *
418 * @return void
419 */
420 protected function makeActionMenu()
421 {
422 $availableActionArray = array(
423 0 => $this->getLanguageService()->getLL('m_function_0'),
424 1 => $this->getLanguageService()->getLL('m_function_1'),
425 2 => $this->getLanguageService()->getLL('m_function_2')
426 );
427 // Find if there are ANY languages at all (and if not, remove the language option from function menu).
428 $count = $this->getDatabaseConnection()->exec_SELECTcountRows('uid', 'sys_language', $this->getBackendUser()->isAdmin() ? '' : 'hidden=0');
429 if (!$count) {
430 unset($availableActionArray['2']);
431 }
432 // page/be_user TSconfig settings and blinding of menu-items
433 $this->modSharedTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.SHARED');
434 $this->modTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.' . $this->moduleName);
435 if ($this->modTSconfig['properties']['QEisDefault']) {
436 ksort($availableActionArray);
437 }
438 $availableActionArray = BackendUtility::unsetMenuItems($this->modTSconfig['properties'], $availableActionArray, 'menu.function');
439 // Remove QuickEdit as option if page type is not...
440 if (!GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['content_doktypes'] . ',6', $this->pageinfo['doktype'])) {
441 unset($availableActionArray[0]);
442 }
443 $actionMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
444 $actionMenu->setIdentifier('actionMenu');
445 $actionMenu->setLabel('');
446
447 foreach ($availableActionArray as $key => $action) {
448 $menuItem = $actionMenu
449 ->makeMenuItem()
450 ->setTitle($action)
451 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&SET[function]=' . $key);
452 if ((int)$this->MOD_SETTINGS['function'] === $key) {
453 $menuItem->setActive(true);
454 }
455 $actionMenu->addMenuItem($menuItem);
456 }
457 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($actionMenu);
458 }
459
460 /**
461 * Clears page cache for the current id, $this->id
462 *
463 * @return void
464 */
465 public function clearCache()
466 {
467 if ($this->clear_cache) {
468 $tce = GeneralUtility::makeInstance(DataHandler::class);
469 $tce->stripslashes_values = false;
470 $tce->start(array(), array());
471 $tce->clear_cacheCmd($this->id);
472 }
473 }
474
475 /**
476 * Generate the flashmessages for current pid
477 *
478 * @return string HTML content with flashmessages
479 */
480 protected function getHeaderFlashMessagesForCurrentPid()
481 {
482 $content = '';
483 $lang = $this->getLanguageService();
484
485 // If page is a folder
486 if ($this->pageinfo['doktype'] == PageRepository::DOKTYPE_SYSFOLDER) {
487 $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
488 $moduleLoader->load($GLOBALS['TBE_MODULES']);
489 $modules = $moduleLoader->modules;
490 if (is_array($modules['web']['sub']['list'])) {
491 $title = $lang->getLL('goToListModule');
492 $message = '<p>' . $lang->getLL('goToListModuleMessage') . '</p>';
493 $message .= '<a class="btn btn-info" href="javascript:top.goToModule(\'web_list\',1);">' . $lang->getLL('goToListModule') . '</a>';
494 $view = GeneralUtility::makeInstance(StandaloneView::class);
495 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html'));
496 $view->assignMultiple(array(
497 'title' => $title,
498 'message' => $message,
499 'state' => InfoboxViewHelper::STATE_INFO
500 ));
501 $content .= $view->render();
502 }
503 }
504 // If content from different pid is displayed
505 if ($this->pageinfo['content_from_pid']) {
506 $contentPage = BackendUtility::getRecord('pages', (int)$this->pageinfo['content_from_pid']);
507 $linkToPid = $this->local_linkThisScript(array('id' => $this->pageinfo['content_from_pid']));
508 $link = '<a href="' . $linkToPid . '">' . htmlspecialchars($title) . ' (PID ' . (int)$this->pageinfo['content_from_pid'] . ')</a>';
509 $title = BackendUtility::getRecordTitle('pages', $contentPage);
510 $message = sprintf($lang->getLL('content_from_pid_title'), $link);
511 $view = GeneralUtility::makeInstance(StandaloneView::class);
512 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html'));
513 $view->assignMultiple(array(
514 'title' => $title,
515 'message' => $message,
516 'state' => InfoboxViewHelper::STATE_INFO
517 ));
518 $content .= $view->render();
519 }
520 return $content;
521 }
522
523 /**
524 *
525 * @return string $title
526 */
527 protected function getLocalizedPageTitle()
528 {
529 if ($this->current_sys_language > 0) {
530 $overlayRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
531 'title',
532 'pages_language_overlay',
533 'pid = ' . (int)$this->id .
534 ' AND sys_language_uid = ' . (int)$this->current_sys_language .
535 BackendUtility::deleteClause('pages_language_overlay') .
536 BackendUtility::versioningPlaceholderClause('pages_language_overlay'),
537 '',
538 '',
539 ''
540 );
541 return $overlayRecord['title'];
542 } else {
543 return $this->pageinfo['title'];
544 }
545 }
546
547 /**
548 * Injects the request object for the current request or subrequest
549 * As this controller goes only through the main() method, it is rather simple for now
550 *
551 * @param ServerRequestInterface $request the current request
552 * @param ResponseInterface $response
553 * @return ResponseInterface the response with the content
554 */
555 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
556 {
557 $GLOBALS['SOBE'] = $this;
558 $this->init();
559 $this->clearCache();
560 $this->main();
561 $response->getBody()->write($this->moduleTemplate->renderContent());
562 return $response;
563 }
564
565 /**
566 * Main function.
567 * Creates some general objects and calls other functions for the main rendering of module content.
568 *
569 * @return void
570 */
571 public function main()
572 {
573 $lang = $this->getLanguageService();
574 // Access check...
575 // The page will show only if there is a valid page and if this page may be viewed by the user
576 $access = is_array($this->pageinfo) ? 1 : 0;
577 // Content
578 $content = '';
579 if ($this->id && $access) {
580 // Initialize permission settings:
581 $this->CALC_PERMS = $this->getBackendUser()->calcPerms($this->pageinfo);
582 $this->EDIT_CONTENT = $this->pageIsNotLockedForEditors();
583
584 // override the default jumpToUrl
585 $this->moduleTemplate->addJavaScriptCode('jumpToUrl', '
586 function jumpToUrl(URL,formEl) {
587 if (document.editform && TBE_EDITOR.isFormChanged) { // Check if the function exists... (works in all browsers?)
588 if (!TBE_EDITOR.isFormChanged()) {
589 window.location.href = URL;
590 } else if (formEl) {
591 if (formEl.type=="checkbox") formEl.checked = formEl.checked ? 0 : 1;
592 }
593 } else {
594 window.location.href = URL;
595 }
596 }
597 ');
598 $this->moduleTemplate->addJavaScriptCode('mainJsFunctions', '
599 if (top.fsMod) {
600 top.fsMod.recentIds["web"] = ' . (int)$this->id . ';
601 top.fsMod.navFrameHighlightedID["web"] = "pages' . (int)$this->id . '_"+top.fsMod.currentBank; ' . (int)$this->id . ';
602 }
603 ' . ($this->popView ? BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id)) : '') . '
604 function deleteRecord(table,id,url) { //
605 if (confirm(' . GeneralUtility::quoteJSvalue($lang->getLL('deleteWarning')) . ')) {
606 window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&vC=' . $this->getBackendUser()->veriCode() . '&prErr=1&uPT=1";
607 }
608 return false;
609 }
610 ');
611
612 // Setting doc-header
613 $this->moduleTemplate->setForm(
614 '<form action="' . htmlspecialchars(BackendUtility::getModuleUrl($this->moduleName, array('id' => $this->id, 'imagemode' => $this->imagemode))) . '" method="post">'
615 );
616
617 // Find backend layout / coumns
618 $backendLayout = GeneralUtility::callUserFunction(BackendLayoutView::class . '->getSelectedBackendLayout', $this->id, $this);
619 if (!empty($backendLayout['__colPosList'])) {
620 $this->colPosList = implode(',', $backendLayout['__colPosList']);
621 }
622 // Removing duplicates, if any
623 $this->colPosList = array_unique(GeneralUtility::intExplode(',', $this->colPosList));
624 // Accessible columns
625 if (isset($this->modSharedTSconfig['properties']['colPos_list']) && trim($this->modSharedTSconfig['properties']['colPos_list']) !== '') {
626 $this->activeColPosList = array_unique(GeneralUtility::intExplode(',', trim($this->modSharedTSconfig['properties']['colPos_list'])));
627 // Match with the list which is present in the colPosList for the current page
628 if (!empty($this->colPosList) && !empty($this->activeColPosList)) {
629 $this->activeColPosList = array_unique(array_intersect(
630 $this->activeColPosList,
631 $this->colPosList
632 ));
633 }
634 } else {
635 $this->activeColPosList = $this->colPosList;
636 }
637 $this->activeColPosList = implode(',', $this->activeColPosList);
638 $this->colPosList = implode(',', $this->colPosList);
639
640 $content .= $this->getHeaderFlashMessagesForCurrentPid();
641
642
643 // Render the primary module content:
644 if ($this->MOD_SETTINGS['function'] == 0) {
645 // QuickEdit
646 $content .= $this->renderQuickEdit();
647 } else {
648 // Page title
649 $content .= '<h1 class="t3js-title-inlineedit">' . htmlspecialchars($this->getLocalizedPageTitle()) . '</h1>';
650 // All other listings
651 $content .= $this->renderListContent();
652 }
653
654 // Setting up the buttons for the docheader
655 $this->makeButtons($this->MOD_SETTINGS['function'] == 0 ? 'quickEdit' : '');
656 // Create LanguageMenu
657 $this->makeLanguageMenu();
658
659 } else {
660 $this->moduleTemplate->addJavaScriptCode(
661 'mainJsFunctions',
662 'if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';'
663 );
664 $content .= '<h1>' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '</h1>';
665 $view = GeneralUtility::makeInstance(StandaloneView::class);
666 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html'));
667 $view->assignMultiple(array(
668 'title' => $lang->getLL('clickAPage_header'),
669 'message' => $lang->getLL('clickAPage_content'),
670 'state' => InfoboxViewHelper::STATE_INFO
671 ));
672 $content .= $view->render();
673 }
674 // Set content
675 $this->moduleTemplate->setContent($content);
676 }
677
678 /**
679 * Rendering the quick-edit view.
680 *
681 * @return string
682 */
683 public function renderQuickEdit()
684 {
685 $databaseConnection = $this->getDatabaseConnection();
686 $beUser = $this->getBackendUser();
687 $lang = $this->getLanguageService();
688 // Alternative form tag; Quick Edit submits its content to tce_db.php.
689 $this->moduleTemplate->setForm(
690 '<form action="' . htmlspecialchars(BackendUtility::getModuleUrl('tce_db', ['prErr' => 1, 'uPT' => 1])) . '" method="post" enctype="multipart/form-data" name="editform" onsubmit="return TBE_EDITOR.checkSubmit(1);">'
691 );
692 // Set the edit_record value for internal use in this function:
693 $edit_record = $this->edit_record;
694 // If a command to edit all records in a column is issue, then select all those elements, and redirect to FormEngine
695 if (substr($edit_record, 0, 9) == '_EDIT_COL') {
696 $res = $databaseConnection->exec_SELECTquery('*', 'tt_content', 'pid=' . (int)$this->id . ' AND colPos=' . (int)substr($edit_record, 10) . ' AND sys_language_uid=' . (int)$this->current_sys_language . ($this->MOD_SETTINGS['tt_content_showHidden'] ? '' : BackendUtility::BEenableFields('tt_content')) . BackendUtility::deleteClause('tt_content') . BackendUtility::versioningPlaceholderClause('tt_content'), '', 'sorting');
697 $idListA = array();
698 while ($cRow = $databaseConnection->sql_fetch_assoc($res)) {
699 $idListA[] = $cRow['uid'];
700 }
701 $url = BackendUtility::getModuleUrl('record_edit', array(
702 'edit[tt_content][' . implode(',', $idListA) . ']' => 'edit',
703 'returnUrl' => $this->local_linkThisScript(array('edit_record' => ''))
704 ));
705 HttpUtility::redirect($url);
706 }
707 // If the former record edited was the creation of a NEW record, this will look up the created records uid:
708 if ($this->new_unique_uid) {
709 $res = $databaseConnection->exec_SELECTquery('*', 'sys_log', 'userid=' . (int)$beUser->user['uid'] . ' AND NEWid=' . $databaseConnection->fullQuoteStr($this->new_unique_uid, 'sys_log'));
710 $sys_log_row = $databaseConnection->sql_fetch_assoc($res);
711 if (is_array($sys_log_row)) {
712 $edit_record = $sys_log_row['tablename'] . ':' . $sys_log_row['recuid'];
713 }
714 }
715 $edit_record = $this->makeQuickEditMenu($edit_record);
716 // Splitting the edit-record cmd value into table/uid:
717 $this->eRParts = explode(':', $edit_record);
718 $tableName = $this->eRParts[0];
719 // Delete-button flag?
720 $this->deleteButton = MathUtility::canBeInterpretedAsInteger($this->eRParts[1]) && $edit_record && ($tableName !== 'pages' && $this->EDIT_CONTENT || $tableName === 'pages' && $this->CALC_PERMS & Permission::PAGE_DELETE);
721 // If undo-button should be rendered (depends on available items in sys_history)
722 $this->undoButton = false;
723 $undoRes = $databaseConnection->exec_SELECTquery('tstamp', 'sys_history', 'tablename=' . $databaseConnection->fullQuoteStr($tableName, 'sys_history') . ' AND recuid=' . (int)$this->eRParts[1], '', 'tstamp DESC', '1');
724 if ($this->undoButtonR = $databaseConnection->sql_fetch_assoc($undoRes)) {
725 $this->undoButton = true;
726 }
727 // Setting up the Return URL for coming back to THIS script (if links take the user to another script)
728 $R_URL_parts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
729 $R_URL_getvars = GeneralUtility::_GET();
730 unset($R_URL_getvars['popView']);
731 unset($R_URL_getvars['new_unique_uid']);
732 $R_URL_getvars['edit_record'] = $edit_record;
733 $this->R_URI = $R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $R_URL_getvars);
734
735 // Creating editing form:
736 $content = '';
737
738 if ($edit_record) {
739 // Splitting uid parts for special features, if new:
740 list($uidVal, $neighborRecordUid, $ex_colPos) = explode('/', $this->eRParts[1]);
741
742 if ($uidVal === 'new') {
743 $command = 'new';
744 // Page id of this new record
745 $theUid = $this->id;
746 if ($neighborRecordUid) {
747 $theUid = $neighborRecordUid;
748 }
749 } else {
750 $command = 'edit';
751 $theUid = $uidVal;
752 // Convert $uidVal to workspace version if any:
753 $draftRecord = BackendUtility::getWorkspaceVersionOfRecord($beUser->workspace, $tableName, $theUid, 'uid');
754 if ($draftRecord) {
755 $theUid = $draftRecord['uid'];
756 }
757 }
758
759 // @todo: Hack because DatabaseInitializeNewRow reads from _GP directly
760 $GLOBALS['_GET']['defVals'][$tableName] = array(
761 'colPos' => (int)$ex_colPos,
762 'sys_language_uid' => (int)$this->current_sys_language
763 );
764
765 /** @var TcaDatabaseRecord $formDataGroup */
766 $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
767 /** @var FormDataCompiler $formDataCompiler */
768 $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
769 /** @var NodeFactory $nodeFactory */
770 $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
771
772 try {
773 $formDataCompilerInput = [
774 'tableName' => $tableName,
775 'vanillaUid' => (int)$theUid,
776 'command' => $command,
777 ];
778 $formData = $formDataCompiler->compile($formDataCompilerInput);
779
780 if ($command !== 'new') {
781 BackendUtility::lockRecords($tableName, $formData['databaseRow']['uid'], $tableName === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
782 }
783
784 $formData['renderType'] = 'outerWrapContainer';
785 $formResult = $nodeFactory->create($formData)->render();
786
787 $panel = $formResult['html'];
788 $formResult['html'] = '';
789
790 /** @var FormResultCompiler $formResultCompiler */
791 $formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
792 $formResultCompiler->mergeResult($formResult);
793
794 $row = $formData['databaseRow'];
795 $new_unique_uid = '';
796 if ($command === 'new') {
797 $new_unique_uid = $row['uid'];
798 }
799
800 // Add hidden fields:
801 if ($uidVal == 'new') {
802 $panel .= '<input type="hidden" name="data[' . $tableName . '][' . $row['uid'] . '][pid]" value="' . $row['pid'] . '" />';
803 }
804 $redirect = ($uidVal == 'new' ? BackendUtility::getModuleUrl(
805 $this->moduleName,
806 ['id' => $this->id,'new_unique_uid' => $new_unique_uid,'returnUrl' => $this->returnUrl]
807 ) : $this->R_URI);
808 $panel .= '
809 <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
810 <input type="hidden" name="edit_record" value="' . $edit_record . '" />
811 <input type="hidden" name="redirect" value="' . htmlspecialchars($redirect) . '" />
812 ';
813 // Add JavaScript as needed around the form:
814 $content = $formResultCompiler->JStop() . $panel . $formResultCompiler->printNeededJSFunctions();
815
816 // Display "is-locked" message:
817 if ($command === 'edit') {
818 $lockInfo = BackendUtility::isRecordLocked($tableName, $formData['databaseRow']['uid']);
819 if ($lockInfo) {
820 /** @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
821 $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, htmlspecialchars($lockInfo['msg']), '', FlashMessage::WARNING);
822 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
823 $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
824 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
825 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
826 $defaultFlashMessageQueue->enqueue($flashMessage);
827 }
828 }
829 } catch (AccessDeniedException $e) {
830 // If no edit access, print error message:
831 $content = $this->moduleTemplate->section($lang->getLL('noAccess'), $lang->getLL('noAccess_msg')
832 . '<br /><br />'
833 . ($beUser->errorMsg ? 'Reason: ' . $beUser->errorMsg . '<br /><br />' : ''), 0, 1);
834 }
835 } else {
836 // If no edit access, print error message:
837 $content = $this->moduleTemplate->section($lang->getLL('noAccess'), $lang->getLL('noAccess_msg'), 0, 1);
838 }
839
840 // Element selection matrix:
841 if ($tableName === 'tt_content' && MathUtility::canBeInterpretedAsInteger($this->eRParts[1])) {
842 $content .= '<h2>' . $lang->getLL('CEonThisPage') . '</h2>';
843 // PositionMap
844 $posMap = GeneralUtility::makeInstance(ContentLayoutPagePositionMap::class);
845 $posMap->cur_sys_language = $this->current_sys_language;
846 $content .= $posMap->printContentElementColumns(
847 $this->id,
848 $this->eRParts[1],
849 $this->colPosList,
850 $this->MOD_SETTINGS['tt_content_showHidden'],
851 $this->R_URI
852 );
853 // Toggle hidden ContentElements
854 $numberOfHiddenElements = $this->getNumberOfHiddenElements();
855 if ($numberOfHiddenElements) {
856 $content .= '<div class="checkbox">';
857 $content .= '<label for="checkTt_content_showHidden">';
858 $content .= BackendUtility::getFuncCheck($this->id, 'SET[tt_content_showHidden]', $this->MOD_SETTINGS['tt_content_showHidden'], '', '', 'id="checkTt_content_showHidden"');
859 $content .= (!$numberOfHiddenElements ? ('<span class="text-muted">' . $lang->getLL('hiddenCE', true) . '</span>') : $lang->getLL('hiddenCE', true) . ' (' . $numberOfHiddenElements . ')');
860 $content .= '</label>';
861 $content .= '</div>';
862 }
863 // CSH
864 $content .= BackendUtility::cshItem($this->descrTable, 'quickEdit_selElement');
865 }
866
867 return $content;
868 }
869
870 /**
871 * Rendering all other listings than QuickEdit
872 *
873 * @return string
874 */
875 public function renderListContent()
876 {
877 $this->moduleTemplate->getPageRenderer()->loadJquery();
878 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
879 /** @var $dbList \TYPO3\CMS\Backend\View\PageLayoutView */
880 $dbList = GeneralUtility::makeInstance(PageLayoutView::class);
881 $dbList->thumbs = $this->imagemode;
882 $dbList->no_noWrap = 1;
883 $dbList->descrTable = $this->descrTable;
884 $this->pointer = MathUtility::forceIntegerInRange($this->pointer, 0, 100000);
885 $dbList->script = BackendUtility::getModuleUrl($this->moduleName);
886 $dbList->showIcon = 0;
887 $dbList->setLMargin = 0;
888 $dbList->doEdit = $this->EDIT_CONTENT;
889 $dbList->ext_CALC_PERMS = $this->CALC_PERMS;
890 $dbList->agePrefixes = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears');
891 $dbList->id = $this->id;
892 $dbList->nextThree = MathUtility::forceIntegerInRange($this->modTSconfig['properties']['editFieldsAtATime'], 0, 10);
893 $dbList->option_newWizard = $this->modTSconfig['properties']['disableNewContentElementWizard'] ? 0 : 1;
894 $dbList->defLangBinding = $this->modTSconfig['properties']['defLangBinding'] ? 1 : 0;
895 if (!$dbList->nextThree) {
896 $dbList->nextThree = 1;
897 }
898 $dbList->externalTables = $this->externalTables;
899 // Create menu for selecting a table to jump to (this is, if more than just pages/tt_content elements are found on the page!)
900 // also fills $dbList->activeTables
901 $dbList->getTableMenu($this->id);
902 // Initialize other variables:
903 $h_func = '';
904 $tableOutput = array();
905 $tableJSOutput = array();
906 $CMcounter = 0;
907 // Traverse the list of table names which has records on this page (that array is populated
908 // by the $dblist object during the function getTableMenu()):
909 foreach ($dbList->activeTables as $table => $value) {
910 $h_func = '';
911 $h_func_b = '';
912 if (!isset($dbList->externalTables[$table])) {
913 // Toggle hidden ContentElements
914 $numberOfHiddenElements = $this->getNumberOfHiddenElements();
915 if ($numberOfHiddenElements > 0) {
916 $h_func_b = '
917 <div class="checkbox">
918 <label for="checkTt_content_showHidden">
919 <input type="checkbox" id="checkTt_content_showHidden" class="checkbox" name="SET[tt_content_showHidden]" value="1" ' . ($this->MOD_SETTINGS['tt_content_showHidden'] ? 'checked="checked"' : '') . ' />
920 ' . $this->getLanguageService()->getLL('hiddenCE', true) . ' (<span class="t3js-hidden-counter">' . $numberOfHiddenElements . '</span>)
921 </label>
922 </div>';
923 }
924
925 // Boolean: Display up/down arrows and edit icons for tt_content records
926 $dbList->tt_contentConfig['showCommands'] = 1;
927 // Boolean: Display info-marks or not
928 $dbList->tt_contentConfig['showInfo'] = 1;
929 // Setting up the tt_content columns to show:
930 if (is_array($GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'])) {
931 $colList = array();
932 $tcaItems = GeneralUtility::callUserFunction(BackendLayoutView::class . '->getColPosListItemsParsed', $this->id, $this);
933 foreach ($tcaItems as $temp) {
934 $colList[] = $temp[1];
935 }
936 } else {
937 // ... should be impossible that colPos has no array. But this is the fallback should it make any sense:
938 $colList = array('1', '0', '2', '3');
939 }
940 if ($this->colPosList !== '') {
941 $colList = array_intersect(GeneralUtility::intExplode(',', $this->colPosList), $colList);
942 }
943 // The order of the rows: Default is left(1), Normal(0), right(2), margin(3)
944 $dbList->tt_contentConfig['cols'] = implode(',', $colList);
945 $dbList->tt_contentConfig['activeCols'] = $this->activeColPosList;
946 $dbList->tt_contentConfig['showHidden'] = $this->MOD_SETTINGS['tt_content_showHidden'];
947 $dbList->tt_contentConfig['sys_language_uid'] = (int)$this->current_sys_language;
948 // If the function menu is set to "Language":
949 if ($this->MOD_SETTINGS['function'] == 2) {
950 $dbList->tt_contentConfig['languageMode'] = 1;
951 $dbList->tt_contentConfig['languageCols'] = $this->MOD_MENU['language'];
952 $dbList->tt_contentConfig['languageColsPointer'] = $this->current_sys_language;
953 }
954 } else {
955 if (isset($this->MOD_SETTINGS) && isset($this->MOD_MENU)) {
956 $h_func = BackendUtility::getFuncMenu($this->id, 'SET[' . $table . ']', $this->MOD_SETTINGS[$table], $this->MOD_MENU[$table], '', '');
957 }
958 }
959 // Start the dblist object:
960 $dbList->itemsLimitSingleTable = 1000;
961 $dbList->start($this->id, $table, $this->pointer, $this->search_field, $this->search_levels, $this->showLimit);
962 $dbList->counter = $CMcounter;
963 $dbList->ext_function = $this->MOD_SETTINGS['function'];
964 // Render versioning selector:
965 $dbList->HTMLcode .= $this->moduleTemplate->getVersionSelector($this->id);
966 // Generate the list of elements here:
967 $dbList->generateList();
968 // Adding the list content to the tableOutput variable:
969 $tableOutput[$table] = $h_func . $dbList->HTMLcode . $h_func_b;
970 // ... and any accumulated JavaScript goes the same way!
971 $tableJSOutput[$table] = $dbList->JScode;
972 // Increase global counter:
973 $CMcounter += $dbList->counter;
974 // Reset variables after operation:
975 $dbList->HTMLcode = '';
976 $dbList->JScode = '';
977 $h_func = '';
978 }
979 // END: traverse tables
980 // For Context Sensitive Menus:
981 // Init the content
982 $content = '';
983 // Additional header content
984 $headerContentHook = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'];
985 if (is_array($headerContentHook)) {
986 foreach ($headerContentHook as $hook) {
987 $params = array();
988 $content .= GeneralUtility::callUserFunction($hook, $params, $this);
989 }
990 }
991 // Add the content for each table we have rendered (traversing $tableOutput variable)
992 foreach ($tableOutput as $table => $output) {
993 $content .= $output;
994 }
995 // Making search form:
996 if (!$this->modTSconfig['properties']['disableSearchBox'] && !empty($tableOutput)) {
997 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ToggleSearchToolbox');
998 $toggleSearchFormButton = $this->buttonBar->makeLinkButton()
999 ->setClasses('t3js-toggle-search-toolbox')
1000 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.title.searchIcon', true))
1001 ->setIcon($this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL))
1002 ->setHref('#');
1003 $this->buttonBar->addButton($toggleSearchFormButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
1004 $content .= $dbList->getSearchBox(0);
1005 }
1006 // Additional footer content
1007 $footerContentHook = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawFooterHook'];
1008 if (is_array($footerContentHook)) {
1009 foreach ($footerContentHook as $hook) {
1010 $params = array();
1011 $content .= GeneralUtility::callUserFunction($hook, $params, $this);
1012 }
1013 }
1014 return $content;
1015 }
1016
1017 /**
1018 * @return ModuleTemplate
1019 */
1020 public function getModuleTemplate()
1021 {
1022 return $this->moduleTemplate;
1023 }
1024
1025 /**
1026 * Print accumulated content of module
1027 *
1028 * @return void
1029 */
1030 public function printContent()
1031 {
1032 echo $this->moduleTemplate->renderContent();
1033 }
1034
1035 /***************************
1036 *
1037 * Sub-content functions, rendering specific parts of the module content.
1038 *
1039 ***************************/
1040 /**
1041 * This creates the buttons for die modules
1042 *
1043 * @param string $function Identifier for function of module
1044 * @return void
1045 */
1046 protected function makeButtons($function = '')
1047 {
1048 $lang = $this->getLanguageService();
1049 // View page
1050 if (!VersionState::cast($this->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
1051 $viewButton = $this->buttonBar->makeLinkButton()
1052 ->setOnClick(htmlspecialchars(BackendUtility::viewOnClick($this->pageinfo['uid'], '', BackendUtility::BEgetRootLine($this->pageinfo['uid']))))
1053 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', true))
1054 ->setIcon($this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL))
1055 ->setHref('#');
1056
1057 $this->buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1058 }
1059 // Shortcut
1060 if ($this->getBackendUser()->mayMakeShortcut()) {
1061 $shortcutButton = $this->buttonBar->makeShortcutButton()
1062 ->setModuleName($this->moduleName)
1063 ->setGetVariables([
1064 'id',
1065 'M',
1066 'edit_record',
1067 'pointer',
1068 'new_unique_uid',
1069 'search_field',
1070 'search_levels',
1071 'showLimit'
1072 ])
1073 ->setSetVariables(array_keys($this->MOD_MENU));
1074 $this->buttonBar->addButton($shortcutButton);
1075 }
1076 // Cache
1077 if (!$this->modTSconfig['properties']['disableAdvanced']) {
1078 $clearCacheButton = $this->buttonBar->makeLinkButton()
1079 ->setHref(BackendUtility::getModuleUrl($this->moduleName, ['id' => $this->pageinfo['uid'], 'clear_cache' => '1']))
1080 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.clear_cache', true))
1081 ->setIcon($this->iconFactory->getIcon('actions-system-cache-clear', Icon::SIZE_SMALL));
1082 $this->buttonBar->addButton($clearCacheButton, ButtonBar::BUTTON_POSITION_RIGHT, 1, array($shortcutButton));
1083 }
1084 if (!$this->modTSconfig['properties']['disableIconToolbar']) {
1085 // Move record
1086 if (MathUtility::canBeInterpretedAsInteger($this->eRParts[1])) {
1087 $urlParameters = [
1088 'table' => $this->eRParts[0],
1089 'uid' => $this->eRParts[1],
1090 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
1091 ];
1092 $moveButton = $this->buttonBar->makeLinkButton()
1093 ->setHref(BackendUtility::getModuleUrl('move_element', $urlParameters))
1094 ->setTitle($lang->getLL('move_' . ($this->eRParts[0] == 'tt_content' ? 'record' : 'page'), true))
1095 ->setIcon($this->iconFactory->getIcon('actions-' . ($this->eRParts[0] == 'tt_content' ? 'document' : 'page') . '-move', Icon::SIZE_SMALL));
1096 $this->buttonBar->addButton($moveButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1097 }
1098
1099 // Edit page properties and page language overlay icons
1100 if ($this->pageIsNotLockedForEditors()) {
1101 // Edit localized page_language_overlay only when one specific language is selected
1102 if ($this->MOD_SETTINGS['function'] == 1 && $this->current_sys_language > 0) {
1103 $overlayRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
1104 'uid',
1105 'pages_language_overlay',
1106 'pid = ' . (int)$this->id . ' ' .
1107 'AND sys_language_uid = ' . (int)$this->current_sys_language .
1108 BackendUtility::deleteClause('pages_language_overlay') .
1109 BackendUtility::versioningPlaceholderClause('pages_language_overlay'),
1110 '',
1111 '',
1112 ''
1113 );
1114 $editLanguageButton = $this->buttonBar->makeLinkButton()
1115 ->setHref('#')
1116 ->setTitle($lang->getLL('editPageLanguageOverlayProperties', true))
1117 ->setOnClick(htmlspecialchars(BackendUtility::editOnClick('&edit[pages_language_overlay][' . $overlayRecord['uid'] . ']=edit')))
1118 ->setIcon($this->iconFactory->getIcon('mimetypes-x-content-page-language-overlay', Icon::SIZE_SMALL));
1119 $this->buttonBar->addButton($editLanguageButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1120 }
1121 $editPageButton = $this->buttonBar->makeLinkButton()
1122 ->setHref('#')
1123 ->setTitle($lang->getLL('editPageProperties', true))
1124 ->setOnClick(htmlspecialchars(BackendUtility::editOnClick('&edit[pages][' . $this->id . ']=edit')))
1125 ->setIcon($this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL));
1126 $this->buttonBar->addButton($editPageButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1127 }
1128
1129 // Add CSH (Context Sensitive Help) icon to tool bar
1130 $contextSensitiveHelpButton = $this->buttonBar->makeHelpButton()
1131 ->setModuleName($this->descrTable)
1132 ->setFieldName(($function === 'quickEdit' ? 'quickEdit' : 'columns_' . $this->MOD_SETTINGS['function']));
1133 $this->buttonBar->addButton($contextSensitiveHelpButton);
1134
1135 // QuickEdit
1136 if ($function == 'quickEdit') {
1137 // Close Record
1138 $closeButton = $this->buttonBar->makeLinkButton()
1139 ->setHref('#')
1140 ->setOnClick(htmlspecialchars('jumpToUrl(' . GeneralUtility::quoteJSvalue($this->closeUrl) . '); return false;'))
1141 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc', true))
1142 ->setIcon($this->iconFactory->getIcon('actions-document-close', Icon::SIZE_SMALL));
1143 $this->buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 0);
1144
1145 // Save Record
1146 $saveButtonDropdown = $this->buttonBar->makeSplitButton();
1147 $saveButton = $this->buttonBar->makeInputButton()
1148 ->setName('_savedok')
1149 ->setValue('1')
1150 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc', true))
1151 ->setIcon($this->iconFactory->getIcon('actions-document-save', Icon::SIZE_SMALL));
1152 $saveButtonDropdown->addItem($saveButton);
1153 $saveAndCloseButton = $this->buttonBar->makeInputButton()
1154 ->setName('_saveandclosedok')
1155 ->setValue('1')
1156 ->setOnClick('document.editform.redirect.value=\'' . $this->closeUrl . '\';')
1157 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc', true))
1158 ->setIcon($this->iconFactory->getIcon('actions-document-save-close', Icon::SIZE_SMALL));
1159 $saveButtonDropdown->addItem($saveAndCloseButton);
1160 $saveAndShowPageButton = $this->buttonBar->makeInputButton()
1161 ->setName('_savedokview')
1162 ->setValue('1')
1163 ->setOnClick('document.editform.redirect.value+=\'&popView=1\';')
1164 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDocShow', true))
1165 ->setIcon($this->iconFactory->getIcon('actions-document-save-view', Icon::SIZE_SMALL));
1166 $saveButtonDropdown->addItem($saveAndShowPageButton);
1167 $this->buttonBar->addButton($saveButtonDropdown, ButtonBar::BUTTON_POSITION_LEFT, 1);
1168
1169 // Delete record
1170 if ($this->deleteButton) {
1171 $deleteButton = $this->buttonBar->makeLinkButton()
1172 ->setHref('#')
1173 ->setOnClick(htmlspecialchars('return deleteRecord(' . GeneralUtility::quoteJSvalue($this->eRParts[0]) . ',' . GeneralUtility::quoteJSvalue($this->eRParts[1]) . ',' . GeneralUtility::quoteJSvalue(GeneralUtility::getIndpEnv('SCRIPT_NAME') . '?id=' . $this->id) . ');'))
1174 ->setTitle($lang->getLL('deleteItem', true))
1175 ->setIcon($this->iconFactory->getIcon('actions-edit-delete', Icon::SIZE_SMALL));
1176 $this->buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
1177 }
1178
1179 // History
1180 if ($this->undoButton) {
1181 $undoButton = $this->buttonBar->makeLinkButton()
1182 ->setHref('#')
1183 ->setOnClick(htmlspecialchars('window.location.href=' .
1184 GeneralUtility::quoteJSvalue(
1185 BackendUtility::getModuleUrl(
1186 'record_history',
1187 array(
1188 'element' => $this->eRParts[0] . ':' . $this->eRParts[1],
1189 'revert' => 'ALL_FIELDS',
1190 'sumUp' => -1,
1191 'returnUrl' => $this->R_URI,
1192 )
1193 )
1194 ) . '; return false;'))
1195 ->setTitle(htmlspecialchars(sprintf($lang->getLL('undoLastChange'), BackendUtility::calcAge($GLOBALS['EXEC_TIME'] - $this->undoButtonR['tstamp'], $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')))))
1196 ->setIcon($this->iconFactory->getIcon('actions-edit-undo', Icon::SIZE_SMALL));
1197 $this->buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
1198 $historyButton = $this->buttonBar->makeLinkButton()
1199 ->setHref('#')
1200 ->setOnClick(htmlspecialchars('jumpToUrl(' .
1201 GeneralUtility::quoteJSvalue(
1202 BackendUtility::getModuleUrl(
1203 'record_history',
1204 array(
1205 'element' => $this->eRParts[0] . ':' . $this->eRParts[1],
1206 'returnUrl' => $this->R_URI,
1207 )
1208 ) . '#latest'
1209 ) . ');return false;'))
1210 ->setTitle($lang->getLL('recordHistory', true))
1211 ->setIcon($this->iconFactory->getIcon('actions-document-history-open', Icon::SIZE_SMALL));
1212 $this->buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1213 }
1214
1215 }
1216 }
1217 return $buttons;
1218 }
1219
1220 /*******************************
1221 *
1222 * Other functions
1223 *
1224 ******************************/
1225 /**
1226 * Returns the number of hidden elements (including those hidden by start/end times)
1227 * on the current page (for the current sys_language)
1228 *
1229 * @return int
1230 */
1231 public function getNumberOfHiddenElements()
1232 {
1233 return $this->getDatabaseConnection()->exec_SELECTcountRows(
1234 'uid',
1235 'tt_content',
1236 'pid=' . (int)$this->id . ' AND sys_language_uid=' . (int)$this->current_sys_language . BackendUtility::BEenableFields('tt_content', 1) . BackendUtility::deleteClause('tt_content') . BackendUtility::versioningPlaceholderClause('tt_content')
1237 );
1238 }
1239
1240 /**
1241 * Returns URL to the current script.
1242 * In particular the "popView" and "new_unique_uid" Get vars are unset.
1243 *
1244 * @param array $params Parameters array, merged with global GET vars.
1245 * @return string URL
1246 */
1247 public function local_linkThisScript($params)
1248 {
1249 $params['popView'] = '';
1250 $params['new_unique_uid'] = '';
1251 return GeneralUtility::linkThisScript($params);
1252 }
1253
1254 /**
1255 * Returns a SQL query for selecting sys_language records.
1256 *
1257 * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT hidden. If set to another value, the query will select all sys_language records that has a pages_language_overlay record on that page (and is not hidden, unless you are admin user)
1258 * @return string Return query string.
1259 */
1260 public function exec_languageQuery($id)
1261 {
1262 if ($id) {
1263 $exQ = BackendUtility::deleteClause('pages_language_overlay') .
1264 ($this->getBackendUser()->isAdmin() ? '' : ' AND sys_language.hidden=0');
1265 return $this->getDatabaseConnection()->exec_SELECTquery(
1266 'sys_language.*',
1267 'pages_language_overlay,sys_language',
1268 'pages_language_overlay.sys_language_uid=sys_language.uid AND pages_language_overlay.pid=' . (int)$id . $exQ .
1269 BackendUtility::versioningPlaceholderClause('pages_language_overlay'),
1270 'pages_language_overlay.sys_language_uid,sys_language.uid,sys_language.pid,sys_language.tstamp,sys_language.hidden,sys_language.title,sys_language.language_isocode,sys_language.static_lang_isocode,sys_language.flag',
1271 'sys_language.title'
1272 );
1273 } else {
1274 return $this->getDatabaseConnection()->exec_SELECTquery(
1275 'sys_language.*',
1276 'sys_language',
1277 'sys_language.hidden=0',
1278 '',
1279 'sys_language.title'
1280 );
1281 }
1282 }
1283
1284 /**
1285 * Get used languages in a colPos of a page
1286 *
1287 * @param int $pageId
1288 * @param int $colPos
1289 * @return bool|\mysqli_result|object
1290 */
1291 public function getUsedLanguagesInPageAndColumn($pageId, $colPos)
1292 {
1293 if (!isset($languagesInColumnCache[$pageId])) {
1294 $languagesInColumnCache[$pageId] = array();
1295 }
1296 if (!isset($languagesInColumnCache[$pageId][$colPos])) {
1297 $languagesInColumnCache[$pageId][$colPos] = array();
1298 }
1299
1300 if (empty($languagesInColumnCache[$pageId][$colPos])) {
1301 $exQ = BackendUtility::deleteClause('tt_content') .
1302 ($this->getBackendUser()->isAdmin() ? '' : ' AND sys_language.hidden=0');
1303
1304 $databaseConnection = $this->getDatabaseConnection();
1305 $res = $databaseConnection->exec_SELECTquery(
1306 'sys_language.*',
1307 'tt_content,sys_language',
1308 'tt_content.sys_language_uid=sys_language.uid AND tt_content.colPos = ' . (int)$colPos . ' AND tt_content.pid=' . (int)$pageId . $exQ .
1309 BackendUtility::versioningPlaceholderClause('tt_content'),
1310 'tt_content.sys_language_uid,sys_language.uid,sys_language.pid,sys_language.tstamp,sys_language.hidden,sys_language.title,sys_language.language_isocode,sys_language.static_lang_isocode,sys_language.flag',
1311 'sys_language.title'
1312 );
1313 while ($row = $databaseConnection->sql_fetch_assoc($res)) {
1314 $languagesInColumnCache[$pageId][$colPos][$row['uid']] = $row;
1315 }
1316 $databaseConnection->sql_free_result($res);
1317 }
1318
1319 return $languagesInColumnCache[$pageId][$colPos];
1320 }
1321
1322 /**
1323 * Check if a column of a page for a language is empty. Translation records are ignored here!
1324 *
1325 * @param int $colPos
1326 * @param int $languageId
1327 * @return bool
1328 */
1329 public function isColumnEmpty($colPos, $languageId)
1330 {
1331 foreach ($this->contentElementCache[$languageId][$colPos] as $uid => $row) {
1332 if ((int)$row['l18n_parent'] === 0) {
1333 return false;
1334 }
1335 }
1336 return true;
1337 }
1338
1339 /**
1340 * Get elements for a column and a language
1341 *
1342 * @param int $pageId
1343 * @param int $colPos
1344 * @param int $languageId
1345 * @return array
1346 */
1347 public function getElementsFromColumnAndLanguage($pageId, $colPos, $languageId)
1348 {
1349 if (!isset($this->contentElementCache[$languageId][$colPos])) {
1350 $languageId = (int)$languageId;
1351 $whereClause = 'tt_content.pid=' . (int)$pageId . ' AND tt_content.colPos=' . (int)$colPos . ' AND tt_content.sys_language_uid=' . $languageId . BackendUtility::deleteClause('tt_content');
1352 if ($languageId > 0) {
1353 $whereClause .= ' AND tt_content.l18n_parent=0 AND sys_language.uid=' . $languageId . ($this->getBackendUser()->isAdmin() ? '' : ' AND sys_language.hidden=0');
1354 }
1355
1356 $databaseConnection = $this->getDatabaseConnection();
1357 $res = $databaseConnection->exec_SELECTquery(
1358 'tt_content.uid',
1359 'tt_content,sys_language',
1360 $whereClause
1361 );
1362 while ($row = $databaseConnection->sql_fetch_assoc($res)) {
1363 $this->contentElementCache[$languageId][$colPos][$row['uid']] = $row;
1364 }
1365 $databaseConnection->sql_free_result($res);
1366 }
1367 if (is_array($this->contentElementCache[$languageId][$colPos])) {
1368 return array_keys($this->contentElementCache[$languageId][$colPos]);
1369 }
1370 return array();
1371 }
1372
1373 /**
1374 * Check the editlock access
1375 *
1376 * @return bool
1377 */
1378 public function pageIsNotLockedForEditors()
1379 {
1380 return $this->getBackendUser()->isAdmin() || !($this->CALC_PERMS & Permission::PAGE_EDIT && $this->pageinfo['editlock']);
1381 }
1382
1383 /**
1384 * Returns LanguageService
1385 *
1386 * @return \TYPO3\CMS\Lang\LanguageService
1387 */
1388 protected function getLanguageService()
1389 {
1390 return $GLOBALS['LANG'];
1391 }
1392
1393 /**
1394 * Returns the current BE user.
1395 *
1396 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1397 */
1398 protected function getBackendUser()
1399 {
1400 return $GLOBALS['BE_USER'];
1401 }
1402
1403 /**
1404 * Returns the database connection
1405 *
1406 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
1407 */
1408 protected function getDatabaseConnection()
1409 {
1410 return $GLOBALS['TYPO3_DB'];
1411 }
1412
1413 /**
1414 * Returns current PageRenderer
1415 *
1416 * @return PageRenderer
1417 */
1418 protected function getPageRenderer()
1419 {
1420 return GeneralUtility::makeInstance(PageRenderer::class);
1421 }
1422
1423 /**
1424 * @param $edit_record array
1425 *
1426 * @return array
1427 */
1428 protected function makeQuickEditMenu($edit_record)
1429 {
1430 $lang = $this->getLanguageService();
1431 $databaseConnection = $this->getDatabaseConnection();
1432 $beUser = $this->getBackendUser();
1433
1434 $quickEditMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1435 $quickEditMenu->setIdentifier('quickEditMenu');
1436 $quickEditMenu->setLabel('');
1437
1438 // Setting close url/return url for exiting this script:
1439 // Goes to 'Columns' view if close is pressed (default)
1440 $this->closeUrl = $this->local_linkThisScript(array('SET' => array('function' => 1)));
1441 if ($this->returnUrl) {
1442 $this->closeUrl = $this->returnUrl;
1443 }
1444 $retUrlStr = $this->returnUrl ? '&returnUrl=' . rawurlencode($this->returnUrl) : '';
1445
1446 // Creating the selector box, allowing the user to select which element to edit:
1447 $isSelected = 0;
1448 $languageOverlayRecord = '';
1449 if ($this->current_sys_language) {
1450 list($languageOverlayRecord) = BackendUtility::getRecordsByField(
1451 'pages_language_overlay',
1452 'pid',
1453 $this->id,
1454 'AND sys_language_uid=' . (int)$this->current_sys_language
1455 );
1456 }
1457 if (is_array($languageOverlayRecord)) {
1458 $inValue = 'pages_language_overlay:' . $languageOverlayRecord['uid'];
1459 $isSelected += (int)$edit_record == $inValue;
1460 $menuItem = $quickEditMenu->makeMenuItem()
1461 ->setTitle('[ ' . $lang->getLL('editLanguageHeader', true) . ' ]')
1462 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&edit_record=' . $inValue . $retUrlStr)
1463 ->setActive($edit_record == $inValue);
1464 $quickEditMenu->addMenuItem($menuItem);
1465 } else {
1466 $inValue = 'pages:' . $this->id;
1467 $isSelected += (int)$edit_record == $inValue;
1468 $menuItem = $quickEditMenu->makeMenuItem()
1469 ->setTitle('[ ' . $lang->getLL('editPageProperties', true) . ' ]')
1470 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&edit_record=' . $inValue . $retUrlStr)
1471 ->setActive($edit_record == $inValue);
1472 $quickEditMenu->addMenuItem($menuItem);
1473 }
1474 // Selecting all content elements from this language and allowed colPos:
1475 $whereClause = 'pid=' . (int)$this->id . ' AND sys_language_uid=' . (int)$this->current_sys_language . ' AND colPos IN (' . $this->colPosList . ')' . ($this->MOD_SETTINGS['tt_content_showHidden'] ? '' : BackendUtility::BEenableFields('tt_content')) . BackendUtility::deleteClause('tt_content') . BackendUtility::versioningPlaceholderClause('tt_content');
1476 if (!$this->getBackendUser()->user['admin']) {
1477 $whereClause .= ' AND editlock = 0';
1478 }
1479 $res = $databaseConnection->exec_SELECTquery('*', 'tt_content', $whereClause, '', 'colPos,sorting');
1480 $colPos = null;
1481 $first = 1;
1482 // Page is the pid if no record to put this after.
1483 $prev = $this->id;
1484 while ($cRow = $databaseConnection->sql_fetch_assoc($res)) {
1485 BackendUtility::workspaceOL('tt_content', $cRow);
1486 if (is_array($cRow)) {
1487 if ($first) {
1488 if (!$edit_record) {
1489 $edit_record = 'tt_content:' . $cRow['uid'];
1490 }
1491 $first = 0;
1492 }
1493 if (!isset($colPos) || $cRow['colPos'] !== $colPos) {
1494 $colPos = $cRow['colPos'];
1495 $menuItem = $quickEditMenu->makeMenuItem()
1496 ->setTitle(' ')
1497 ->setHref('#');
1498 $quickEditMenu->addMenuItem($menuItem);
1499 $menuItem = $quickEditMenu->makeMenuItem()
1500 ->setTitle('__' . $lang->sL(BackendUtility::getLabelFromItemlist('tt_content', 'colPos', $colPos), true) . ':__')
1501 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&edit_record=_EDIT_COL:' . $colPos . $retUrlStr);
1502 $quickEditMenu->addMenuItem($menuItem);
1503 }
1504 $inValue = 'tt_content:' . $cRow['uid'];
1505 $isSelected += (int)$edit_record == $inValue;
1506 $menuItem = $quickEditMenu->makeMenuItem()
1507 ->setTitle(htmlspecialchars(GeneralUtility::fixed_lgd_cs(($cRow['header'] ? $cRow['header'] : '[' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.no_title') . '] ' . strip_tags($cRow['bodytext'])), $beUser->uc['titleLen'])))
1508 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&edit_record=' . $inValue . $retUrlStr)
1509 ->setActive($edit_record == $inValue);
1510 $quickEditMenu->addMenuItem($menuItem);
1511 $prev = -$cRow['uid'];
1512 }
1513 }
1514 // If edit_record is not set (meaning, no content elements was found for this language) we simply set it to create a new element:
1515 if (!$edit_record) {
1516 $edit_record = 'tt_content:new/' . $prev . '/' . $colPos;
1517 $inValue = 'tt_content:new/' . $prev . '/' . $colPos;
1518 $isSelected += (int)$edit_record == $inValue;
1519 $menuItem = $quickEditMenu->makeMenuItem()
1520 ->setTitle('[ ' . $lang->getLL('newLabel', 1) . ' ]')
1521 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&edit_record=' . $inValue . $retUrlStr)
1522 ->setActive($edit_record == $inValue);
1523 $quickEditMenu->addMenuItem($menuItem);
1524 }
1525 // If none is yet selected...
1526 if (!$isSelected) {
1527 $menuItem = $quickEditMenu->makeMenuItem()
1528 ->setTitle('__________')
1529 ->setHref('#');
1530 $quickEditMenu->addMenuItem($menuItem);
1531 $menuItem = $quickEditMenu->makeMenuItem()
1532 ->setTitle('[ ' . $lang->getLL('newLabel', true) . ' ]')
1533 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&edit_record=' . $edit_record . $retUrlStr)
1534 ->setActive($edit_record == $inValue);
1535 $quickEditMenu->addMenuItem($menuItem);
1536 }
1537 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($quickEditMenu);
1538 return $edit_record;
1539 }
1540
1541 /**
1542 * Make the LanguageMenu
1543 *
1544 * @return void
1545 */
1546 protected function makeLanguageMenu()
1547 {
1548 if (count($this->MOD_MENU['language']) > 1) {
1549 $lang = $this->getLanguageService();
1550 $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1551 $languageMenu->setIdentifier('languageMenu');
1552 $languageMenu->setLabel($lang->sL('LLL:EXT:lang/locallang_general.xlf:LGL.language', true));
1553 foreach ($this->MOD_MENU['language'] as $key => $language) {
1554 $menuItem = $languageMenu
1555 ->makeMenuItem()
1556 ->setTitle($language)
1557 ->setHref(BackendUtility::getModuleUrl($this->moduleName) . '&id=' . $this->id . '&SET[language]=' . $key);
1558 if ((int)$this->current_sys_language === $key) {
1559 $menuItem->setActive(true);
1560 }
1561 $languageMenu->addMenuItem($menuItem);
1562 }
1563 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1564 }
1565 }
1566 }