2 namespace TYPO3\CMS\Backend\Controller
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
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\Form\Utility\FormEngineUtility
;
25 use TYPO3\CMS\Backend\Module\AbstractModule
;
26 use TYPO3\CMS\Backend\Template\Components\ButtonBar
;
27 use TYPO3\CMS\Backend\Utility\BackendUtility
;
28 use TYPO3\CMS\Core\DataHandling\DataHandler
;
29 use TYPO3\CMS\Core\Imaging\Icon
;
30 use TYPO3\CMS\Core\Messaging\FlashMessage
;
31 use TYPO3\CMS\Core\Messaging\FlashMessageQueue
;
32 use TYPO3\CMS\Core\Messaging\FlashMessageService
;
33 use TYPO3\CMS\Core\Page\PageRenderer
;
34 use TYPO3\CMS\Core\Type\Bitmask\Permission
;
35 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility
;
36 use TYPO3\CMS\Core\Utility\GeneralUtility
;
37 use TYPO3\CMS\Core\Utility\HttpUtility
;
38 use TYPO3\CMS\Core\Utility\MathUtility
;
39 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher
;
40 use TYPO3\CMS\Frontend\Page\PageRepository
;
43 * Script Class: Drawing the editing form for editing records in TYPO3.
44 * Notice: It does NOT use tce_db.php to submit data to, rather it handles submissions itself
46 class EditDocumentController
extends AbstractModule
49 * GPvar "edit": Is an array looking approx like [tablename][list-of-ids]=command, eg.
50 * "&edit[pages][123]=edit". See \TYPO3\CMS\Backend\Utility\BackendUtility::editOnClick(). Value can be seen
51 * modified internally (converting NEW keyword to id, workspace/versioning etc).
58 * Commalist of fieldnames to edit. The point is IF you specify this list, only those
59 * fields will be rendered in the form. Otherwise all (available) fields in the record
60 * is shown according to the types configuration in $GLOBALS['TCA']
67 * Default values for fields (array with tablenames, fields etc. as keys).
68 * Can be seen modified internally.
75 * Array of values to force being set (as hidden fields). Will be set as $this->defVals
76 * IF defVals does not exist.
83 * If set, this value will be set in $this->retUrl (which is used quite many places
84 * as the return URL). If not set, "dummy.php" will be set in $this->retUrl
91 * Close-document command. Not really sure of all options...
98 * Quite simply, if this variable is set, then the processing of incoming data will be performed
99 * as if a save-button is pressed. Used in the forms as a hidden field which can be set through
100 * JavaScript if the form is somehow submitted by JavaScript).
107 * The data array from which the data comes...
131 * Redirect (not used???)
138 * Boolean: If set, then the GET var "&id=" will be added to the
139 * retUrl string so that the NEW id of something is returned to the script calling the form.
143 public $returnNewPageId;
158 * ID for displaying the page in the frontend (used for SAVE/VIEW operations)
165 * Additional GET vars for the link, eg. "&L=xxx"
169 public $popViewId_addParams;
172 * Alternative URL for viewing the frontend pages.
179 * Alternative title for the document handler.
186 * If set, then no SAVE/VIEW button is printed
195 public $perms_clause;
198 * If set, the $this->editconf array is returned to the calling script
199 * (used by wizard_add.php for instance)
203 public $returnEditConf;
206 * Workspace used for the editing action.
210 protected $workspace;
213 * document template object
215 * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
220 * a static HTML template, usually in templates/alt_doc.html
227 * Content accumulation
234 * Return URL script, processed. This contains the script (if any) that we should
235 * RETURN TO from the FormEngine script IF we press the close button. Thus this
236 * variable is normally passed along from the calling script so we can properly return if needed.
243 * Contains the parts of the REQUEST_URI (current url). By parts we mean the result of resolving
244 * REQUEST_URI (current url) by the parse_url() function. The result is an array where eg. "path"
245 * is the script path and "query" is the parameters...
252 * Contains the current GET vars array; More specifically this array is the foundation for creating
253 * the R_URI internal var (which becomes the "url of this script" to which we submit the forms etc.)
257 public $R_URL_getvars;
260 * Set to the URL of this script including variables which is needed to re-display the form. See main()
277 * Is loaded with the "title" of the currently "open document" - this is used in the
278 * Document Selector box. (see makeDocSel())
282 public $storeTitle = '';
285 * Contains an array with key/value pairs of GET parameters needed to reach the
286 * current document displayed - used in the Document Selector box. (see compileStoreDat())
293 * Contains storeArray, but imploded into a GET parameter string (see compileStoreDat())
300 * Hashed value of storeURL (see compileStoreDat())
307 * Module session data
314 * An array of the "open documents" - keys are md5 hashes (see $storeUrlMd5) identifying
315 * the various documents on the GET parameter list needed to open it. The values are
316 * arrays with 0,1,2 keys with information about the document (see compileStoreDat()).
317 * The docHandler variable is stored in the $docDat session data, key "0".
324 * Array of the elements to create edit forms for.
328 public $elementsData;
331 * Pointer to the first element in $elementsData
338 * Counter, used to count the number of errors (when users do not have edit permissions)
345 * Counter, used to count the number of new record forms displayed
352 * Is set to the pid value of the last shown record - thus indicating which page to
353 * show when clicking the SAVE/VIEW button
360 * Is set to additional parameters (like "&L=xxx") if the record supports it.
364 public $viewId_addParams;
367 * Module TSconfig, loaded from main() based on the page id value of viewId
374 * @var FormResultCompiler
376 protected $formResultCompiler;
379 * Used internally to disable the storage of the document reference (eg. new records)
383 public $dontStoreDocumentRef = 0;
386 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
388 protected $signalSlotDispatcher;
391 * Stores information needed to preview the currently saved record
395 protected $previewData = [];
400 public function __construct()
402 parent
::__construct();
403 $GLOBALS['SOBE'] = $this;
404 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_alt_doc.xlf');
408 * Get the SignalSlot dispatcher
410 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
412 protected function getSignalSlotDispatcher()
414 if (!isset($this->signalSlotDispatcher
)) {
415 $this->signalSlotDispatcher
= GeneralUtility
::makeInstance(Dispatcher
::class);
417 return $this->signalSlotDispatcher
;
421 * Emits a signal after a function was executed
423 * @param string $signalName
425 protected function emitFunctionAfterSignal($signalName)
427 $this->getSignalSlotDispatcher()->dispatch(__CLASS__
, $signalName . 'After', array($this));
431 * First initialization.
435 public function preInit()
437 if (GeneralUtility
::_GP('justLocalized')) {
438 $this->localizationRedirect(GeneralUtility
::_GP('justLocalized'));
441 $this->editconf
= GeneralUtility
::_GP('edit');
442 $this->defVals
= GeneralUtility
::_GP('defVals');
443 $this->overrideVals
= GeneralUtility
::_GP('overrideVals');
444 $this->columnsOnly
= GeneralUtility
::_GP('columnsOnly');
445 $this->returnUrl
= GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('returnUrl'));
446 $this->closeDoc
= GeneralUtility
::_GP('closeDoc');
447 $this->doSave
= GeneralUtility
::_GP('doSave');
448 $this->returnEditConf
= GeneralUtility
::_GP('returnEditConf');
449 $this->localizationMode
= GeneralUtility
::_GP('localizationMode');
450 $this->workspace
= GeneralUtility
::_GP('workspace');
451 $this->uc
= GeneralUtility
::_GP('uc');
452 // Setting override values as default if defVals does not exist.
453 if (!is_array($this->defVals
) && is_array($this->overrideVals
)) {
454 $this->defVals
= $this->overrideVals
;
456 // Setting return URL
457 $this->retUrl
= $this->returnUrl ?
: BackendUtility
::getModuleUrl('dummy');
458 // Fix $this->editconf if versioning applies to any of the records
459 $this->fixWSversioningInEditConf();
460 // Make R_URL (request url) based on input GETvars:
461 $this->R_URL_parts
= parse_url(GeneralUtility
::getIndpEnv('REQUEST_URI'));
462 $this->R_URL_getvars
= GeneralUtility
::_GET();
463 $this->R_URL_getvars
['edit'] = $this->editconf
;
464 // MAKE url for storing
465 $this->compileStoreDat();
466 // Get session data for the module:
467 $this->docDat
= $this->getBackendUser()->getModuleData('FormEngine', 'ses');
468 $this->docHandler
= $this->docDat
[0];
469 // If a request for closing the document has been sent, act accordingly:
470 if ($this->closeDoc
> 0) {
471 $this->closeDocument($this->closeDoc
);
473 // If NO vars are sent to the script, try to read first document:
474 // Added !is_array($this->editconf) because editConf must not be set either.
475 // Anyways I can't figure out when this situation here will apply...
476 if (is_array($this->R_URL_getvars
) && count($this->R_URL_getvars
) < 2 && !is_array($this->editconf
)) {
477 $this->setDocument($this->docDat
[1]);
480 // Sets a temporary workspace, this request is based on
481 if ($this->workspace
!== null) {
482 $this->getBackendUser()->setTemporaryWorkspace($this->workspace
);
485 $this->emitFunctionAfterSignal(__FUNCTION__
);
489 * Detects, if a save command has been triggered.
491 * @return bool TRUE, then save the document (data submitted)
493 public function doProcessData()
496 ||
isset($_POST['_savedok'])
497 ||
isset($_POST['_saveandclosedok'])
498 ||
isset($_POST['_savedokview'])
499 ||
isset($_POST['_savedoknew'])
500 ||
isset($_POST['_translation_savedok_x'])
501 ||
isset($_POST['_translation_savedokclear_x']);
506 * Do processing of data, submitting it to TCEmain.
510 public function processData()
512 $beUser = $this->getBackendUser();
513 // GPvars specifically for processing:
514 $control = GeneralUtility
::_GP('control');
515 $this->data
= GeneralUtility
::_GP('data');
516 $this->cmd
= GeneralUtility
::_GP('cmd');
517 $this->mirror
= GeneralUtility
::_GP('mirror');
518 $this->cacheCmd
= GeneralUtility
::_GP('cacheCmd');
519 $this->redirect
= GeneralUtility
::_GP('redirect');
520 $this->returnNewPageId
= GeneralUtility
::_GP('returnNewPageId');
521 $this->vC
= GeneralUtility
::_GP('vC');
522 // See tce_db.php for relevate options here:
523 // Only options related to $this->data submission are included here.
524 /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */
525 $tce = GeneralUtility
::makeInstance(DataHandler
::class);
527 if (!empty($control)) {
528 $tce->setControl($control);
530 if (isset($_POST['_translation_savedok_x'])) {
531 $tce->updateModeL10NdiffData
= 'FORCE_FFUPD';
533 if (isset($_POST['_translation_savedokclear_x'])) {
534 $tce->updateModeL10NdiffData
= 'FORCE_FFUPD';
535 $tce->updateModeL10NdiffDataClear
= true;
537 // Setting default values specific for the user:
538 $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults');
539 if (is_array($TCAdefaultOverride)) {
540 $tce->setDefaultsFromUserTS($TCAdefaultOverride);
542 // Setting internal vars:
543 if ($beUser->uc
['neverHideAtCopy']) {
544 $tce->neverHideAtCopy
= 1;
546 // Loading TCEmain with data:
547 $tce->start($this->data
, $this->cmd
);
548 if (is_array($this->mirror
)) {
549 $tce->setMirror($this->mirror
);
551 // Checking referer / executing
552 $refInfo = parse_url(GeneralUtility
::getIndpEnv('HTTP_REFERER'));
553 $httpHost = GeneralUtility
::getIndpEnv('TYPO3_HOST_ONLY');
554 if ($httpHost != $refInfo['host']
555 && $this->vC
!= $beUser->veriCode()
556 && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']
564 'Referer host \'%s\' and server host \'%s\' did not match and veriCode was not valid either!',
566 array($refInfo['host'], $httpHost)
568 debug('Error: Referer host did not match with server host.');
570 // Perform the saving operation with TCEmain:
571 $tce->process_uploads($_FILES);
572 $tce->process_datamap();
573 $tce->process_cmdmap();
574 // If pages are being edited, we set an instruction about updating the page tree after this operation.
575 if ($tce->pagetreeNeedsRefresh
576 && (isset($this->data
['pages']) ||
$beUser->workspace
!= 0 && !empty($this->data
))
578 BackendUtility
::setUpdateSignal('updatePageTree');
580 // If there was saved any new items, load them:
581 if (!empty($tce->substNEWwithIDs_table
)) {
582 // save the expanded/collapsed states for new inline records, if any
583 FormEngineUtility
::updateInlineView($this->uc
, $tce);
584 $newEditConf = array();
585 foreach ($this->editconf
as $tableName => $tableCmds) {
586 $keys = array_keys($tce->substNEWwithIDs_table
, $tableName);
588 foreach ($keys as $key) {
589 $editId = $tce->substNEWwithIDs
[$key];
590 // Check if the $editId isn't a child record of an IRRE action
591 if (!(is_array($tce->newRelatedIDs
[$tableName])
592 && in_array($editId, $tce->newRelatedIDs
[$tableName]))
594 // Translate new id to the workspace version:
595 if ($versionRec = BackendUtility
::getWorkspaceVersionOfRecord(
601 $editId = $versionRec['uid'];
603 $newEditConf[$tableName][$editId] = 'edit';
605 // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
607 if ($tableName == 'pages'
608 && $this->retUrl
!= BackendUtility
::getModuleUrl('dummy')
609 && $this->returnNewPageId
611 $this->retUrl
.= '&id=' . $tce->substNEWwithIDs
[$key];
615 $newEditConf[$tableName] = $tableCmds;
618 // Resetting editconf if newEditConf has values:
619 if (!empty($newEditConf)) {
620 $this->editconf
= $newEditConf;
622 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
623 $this->R_URL_getvars
['edit'] = $this->editconf
;
624 // Unsetting default values since we don't need them anymore.
625 unset($this->R_URL_getvars
['defVals']);
626 // Re-compile the store* values since editconf changed...
627 $this->compileStoreDat();
629 // See if any records was auto-created as new versions?
630 if (!empty($tce->autoVersionIdMap
)) {
631 $this->fixWSversioningInEditConf($tce->autoVersionIdMap
);
633 // If a document is saved and a new one is created right after.
634 if (isset($_POST['_savedoknew']) && is_array($this->editconf
)) {
635 // Finding the current table:
636 reset($this->editconf
);
637 $nTable = key($this->editconf
);
638 // Finding the first id, getting the records pid+uid
639 reset($this->editconf
[$nTable]);
640 $nUid = key($this->editconf
[$nTable]);
641 $nRec = BackendUtility
::getRecord($nTable, $nUid, 'pid,uid');
642 // Setting a blank editconf array for a new record:
643 $this->editconf
= array();
644 if ($this->getNewIconMode($nTable) == 'top') {
645 $this->editconf
[$nTable][$nRec['pid']] = 'new';
647 $this->editconf
[$nTable][-$nRec['uid']] = 'new';
649 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
650 $this->R_URL_getvars
['edit'] = $this->editconf
;
651 // Re-compile the store* values since editconf changed...
652 $this->compileStoreDat();
654 // If a preview is requested
655 if (isset($_POST['_savedokview'])) {
656 // Get the first table and id of the data array from DataHandler
657 $table = reset(array_keys($this->data
));
658 $id = reset(array_keys($this->data
[$table]));
659 if (!MathUtility
::canBeInterpretedAsInteger($id)) {
660 $id = $tce->substNEWwithIDs
[$id];
662 // Store this information for later use
663 $this->previewData
['table'] = $table;
664 $this->previewData
['id'] = $id;
666 $tce->printLogErrorMessages(isset($_POST['_saveandclosedok']) ||
isset($_POST['_translation_savedok_x']) ?
$this->retUrl
: $this->R_URL_parts
['path'] . '?' . GeneralUtility
::implodeArrayForUrl('', $this->R_URL_getvars
));
668 // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
669 // because if not, we just get that element re-listed as new. And we don't want that!
670 if (isset($_POST['_saveandclosedok']) ||
isset($_POST['_translation_savedok_x']) ||
$this->closeDoc
< 0) {
671 $this->closeDocument(abs($this->closeDoc
));
676 * Initialize the normal module operation
680 public function init()
682 $beUser = $this->getBackendUser();
683 // Setting more GPvars:
684 $this->popViewId
= GeneralUtility
::_GP('popViewId');
685 $this->popViewId_addParams
= GeneralUtility
::_GP('popViewId_addParams');
686 $this->viewUrl
= GeneralUtility
::_GP('viewUrl');
687 $this->recTitle
= GeneralUtility
::_GP('recTitle');
688 $this->noView
= GeneralUtility
::_GP('noView');
689 $this->perms_clause
= $beUser->getPagePermsClause(1);
690 // Set other internal variables:
691 $this->R_URL_getvars
['returnUrl'] = $this->retUrl
;
692 $this->R_URI
= $this->R_URL_parts
['path'] . '?' . ltrim(GeneralUtility
::implodeArrayForUrl(
696 // Setting virtual document name
697 $this->MCONF
['name'] = 'xMOD_alt_doc.php';
699 // Create an instance of the document template object
700 $this->doc
= $GLOBALS['TBE_TEMPLATE'];
701 $pageRenderer = GeneralUtility
::makeInstance(PageRenderer
::class);
702 $pageRenderer->addInlineLanguageLabelFile('EXT:lang/locallang_alt_doc.xlf');
703 // override the default jumpToUrl
704 $this->moduleTemplate
->addJavaScriptCode(
707 function jumpToUrl(URL,formEl) {
708 if (!TBE_EDITOR.isFormChanged()) {
709 window.location.href = URL;
710 } else if (formEl && formEl.type=="checkbox") {
711 formEl.checked = formEl.checked ? 0 : 1;
716 // define the window size of the element browser
717 $popupWindowWidth = 700;
718 $popupWindowHeight = 750;
719 $popupWindowSize = trim($beUser->getTSConfigVal('options.popupWindowSize'));
720 if (!empty($popupWindowSize)) {
721 list($popupWindowWidth, $popupWindowHeight) = GeneralUtility
::intExplode('x', $popupWindowSize);
723 $t3Configuration = array(
724 'PopupWindow' => array(
725 'width' => $popupWindowWidth,
726 'height' => $popupWindowHeight
730 if (ExtensionManagementUtility
::isLoaded('feedit') && (int)GeneralUtility
::_GP('feEdit') === 1) {
731 // We have to load some locallang strings and push them into TYPO3.LLL if this request was
732 // triggered by feedit. Originally, this object is fed by BackendController which is not
733 // called here. This block of code is intended to be removed at a later point again.
734 $lang = $this->getLanguageService();
736 'csh_tooltip_loading' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:csh_tooltip_loading')
738 $generatedLabels = array();
739 $generatedLabels['core'] = $coreLabels;
740 $code = 'TYPO3.LLL = ' . json_encode($generatedLabels) . ';';
741 $filePath = 'typo3temp/assets/js/backend-' . sha1($code) . '.js';
742 if (!file_exists(PATH_site
. $filePath)) {
743 // writeFileToTypo3tempDir() returns NULL on success (please double-read!)
744 $error = GeneralUtility
::writeFileToTypo3tempDir(PATH_site
. $filePath, $code);
745 if ($error !== null) {
746 throw new \
RuntimeException('Locallang JS file could not be written to ' . $filePath . '. Reason: ' . $error, 1446118286);
749 $pageRenderer->addJsFile('../' . $filePath);
751 // define the window size of the popups within the RTE
752 $rtePopupWindowSize = trim($beUser->getTSConfigVal('options.rte.popupWindowSize'));
753 if (!empty($rtePopupWindowSize)) {
754 list($rtePopupWindowWidth, $rtePopupWindowHeight) = GeneralUtility
::trimExplode('x', $rtePopupWindowSize);
756 $rtePopupWindowWidth = !empty($rtePopupWindowWidth) ?
(int)$rtePopupWindowWidth : ($popupWindowWidth-200);
757 $rtePopupWindowHeight = !empty($rtePopupWindowHeight) ?
(int)$rtePopupWindowHeight : ($popupWindowHeight-250);
758 $t3Configuration['RTEPopupWindow'] = [
759 'width' => $rtePopupWindowWidth,
760 'height' => $rtePopupWindowHeight
765 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
767 // passwordDummy is used by tbe_editor.js and has to be declared here as
768 // TS object overwrites the object declared in tbe_editor.js
769 function typoSetup() { //
771 this.passwordDummy = "********";
773 var TS = new typoSetup();
776 function launchView(table,uid) { //
777 var thePreviewWindow = window.open(
778 ' . GeneralUtility
::quoteJSvalue(BackendUtility
::getModuleUrl('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
779 "ShowItem" + TS.uniqueID,
780 "height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
782 if (thePreviewWindow && thePreviewWindow.focus) {
783 thePreviewWindow.focus();
786 function deleteRecord(table,id,url) { //
787 window.location.href = ' . GeneralUtility
::quoteJSvalue(BackendUtility
::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&vC=' . $beUser->veriCode() . '&prErr=1&uPT=1";
791 $previewCode = isset($_POST['_savedokview']) && $this->popViewId ?
$this->generatePreviewCode() : '';
792 $this->moduleTemplate
->addJavaScriptCode(
794 $javascript . $previewCode
796 // Setting up the context sensitive menu:
797 $this->moduleTemplate
->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
799 $this->emitFunctionAfterSignal(__FUNCTION__
);
805 protected function generatePreviewCode()
807 $table = $this->previewData
['table'];
808 $recordId = $this->previewData
['id'];
810 if ($table === 'pages') {
811 $currentPageId = $recordId;
813 $currentPageId = MathUtility
::convertToPositiveInteger($this->popViewId
);
816 $pageTsConfig = BackendUtility
::getPagesTSconfig($currentPageId);
817 $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
818 ?
$pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
821 $recordArray = BackendUtility
::getRecord($table, $recordId);
823 // find the right preview page id
825 if (isset($previewConfiguration['previewPageId'])) {
826 $previewPageId = $previewConfiguration['previewPageId'];
828 // if no preview page was configured
829 if (!$previewPageId) {
830 $rootPageData = null;
831 $rootLine = BackendUtility
::BEgetRootLine($currentPageId);
832 $currentPage = reset($rootLine);
833 if ((int)$currentPage['doktype'] === PageRepository
::DOKTYPE_DEFAULT
) {
834 // try the current page
835 $previewPageId = $currentPageId;
837 // or search for the root page
838 foreach ($rootLine as $page) {
839 if ($page['is_siteroot']) {
840 $rootPageData = $page;
844 $previewPageId = isset($rootPageData)
845 ?
(int)$rootPageData['uid']
855 $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
856 ?
$GLOBALS['TCA'][$table]['ctrl']['languageField']
858 if ($languageField && !empty($recordArray[$languageField])) {
859 $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
860 ?
$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
862 if ($l18nPointer && !empty($recordArray[$l18nPointer])
863 && isset($previewConfiguration['useDefaultLanguageRecord'])
864 && !$previewConfiguration['useDefaultLanguageRecord']
867 $recordId = $recordArray[$l18nPointer];
869 $linkParameters['L'] = $recordArray[$languageField];
872 // map record data to GET parameters
873 if (isset($previewConfiguration['fieldToParameterMap.'])) {
874 foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
875 $value = $recordArray[$field];
876 if ($field === 'uid') {
879 $linkParameters[$parameterName] = $value;
883 // add/override parameters by configuration
884 if (isset($previewConfiguration['additionalGetParameters.'])) {
885 $additionalGetParameters = [];
886 $this->parseAdditionalGetParameters(
887 $additionalGetParameters,
888 $previewConfiguration['additionalGetParameters.']
890 $linkParameters = array_replace($linkParameters, $additionalGetParameters);
893 $this->popViewId
= $previewPageId;
894 $this->popViewId_addParams
= GeneralUtility
::implodeArrayForUrl('', $linkParameters, '', false, true);
896 $previewPageRootline = BackendUtility
::BEgetRootLine($this->popViewId
);
900 . BackendUtility
::viewOnClick(
903 $previewPageRootline,
906 $this->popViewId_addParams
,
912 . BackendUtility
::viewOnClick(
915 $previewPageRootline,
918 $this->popViewId_addParams
925 * Migrates a set of (possibly nested) GET parameters in TypoScript syntax to a plain array
927 * This basically removes the trailing dots of sub-array keys in TypoScript.
928 * The result can be used to create a query string with GeneralUtility::implodeArrayForUrl().
930 * @param array $parameters Should be an empty array by default
931 * @param array $typoScript The TypoScript configuration
933 protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
935 foreach ($typoScript as $key => $value) {
936 if (is_array($value)) {
937 $key = rtrim($key, '.');
938 $parameters[$key] = [];
939 $this->parseAdditionalGetParameters($parameters[$key], $value);
941 $parameters[$key] = $value;
947 * Main module operation
951 public function main()
955 if (is_array($this->editconf
)) {
956 /** @var FormResultCompiler formResultCompiler */
957 $this->formResultCompiler
= GeneralUtility
::makeInstance(FormResultCompiler
::class);
959 // Creating the editing form, wrap it with buttons, document selector etc.
960 $editForm = $this->makeEditForm();
962 $this->firstEl
= reset($this->elementsData
);
963 // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
964 if (($this->docDat
[1] !== $this->storeUrlMd5
965 ||
!isset($this->docHandler
[$this->storeUrlMd5
]))
966 && !$this->dontStoreDocumentRef
968 $this->docHandler
[$this->storeUrlMd5
] = array(
974 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler
, $this->storeUrlMd5
));
975 BackendUtility
::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler
));
977 // Module configuration
978 $this->modTSconfig
= $this->viewId ? BackendUtility
::getModTSconfig(
982 $body = $this->formResultCompiler
->JStop();
983 $body .= $this->compileForm($editForm);
984 $body .= $this->formResultCompiler
->printNeededJSFunctions();
989 // The page will show only if there is a valid page and if this page may be viewed by the user
990 $this->pageinfo
= BackendUtility
::readPageAccess($this->viewId
, $this->perms_clause
);
991 if ($this->pageinfo
) {
992 $this->moduleTemplate
->getDocHeaderComponent()->setMetaInformation($this->pageinfo
);
994 // Setting up the buttons and markers for docheader
996 $this->languageSwitch($this->firstEl
['table'], $this->firstEl
['uid'], $this->firstEl
['pid']);
997 $this->moduleTemplate
->setContent($body);
1000 /***************************
1002 * Sub-content functions, rendering specific parts of the module content.
1004 ***************************/
1006 * Creates the editing form with FormEnigne, based on the input from GPvars.
1008 * @return string HTML form elements wrapped in tables
1010 public function makeEditForm()
1012 // Initialize variables:
1013 $this->elementsData
= array();
1018 $beUser = $this->getBackendUser();
1019 // Traverse the GPvar edit array
1021 foreach ($this->editconf
as $table => $conf) {
1022 if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
1023 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1024 foreach ($conf as $cKey => $command) {
1025 if ($command == 'edit' ||
$command == 'new') {
1027 $ids = GeneralUtility
::trimExplode(',', $cKey, true);
1028 // Traverse the ids:
1029 foreach ($ids as $theUid) {
1030 // Don't save this document title in the document selector if the document is new.
1031 if ($command === 'new') {
1032 $this->dontStoreDocumentRef
= 1;
1035 /** @var TcaDatabaseRecord $formDataGroup */
1036 $formDataGroup = GeneralUtility
::makeInstance(TcaDatabaseRecord
::class);
1037 /** @var FormDataCompiler $formDataCompiler */
1038 $formDataCompiler = GeneralUtility
::makeInstance(FormDataCompiler
::class, $formDataGroup);
1039 /** @var NodeFactory $nodeFactory */
1040 $nodeFactory = GeneralUtility
::makeInstance(NodeFactory
::class);
1043 // Reset viewId - it should hold data of last entry only
1045 $this->viewId_addParams
= '';
1047 $formDataCompilerInput = [
1048 'tableName' => $table,
1049 'vanillaUid' => (int)$theUid,
1050 'command' => $command,
1051 'returnUrl' => $this->R_URI
,
1053 if (is_array($this->overrideVals
) && is_array($this->overrideVals
[$table])) {
1054 $formDataCompilerInput['overrideValues'] = $this->overrideVals
[$table];
1057 $formData = $formDataCompiler->compile($formDataCompilerInput);
1059 // Set this->viewId if possible
1060 if ($command === 'new'
1061 && $table !== 'pages'
1062 && !empty($formData['parentPageRow']['uid'])
1064 $this->viewId
= $formData['parentPageRow']['uid'];
1066 if ($table == 'pages') {
1067 $this->viewId
= $formData['databaseRow']['uid'];
1068 } elseif (!empty($formData['parentPageRow']['uid'])) {
1069 $this->viewId
= $formData['parentPageRow']['uid'];
1070 // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1071 if (!empty($formData['processedTca']['ctrl']['languageField'])
1072 && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1073 && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1075 $this->viewId_addParams
= '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1080 // Determine if delete button can be shown
1081 $deleteAccess = false;
1082 if ($command === 'edit') {
1083 $permission = $formData['userPermissionOnPage'];
1084 if ($formData['tableName'] === 'pages') {
1085 $deleteAccess = $permission & Permission
::PAGE_DELETE ?
true : false;
1087 $deleteAccess = $permission & Permission
::CONTENT_EDIT ?
true : false;
1091 // Display "is-locked" message:
1092 if ($command === 'edit') {
1093 $lockInfo = BackendUtility
::isRecordLocked($table, $formData['databaseRow']['uid']);
1095 /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
1096 $flashMessage = GeneralUtility
::makeInstance(
1097 FlashMessage
::class,
1100 FlashMessage
::WARNING
1102 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1103 $flashMessageService = GeneralUtility
::makeInstance(FlashMessageService
::class);
1104 /** @var $defaultFlashMessageQueue FlashMessageQueue */
1105 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1106 $defaultFlashMessageQueue->enqueue($flashMessage);
1111 if (!$this->storeTitle
) {
1112 $this->storeTitle
= $this->recTitle
1113 ?
htmlspecialchars($this->recTitle
)
1114 : BackendUtility
::getRecordTitle($table, FormEngineUtility
::databaseRowCompatibility($formData['databaseRow']), true);
1117 $this->elementsData
[] = array(
1119 'uid' => $formData['databaseRow']['uid'],
1120 'pid' => $formData['databaseRow']['pid'],
1122 'deleteAccess' => $deleteAccess
1125 if ($command !== 'new') {
1126 BackendUtility
::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ?
$formData['databaseRow']['pid'] : 0);
1129 // Set list if only specific fields should be rendered. This will trigger
1130 // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1131 if ($this->columnsOnly
) {
1132 if (is_array($this->columnsOnly
)) {
1133 $formData['fieldListToRender'] = $this->columnsOnly
[$table];
1135 $formData['fieldListToRender'] = $this->columnsOnly
;
1139 $formData['renderType'] = 'outerWrapContainer';
1140 $formResult = $nodeFactory->create($formData)->render();
1142 $html = $formResult['html'];
1144 $formResult['html'] = '';
1145 $formResult['doSaveFieldName'] = 'doSave';
1147 // @todo: Put all the stuff into FormEngine as final "compiler" class
1148 // @todo: This is done here for now to not rewrite JStop()
1149 // @todo: and printNeededJSFunctions() now
1150 $this->formResultCompiler
->mergeResult($formResult);
1152 // Seems the pid is set as hidden field (again) at end?!
1153 if ($command == 'new') {
1154 // @todo: looks ugly
1156 . '<input type="hidden"'
1157 . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1158 . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1163 } catch (AccessDeniedException
$e) {
1165 // Try to fetch error message from "recordInternals" be user object
1166 // @todo: This construct should be logged and localized and de-uglified
1167 $message = $beUser->errorMsg
;
1168 if (empty($message)) {
1169 // Create message from exception.
1170 $message = $e->getMessage() . ' ' . $e->getCode();
1172 $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', true)
1173 . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1175 } // End of for each uid
1184 * Create the panel of buttons for submitting the form or otherwise perform operations.
1186 * @return array All available buttons as an assoc. array
1188 protected function getButtons()
1190 $lang = $this->getLanguageService();
1191 // Render SAVE type buttons:
1192 // The action of each button is decided by its name attribute. (See doProcessData())
1193 $buttonBar = $this->moduleTemplate
->getDocHeaderComponent()->getButtonBar();
1194 if (!$this->errorC
&& !$GLOBALS['TCA'][$this->firstEl
['table']]['ctrl']['readOnly']) {
1195 $saveSplitButton = $buttonBar->makeSplitButton();
1197 $saveButton = $buttonBar->makeInputButton()
1198 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc'))
1199 ->setName('_savedok')
1201 ->setForm('EditDocumentController')
1202 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon('actions-document-save', Icon
::SIZE_SMALL
));
1203 $saveSplitButton->addItem($saveButton, true);
1205 // SAVE / VIEW button:
1206 if ($this->viewId
&& !$this->noView
&& $this->getNewIconMode($this->firstEl
['table'], 'saveDocView')) {
1207 $pagesTSconfig = BackendUtility
::getPagesTSconfig($this->pageinfo
['uid']);
1208 if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1209 $excludeDokTypes = GeneralUtility
::intExplode(
1211 $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1215 // exclude sysfolders, spacers and recycler by default
1216 $excludeDokTypes = array(
1217 PageRepository
::DOKTYPE_RECYCLER
,
1218 PageRepository
::DOKTYPE_SYSFOLDER
,
1219 PageRepository
::DOKTYPE_SPACER
1222 if (!in_array((int)$this->pageinfo
['doktype'], $excludeDokTypes, true)
1223 ||
isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl
['table'] . '.']['previewPageId'])
1225 $saveAndOpenButton = $buttonBar->makeInputButton()
1226 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDocShow'))
1227 ->setName('_savedokview')
1229 ->setForm('EditDocumentController')
1230 ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1231 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1232 'actions-document-save-view',
1235 $saveSplitButton->addItem($saveAndOpenButton);
1238 // SAVE / NEW button:
1239 if (count($this->elementsData
) === 1 && $this->getNewIconMode($this->firstEl
['table'])) {
1240 $saveAndNewButton = $buttonBar->makeInputButton()
1241 ->setName('_savedoknew')
1242 ->setClasses('t3js-editform-submitButton')
1244 ->setForm('EditDocumentController')
1245 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveNewDoc'))
1246 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1247 'actions-document-save-new',
1250 $saveSplitButton->addItem($saveAndNewButton);
1253 $saveAndCloseButton = $buttonBar->makeInputButton()
1254 ->setName('_saveandclosedok')
1255 ->setClasses('t3js-editform-submitButton')
1257 ->setForm('EditDocumentController')
1258 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc'))
1259 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1260 'actions-document-save-close',
1263 $saveSplitButton->addItem($saveAndCloseButton);
1264 // FINISH TRANSLATION / SAVE / CLOSE
1265 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1266 $saveTranslationButton = $buttonBar->makeInputButton()
1267 ->setName('_translation_savedok')
1269 ->setForm('EditDocumentController')
1270 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDoc'))
1271 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1272 'actions-document-save-cleartranslationcache',
1275 $saveSplitButton->addItem($saveTranslationButton);
1276 $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1277 ->setName('_translation_savedokclear')
1279 ->setForm('EditDocumentController')
1280 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDocClear'))
1281 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1282 'actions-document-save-cleartranslationcache',
1285 $saveSplitButton->addItem($saveAndClearTranslationButton);
1287 $buttonBar->addButton($saveSplitButton, ButtonBar
::BUTTON_POSITION_LEFT
, 2);
1290 $closeButton = $buttonBar->makeLinkButton()
1292 ->setClasses('t3js-editform-close')
1293 ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc'))
1294 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1295 'actions-document-close',
1298 $buttonBar->addButton($closeButton);
1299 // DELETE + UNDO buttons:
1301 && !$GLOBALS['TCA'][$this->firstEl
['table']]['ctrl']['readOnly']
1302 && count($this->elementsData
) === 1
1304 if ($this->firstEl
['cmd'] !== 'new' && MathUtility
::canBeInterpretedAsInteger($this->firstEl
['uid'])) {
1306 if ($this->firstEl
['deleteAccess']
1307 && !$GLOBALS['TCA'][$this->firstEl
['table']]['ctrl']['readOnly']
1308 && !$this->getNewIconMode($this->firstEl
['table'], 'disableDelete')
1310 $returnUrl = $this->retUrl
;
1311 if ($this->firstEl
['table'] === 'pages') {
1312 parse_str((string)parse_url($returnUrl, PHP_URL_QUERY
), $queryParams);
1313 if (isset($queryParams['M'])
1314 && isset($queryParams['id'])
1315 && (string)$this->firstEl
['uid'] === (string)$queryParams['id']
1317 // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1318 // tree from the outside to be able to mark the pid as active
1319 $returnUrl = BackendUtility
::getModuleUrl($queryParams['M'], ['id' => 0]);
1322 $deleteButton = $buttonBar->makeLinkButton()
1324 ->setClasses('t3js-editform-delete-record')
1325 ->setTitle($lang->getLL('deleteItem'))
1326 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1327 'actions-edit-delete',
1330 ->setDataAttributes([
1331 'return-url' => $returnUrl,
1332 'uid' => $this->firstEl
['uid'],
1333 'table' => $this->firstEl
['table']
1335 $buttonBar->addButton($deleteButton, ButtonBar
::BUTTON_POSITION_LEFT
, 3);
1338 $undoRes = $this->getDatabaseConnection()->exec_SELECTquery(
1342 . $this->getDatabaseConnection()->fullQuoteStr($this->firstEl
['table'], 'sys_history')
1344 . (int)$this->firstEl
['uid'],
1349 if ($undoButtonR = $this->getDatabaseConnection()->sql_fetch_assoc($undoRes)) {
1350 $aOnClick = 'window.location.href=' .
1351 GeneralUtility
::quoteJSvalue(
1352 BackendUtility
::getModuleUrl(
1355 'element' => $this->firstEl
['table'] . ':' . $this->firstEl
['uid'],
1356 'revert' => 'ALL_FIELDS',
1358 'returnUrl' => $this->R_URI
,
1361 ) . '; return false;';
1363 $undoButton = $buttonBar->makeLinkButton()
1365 ->setOnClick($aOnClick)
1368 $lang->getLL('undoLastChange'),
1369 BackendUtility
::calcAge(
1370 ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1371 $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')
1375 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1376 'actions-edit-undo',
1379 $buttonBar->addButton($undoButton, ButtonBar
::BUTTON_POSITION_LEFT
, 3);
1381 if ($this->getNewIconMode($this->firstEl
['table'], 'showHistory')) {
1382 $aOnClick = 'window.location.href=' .
1383 GeneralUtility
::quoteJSvalue(
1384 BackendUtility
::getModuleUrl(
1387 'element' => $this->firstEl
['table'] . ':' . $this->firstEl
['uid'],
1388 'returnUrl' => $this->R_URI
,
1391 ) . '; return false;';
1393 $historyButton = $buttonBar->makeLinkButton()
1395 ->setOnClick($aOnClick)
1396 ->setTitle('Open history of this record')
1397 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1398 'actions-document-history-open',
1401 $buttonBar->addButton($historyButton, ButtonBar
::BUTTON_POSITION_LEFT
, 3);
1403 // If only SOME fields are shown in the form, this will link the user to the FULL form:
1404 if ($this->columnsOnly
) {
1405 $columnsOnlyButton = $buttonBar->makeLinkButton()
1406 ->setHref($this->R_URI
. '&columnsOnly=')
1407 ->setTitle($lang->getLL('editWholeRecord'))
1408 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon(
1409 'actions-document-open',
1412 $buttonBar->addButton($columnsOnlyButton, ButtonBar
::BUTTON_POSITION_LEFT
, 3);
1416 $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1417 $buttonBar->addButton($cshButton);
1418 $this->shortCutLink();
1419 $this->openInNewWindowLink();
1423 * Put together the various elements (buttons, selectors, form) into a table
1425 * @param string $editForm HTML form.
1426 * @return string Composite HTML
1428 public function compileForm($editForm)
1431 <!-- EDITING FORM -->
1433 action="' . htmlspecialchars($this->R_URI
) . '"
1435 enctype="multipart/form-data"
1437 id="EditDocumentController"
1438 onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1441 <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl
) . '" />
1442 <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl
) . '" />';
1443 if ($this->returnNewPageId
) {
1444 $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1446 $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId
) . '" />';
1447 if ($this->viewId_addParams
) {
1448 $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams
) . '" />';
1451 <input type="hidden" name="closeDoc" value="0" />
1452 <input type="hidden" name="doSave" value="0" />
1453 <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1454 <input type="hidden" name="_scrollPosition" value="" />';
1455 return $formContent;
1459 * Create shortcut icon
1461 public function shortCutLink()
1463 if ($this->returnUrl
!== ExtensionManagementUtility
::extRelPath('backend') . 'Resources/Private/Templates/Close.html') {
1464 $shortCutButton = $this->moduleTemplate
->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1465 $shortCutButton->setModuleName($this->MCONF
['name'])
1474 $this->moduleTemplate
->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1479 * Creates open-in-window link
1481 public function openInNewWindowLink()
1483 $backendRelPath = ExtensionManagementUtility
::extRelPath('backend');
1484 if ($this->returnUrl
!== $backendRelPath . 'Resources/Private/Templates/Close.html') {
1485 $aOnClick = 'vHWin=window.open(' . GeneralUtility
::quoteJSvalue(GeneralUtility
::linkThisScript(
1486 array('returnUrl' => $backendRelPath . 'Resources/Private/Templates/Close.html')
1489 . GeneralUtility
::quoteJSvalue(md5($this->R_URI
))
1490 . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1491 $openInNewWindowButton = $this->moduleTemplate
->getDocHeaderComponent()->getButtonBar()
1494 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow'))
1495 ->setIcon($this->moduleTemplate
->getIconFactory()->getIcon('actions-window-open', Icon
::SIZE_SMALL
))
1496 ->setOnClick($aOnClick);
1497 $this->moduleTemplate
->getDocHeaderComponent()->getButtonBar()->addButton(
1498 $openInNewWindowButton,
1499 ButtonBar
::BUTTON_POSITION_RIGHT
1504 /***************************
1506 * Localization stuff
1508 ***************************/
1510 * Make selector box for creating new translation for a record or switching to edit the record in an existing
1512 * Displays only languages which are available for the current page.
1514 * @param string $table Table name
1515 * @param int $uid Uid for which to create a new language
1516 * @param int $pid Pid of the record
1518 public function languageSwitch($table, $uid, $pid = null)
1520 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1521 $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1522 // Table editable and activated for languages?
1523 if ($this->getBackendUser()->check('tables_modify', $table)
1525 && $transOrigPointerField && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
1527 if (is_null($pid)) {
1528 $row = BackendUtility
::getRecord($table, $uid, 'pid');
1531 // Get all available languages for the page
1532 $langRows = $this->getLanguages($pid);
1533 // Page available in other languages than default language?
1534 if (is_array($langRows) && count($langRows) > 1) {
1535 $rowsByLang = array();
1536 $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1537 // Get record in current language
1538 $rowCurrent = BackendUtility
::getLiveVersionOfRecord($table, $uid, $fetchFields);
1539 if (!is_array($rowCurrent)) {
1540 $rowCurrent = BackendUtility
::getRecord($table, $uid, $fetchFields);
1542 $currentLanguage = (int)$rowCurrent[$languageField];
1543 // Disabled for records with [all] language!
1544 if ($currentLanguage > -1) {
1545 // Get record in default language if needed
1546 if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1547 $rowsByLang[0] = BackendUtility
::getLiveVersionOfRecord(
1549 $rowCurrent[$transOrigPointerField],
1552 if (!is_array($rowsByLang[0])) {
1553 $rowsByLang[0] = BackendUtility
::getRecord(
1555 $rowCurrent[$transOrigPointerField],
1560 $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1562 if ($rowCurrent[$transOrigPointerField] ||
$currentLanguage === 0) {
1563 // Get record in other languages to see what's already available
1564 $translations = $this->getDatabaseConnection()->exec_SELECTgetRows(
1567 'pid=' . (int)$pid . ' AND ' . $languageField . '>0' . ' AND ' . $transOrigPointerField . '=' . (int)$rowsByLang[0]['uid'] . BackendUtility
::deleteClause($table) . BackendUtility
::versioningPlaceholderClause($table)
1569 foreach ($translations as $row) {
1570 $rowsByLang[$row[$languageField]] = $row;
1573 $languageMenu = $this->moduleTemplate
->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1574 $languageMenu->setIdentifier('_langSelector');
1575 $languageMenu->setLabel($this->getLanguageService()->sL(
1576 'LLL:EXT:lang/locallang_general.xlf:LGL.language',
1579 foreach ($langRows as $lang) {
1580 if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1581 $newTranslation = isset($rowsByLang[$lang['uid']]) ?
'' : ' [' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.new', true) . ']';
1582 // Create url for creating a localized record
1583 if ($newTranslation) {
1584 $redirectUrl = BackendUtility
::getModuleUrl('record_edit', array(
1585 'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1586 'returnUrl' => $this->retUrl
1588 $href = BackendUtility
::getLinkToDataHandlerAction(
1589 '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1593 $href = BackendUtility
::getModuleUrl('record_edit', array(
1594 'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1595 'returnUrl' => $this->retUrl
1598 $menuItem = $languageMenu->makeMenuItem()
1599 ->setTitle($lang['title'] . $newTranslation)
1601 if ((int)$lang['uid'] === $currentLanguage) {
1602 $menuItem->setActive(true);
1604 $languageMenu->addMenuItem($menuItem);
1607 $this->moduleTemplate
->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1614 * Redirects to FormEngine with new parameters to edit a just created localized record
1616 * @param string $justLocalized String passed by GET &justLocalized=
1619 public function localizationRedirect($justLocalized)
1621 list($table, $orig_uid, $language) = explode(':', $justLocalized);
1622 if ($GLOBALS['TCA'][$table]
1623 && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1624 && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1626 $localizedRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
1629 $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$language . ' AND '
1630 . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$orig_uid
1631 . BackendUtility
::deleteClause($table) . BackendUtility
::versioningPlaceholderClause($table)
1633 if (is_array($localizedRecord)) {
1634 // Create parameters and finally run the classic page module for creating a new page translation
1635 $location = BackendUtility
::getModuleUrl('record_edit', array(
1636 'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1637 'returnUrl' => GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('returnUrl'))
1639 HttpUtility
::redirect($location);
1645 * Returns sys_language records available for record translations on given page.
1647 * @param int $id Page id: If zero, the query will select all sys_language records from root level which are NOT
1648 * hidden. If set to another value, the query will select all sys_language records that has a
1649 * pages_language_overlay record on that page (and is not hidden, unless you are admin user)
1650 * @return array Language records including faked record for default language
1652 public function getLanguages($id)
1654 $modSharedTSconfig = BackendUtility
::getModTSconfig($id, 'mod.SHARED');
1655 // Fallback non sprite-configuration
1656 if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1657 $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1660 $modSharedTSconfig['properties']['defaultLanguageFlag']
1668 'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1669 ?
$modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1670 : $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage'),
1671 'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1674 $exQ = $this->getBackendUser()->isAdmin() ?
'' : ' AND sys_language.hidden=0';
1676 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1678 'pages_language_overlay,sys_language',
1679 'pages_language_overlay.sys_language_uid=sys_language.uid
1680 AND pages_language_overlay.pid=' . (int)$id . BackendUtility
::deleteClause('pages_language_overlay')
1682 'pages_language_overlay.sys_language_uid,
1685 sys_language.tstamp,
1686 sys_language.hidden,
1688 sys_language.language_isocode,
1689 sys_language.static_lang_isocode,
1691 'sys_language.title'
1694 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1697 'sys_language.hidden=0',
1699 'sys_language.title'
1703 foreach ($rows as $row) {
1704 $languages[$row['uid']] = $row;
1710 /***************************
1714 ***************************/
1716 * Fix $this->editconf if versioning applies to any of the records
1718 * @param array|bool $mapArray Mapping between old and new ids if auto-versioning has been performed.
1721 public function fixWSversioningInEditConf($mapArray = false)
1723 // Traverse the editConf array
1724 if (is_array($this->editconf
)) {
1726 foreach ($this->editconf
as $table => $conf) {
1727 if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1728 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1730 foreach ($conf as $cKey => $cmd) {
1731 if ($cmd == 'edit') {
1732 // Traverse the ids:
1733 $ids = GeneralUtility
::trimExplode(',', $cKey, true);
1734 foreach ($ids as $idKey => $theUid) {
1735 if (is_array($mapArray)) {
1736 if ($mapArray[$table][$theUid]) {
1737 $ids[$idKey] = $mapArray[$table][$theUid];
1740 // Default, look for versions in workspace for record:
1741 $calcPRec = $this->getRecordForEdit($table, $theUid);
1742 if (is_array($calcPRec)) {
1743 // Setting UID again if it had changed, eg. due to workspace versioning.
1744 $ids[$idKey] = $calcPRec['uid'];
1748 // Add the possibly manipulated IDs to the new-build newConf array:
1749 $newConf[implode(',', $ids)] = $cmd;
1751 $newConf[$cKey] = $cmd;
1754 // Store the new conf array:
1755 $this->editconf
[$table] = $newConf;
1762 * Get record for editing.
1764 * @param string $table Table name
1765 * @param int $theUid Record UID
1766 * @return array Returns record to edit, FALSE if none
1768 public function getRecordForEdit($table, $theUid)
1770 // Fetch requested record:
1771 $reqRecord = BackendUtility
::getRecord($table, $theUid, 'uid,pid');
1772 if (is_array($reqRecord)) {
1773 // If workspace is OFFLINE:
1774 if ($this->getBackendUser()->workspace
!= 0) {
1775 // Check for versioning support of the table:
1776 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1777 // If the record is already a version of "something" pass it by.
1778 if ($reqRecord['pid'] == -1) {
1779 // (If it turns out not to be a version of the current workspace there will be trouble, but
1780 // that is handled inside TCEmain then and in the interface it would clearly be an error of
1781 // links if the user accesses such a scenario)
1784 // The input record was online and an offline version must be found or made:
1785 // Look for version of this workspace:
1786 $versionRec = BackendUtility
::getWorkspaceVersionOfRecord(
1787 $this->getBackendUser()->workspace
,
1792 return is_array($versionRec) ?
$versionRec : $reqRecord;
1795 // This means that editing cannot occur on this record because it was not supporting versioning
1796 // which is required inside an offline workspace.
1800 // In ONLINE workspace, just return the originally requested record:
1804 // Return FALSE because the table/uid was not found anyway.
1810 * Populates the variables $this->storeArray, $this->storeUrl, $this->storeUrlMd5
1815 public function compileStoreDat()
1817 $this->storeArray
= GeneralUtility
::compileSelectedGetVarsFromArray(
1818 'edit,defVals,overrideVals,columnsOnly,noView,workspace',
1819 $this->R_URL_getvars
1821 $this->storeUrl
= GeneralUtility
::implodeArrayForUrl('', $this->storeArray
);
1822 $this->storeUrlMd5
= md5($this->storeUrl
);
1826 * Function used to look for configuration of buttons in the form: Fx. disabling buttons or showing them at various
1829 * @param string $table The table for which the configuration may be specific
1830 * @param string $key The option for look for. Default is checking if the saveDocNew button should be displayed.
1831 * @return string Return value fetched from USER TSconfig
1833 public function getNewIconMode($table, $key = 'saveDocNew')
1835 $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1836 $output = trim(isset($TSconfig['properties'][$table]) ?
$TSconfig['properties'][$table] : $TSconfig['value']);
1841 * Handling the closing of a document
1843 * @param int $code Close code: 0/1 will redirect to $this->retUrl, 3 will clear the docHandler (thus closing all
1844 * documents) and other values will call setDocument with ->retUrl
1847 public function closeDocument($code = 0)
1849 // If current document is found in docHandler,
1850 // then unset it, possibly unset it ALL and finally, write it to the session data
1851 if (isset($this->docHandler
[$this->storeUrlMd5
])) {
1852 // add the closing document to the recent documents
1853 $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1854 if (!is_array($recentDocs)) {
1855 $recentDocs = array();
1857 $closedDoc = $this->docHandler
[$this->storeUrlMd5
];
1858 $recentDocs = array_merge(array($this->storeUrlMd5
=> $closedDoc), $recentDocs);
1859 if (count($recentDocs) > 8) {
1860 $recentDocs = array_slice($recentDocs, 0, 8);
1862 // remove it from the list of the open documents
1863 unset($this->docHandler
[$this->storeUrlMd5
]);
1865 $recentDocs = array_merge($this->docHandler
, $recentDocs);
1866 $this->docHandler
= array();
1868 $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1869 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler
, $this->docDat
[1]));
1870 BackendUtility
::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler
));
1872 // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
1873 // other scripts, like wizard_add, to know which records was created or so...)
1874 if ($this->returnEditConf
&& $this->retUrl
!= BackendUtility
::getModuleUrl('dummy')) {
1875 $this->retUrl
.= '&returnEditConf=' . rawurlencode(json_encode($this->editconf
));
1877 // If code is NOT set OR set to 1, then make a header location redirect to $this->retUrl
1878 if (!$code ||
$code == 1) {
1879 HttpUtility
::redirect($this->retUrl
);
1881 $this->setDocument('', $this->retUrl
);
1886 * Redirects to the document pointed to by $currentDocFromHandlerMD5 OR $retUrl (depending on some internal
1888 * Most likely you will get a header-location redirect from this function.
1890 * @param string $currentDocFromHandlerMD5 Pointer to the document in the docHandler array
1891 * @param string $retUrl Alternative/Default retUrl
1894 public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
1896 if ($retUrl === '') {
1899 if (!$this->modTSconfig
['properties']['disableDocSelector']
1900 && is_array($this->docHandler
)
1901 && !empty($this->docHandler
)
1903 if (isset($this->docHandler
[$currentDocFromHandlerMD5])) {
1904 $setupArr = $this->docHandler
[$currentDocFromHandlerMD5];
1906 $setupArr = reset($this->docHandler
);
1909 $sParts = parse_url(GeneralUtility
::getIndpEnv('REQUEST_URI'));
1910 $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1913 HttpUtility
::redirect($retUrl);
1917 * Injects the request object for the current request or subrequest
1919 * @param ServerRequestInterface $request the current request
1920 * @param ResponseInterface $response
1921 * @return ResponseInterface the response with the content
1923 public function mainAction(ServerRequestInterface
$request, ResponseInterface
$response)
1925 BackendUtility
::lockRecords();
1927 // Preprocessing, storing data if submitted to
1930 // Checks, if a save button has been clicked (or the doSave variable is sent)
1931 if ($this->doProcessData()) {
1932 $this->processData();
1938 $response->getBody()->write($this->moduleTemplate
->renderContent());
1943 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1945 protected function getBackendUser()
1947 return $GLOBALS['BE_USER'];
1951 * Returns LanguageService
1953 * @return \TYPO3\CMS\Lang\LanguageService
1955 protected function getLanguageService()
1957 return $GLOBALS['LANG'];
1961 * Returns the database connection
1963 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
1965 protected function getDatabaseConnection()
1967 return $GLOBALS['TYPO3_DB'];