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 TYPO3\CMS\Backend\Form\DataPreprocessor
;
18 use TYPO3\CMS\Backend\Form\FormEngine
;
19 use TYPO3\CMS\Backend\Form\Utility\FormEngineUtility
;
20 use TYPO3\CMS\Backend\Utility\BackendUtility
;
21 use TYPO3\CMS\Backend\Utility\IconUtility
;
22 use TYPO3\CMS\Core\DataHandling\DataHandler
;
23 use TYPO3\CMS\Core\Html\HtmlParser
;
24 use TYPO3\CMS\Core\Messaging\FlashMessage
;
25 use TYPO3\CMS\Core\Messaging\FlashMessageService
;
26 use TYPO3\CMS\Core\Page\PageRenderer
;
27 use TYPO3\CMS\Core\Type\Bitmask\JsConfirmation
;
28 use TYPO3\CMS\Core\Type\Bitmask\Permission
;
29 use TYPO3\CMS\Core\Utility\GeneralUtility
;
30 use TYPO3\CMS\Core\Utility\HttpUtility
;
31 use TYPO3\CMS\Core\Utility\MathUtility
;
32 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher
;
33 use TYPO3\CMS\Frontend\Page\PageRepository
;
36 * Script Class: Drawing the editing form for editing records in TYPO3.
37 * Notice: It does NOT use tce_db.php to submit data to, rather it handles submissions itself
39 class EditDocumentController
{
42 * GPvar "edit": Is an array looking approx like [tablename][list-of-ids]=command, eg.
43 * "&edit[pages][123]=edit". See \TYPO3\CMS\Backend\Utility\BackendUtility::editOnClick(). Value can be seen modified
44 * internally (converting NEW keyword to id, workspace/versioning etc).
51 * Commalist of fieldnames to edit. The point is IF you specify this list, only those
52 * fields will be rendered in the form. Otherwise all (available) fields in the record
53 * is shown according to the types configuration in $GLOBALS['TCA']
60 * Default values for fields (array with tablenames, fields etc. as keys).
61 * Can be seen modified internally.
68 * Array of values to force being set (as hidden fields). Will be set as $this->defVals
69 * IF defVals does not exist.
76 * If set, this value will be set in $this->retUrl (which is used quite many places
77 * as the return URL). If not set, "dummy.php" will be set in $this->retUrl
84 * Close-document command. Not really sure of all options...
91 * Quite simply, if this variable is set, then the processing of incoming data will be performed
92 * as if a save-button is pressed. Used in the forms as a hidden field which can be set through
93 * JavaScript if the form is somehow submitted by JavaScript).
100 * The data array from which the data comes...
124 * Redirect (not used???)
131 * Boolean: If set, then the GET var "&id=" will be added to the
132 * retUrl string so that the NEW id of something is returned to the script calling the form.
136 public $returnNewPageId;
151 * ID for displaying the page in the frontend (used for SAVE/VIEW operations)
158 * Additional GET vars for the link, eg. "&L=xxx"
162 public $popViewId_addParams;
165 * Alternative URL for viewing the frontend pages.
172 * If this is pointing to a page id it will automatically load all content elements
173 * (NORMAL column/default language) from that page into the form!
176 * @deprecated since TYPO3 CMS 7, will be removed with TYPO3 CMS 8
178 public $editRegularContentFromId;
181 * Alternative title for the document handler.
188 * If set, then no SAVE/VIEW button is printed
197 public $perms_clause;
200 * If set, the $this->editconf array is returned to the calling script
201 * (used by wizard_add.php for instance)
205 public $returnEditConf;
208 * localization mode for TCEforms (eg. "text")
212 public $localizationMode;
215 * Workspace used for the editing action.
219 protected $workspace;
222 * document template object
224 * @var \TYPO3\CMS\Backend\Template\DocumentTemplate
229 * a static HTML template, usually in templates/alt_doc.html
236 * Content accumulation
243 * Return URL script, processed. This contains the script (if any) that we should
244 * RETURN TO from the FormEngine script IF we press the close button. Thus this
245 * variable is normally passed along from the calling script so we can properly return if needed.
252 * Contains the parts of the REQUEST_URI (current url). By parts we mean the result of resolving
253 * REQUEST_URI (current url) by the parse_url() function. The result is an array where eg. "path"
254 * is the script path and "query" is the parameters...
261 * Contains the current GET vars array; More specifically this array is the foundation for creating
262 * the R_URI internal var (which becomes the "url of this script" to which we submit the forms etc.)
266 public $R_URL_getvars;
269 * Set to the URL of this script including variables which is needed to re-display the form. See main()
286 * Is loaded with the "title" of the currently "open document" - this is used in the
287 * Document Selector box. (see makeDocSel())
294 * Contains an array with key/value pairs of GET parameters needed to reach the
295 * current document displayed - used in the Document Selector box. (see compileStoreDat())
302 * Contains storeArray, but imploded into a GET parameter string (see compileStoreDat())
309 * Hashed value of storeURL (see compileStoreDat())
316 * Module session data
323 * An array of the "open documents" - keys are md5 hashes (see $storeUrlMd5) identifying
324 * the various documents on the GET parameter list needed to open it. The values are
325 * arrays with 0,1,2 keys with information about the document (see compileStoreDat()).
326 * The docHandler variable is stored in the $docDat session data, key "0".
333 * Array of the elements to create edit forms for.
337 public $elementsData;
340 * Pointer to the first element in $elementsData
347 * Counter, used to count the number of errors (when users do not have edit permissions)
354 * Counter, used to count the number of new record forms displayed
361 * Is set to the pid value of the last shown record - thus indicating which page to
362 * show when clicking the SAVE/VIEW button
369 * Is set to additional parameters (like "&L=xxx") if the record supports it.
373 public $viewId_addParams;
376 * Module TSconfig, loaded from main() based on the page id value of viewId
383 * instance of TCEforms class
385 * @var \TYPO3\CMS\Backend\Form\FormEngine
390 * Contains the root-line path of the currently edited record(s) - for display.
394 public $generalPathOfForm;
397 * Used internally to disable the storage of the document reference (eg. new records)
401 public $dontStoreDocumentRef;
404 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
406 protected $signalSlotDispatcher;
409 * Stores information needed to preview the currently saved record
413 protected $previewData = [];
418 public function __construct() {
419 $GLOBALS['SOBE'] = $this;
420 $this->getLanguageService()->includeLLFile('EXT:lang/locallang_alt_doc.xlf');
424 * Get the SignalSlot dispatcher
426 * @return \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
428 protected function getSignalSlotDispatcher() {
429 if (!isset($this->signalSlotDispatcher
)) {
430 $this->signalSlotDispatcher
= GeneralUtility
::makeInstance(Dispatcher
::class);
432 return $this->signalSlotDispatcher
;
436 * Emits a signal after a function was executed
438 * @param string $signalName
440 protected function emitFunctionAfterSignal($signalName) {
441 $this->getSignalSlotDispatcher()->dispatch(__CLASS__
, $signalName . 'After', array($this));
445 * First initialization.
449 public function preInit() {
450 if (GeneralUtility
::_GP('justLocalized')) {
451 $this->localizationRedirect(GeneralUtility
::_GP('justLocalized'));
454 $this->editconf
= GeneralUtility
::_GP('edit');
455 $this->defVals
= GeneralUtility
::_GP('defVals');
456 $this->overrideVals
= GeneralUtility
::_GP('overrideVals');
457 $this->columnsOnly
= GeneralUtility
::_GP('columnsOnly');
458 $this->returnUrl
= GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('returnUrl'));
459 $this->closeDoc
= GeneralUtility
::_GP('closeDoc');
460 $this->doSave
= GeneralUtility
::_GP('doSave');
461 $this->returnEditConf
= GeneralUtility
::_GP('returnEditConf');
462 $this->localizationMode
= GeneralUtility
::_GP('localizationMode');
463 $this->workspace
= GeneralUtility
::_GP('workspace');
464 $this->uc
= GeneralUtility
::_GP('uc');
465 // Setting override values as default if defVals does not exist.
466 if (!is_array($this->defVals
) && is_array($this->overrideVals
)) {
467 $this->defVals
= $this->overrideVals
;
469 // Setting return URL
470 $this->retUrl
= $this->returnUrl ?
: BackendUtility
::getModuleUrl('dummy');
471 // Fix $this->editconf if versioning applies to any of the records
472 $this->fixWSversioningInEditConf();
473 // Make R_URL (request url) based on input GETvars:
474 $this->R_URL_parts
= parse_url(GeneralUtility
::getIndpEnv('REQUEST_URI'));
475 $this->R_URL_getvars
= GeneralUtility
::_GET();
476 $this->R_URL_getvars
['edit'] = $this->editconf
;
477 // MAKE url for storing
478 $this->compileStoreDat();
479 // Initialize more variables.
480 $this->dontStoreDocumentRef
= 0;
481 $this->storeTitle
= '';
482 // Get session data for the module:
483 $this->docDat
= $this->getBackendUser()->getModuleData('FormEngine', 'ses');
484 $this->docHandler
= $this->docDat
[0];
485 // If a request for closing the document has been sent, act accordingly:
486 if ($this->closeDoc
> 0) {
487 $this->closeDocument($this->closeDoc
);
489 // If NO vars are sent to the script, try to read first document:
490 // Added !is_array($this->editconf) because editConf must not be set either.
491 // Anyways I can't figure out when this situation here will apply...
492 if (is_array($this->R_URL_getvars
) && count($this->R_URL_getvars
) < 2 && !is_array($this->editconf
)) {
493 $this->setDocument($this->docDat
[1]);
496 // Sets a temporary workspace, this request is based on
497 if ($this->workspace
!== NULL
) {
498 $this->getBackendUser()->setTemporaryWorkspace($this->workspace
);
501 $this->emitFunctionAfterSignal(__FUNCTION__
);
505 * Detects, if a save command has been triggered.
507 * @return bool TRUE, then save the document (data submitted)
509 public function doProcessData() {
510 $out = $this->doSave ||
isset($_POST['_savedok_x']) ||
isset($_POST['_saveandclosedok_x']) ||
isset($_POST['_savedokview_x']) ||
isset($_POST['_savedoknew_x']) ||
isset($_POST['_translation_savedok_x']) ||
isset($_POST['_translation_savedokclear_x']);
515 * Do processing of data, submitting it to TCEmain.
519 public function processData() {
520 $beUser = $this->getBackendUser();
521 // GPvars specifically for processing:
522 $control = GeneralUtility
::_GP('control');
523 $this->data
= GeneralUtility
::_GP('data');
524 $this->cmd
= GeneralUtility
::_GP('cmd');
525 $this->mirror
= GeneralUtility
::_GP('mirror');
526 $this->cacheCmd
= GeneralUtility
::_GP('cacheCmd');
527 $this->redirect
= GeneralUtility
::_GP('redirect');
528 $this->returnNewPageId
= GeneralUtility
::_GP('returnNewPageId');
529 $this->vC
= GeneralUtility
::_GP('vC');
530 // See tce_db.php for relevate options here:
531 // Only options related to $this->data submission are included here.
532 /** @var $tce \TYPO3\CMS\Core\DataHandling\DataHandler */
533 $tce = GeneralUtility
::makeInstance(DataHandler
::class);
534 $tce->stripslashes_values
= FALSE
;
536 if (!empty($control)) {
537 $tce->setControl($control);
539 if (isset($_POST['_translation_savedok_x'])) {
540 $tce->updateModeL10NdiffData
= 'FORCE_FFUPD';
542 if (isset($_POST['_translation_savedokclear_x'])) {
543 $tce->updateModeL10NdiffData
= 'FORCE_FFUPD';
544 $tce->updateModeL10NdiffDataClear
= TRUE
;
546 // Setting default values specific for the user:
547 $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults');
548 if (is_array($TCAdefaultOverride)) {
549 $tce->setDefaultsFromUserTS($TCAdefaultOverride);
551 // Setting internal vars:
552 if ($beUser->uc
['neverHideAtCopy']) {
553 $tce->neverHideAtCopy
= 1;
555 // Loading TCEmain with data:
556 $tce->start($this->data
, $this->cmd
);
557 if (is_array($this->mirror
)) {
558 $tce->setMirror($this->mirror
);
560 // Checking referer / executing
561 $refInfo = parse_url(GeneralUtility
::getIndpEnv('HTTP_REFERER'));
562 $httpHost = GeneralUtility
::getIndpEnv('TYPO3_HOST_ONLY');
563 if ($httpHost != $refInfo['host'] && $this->vC
!= $beUser->veriCode() && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']) {
564 $tce->log('', 0, 0, 0, 1, 'Referer host \'%s\' and server host \'%s\' did not match and veriCode was not valid either!', 1, array($refInfo['host'], $httpHost));
565 debug('Error: Referer host did not match with server host.');
567 // Perform the saving operation with TCEmain:
568 $tce->process_uploads($_FILES);
569 $tce->process_datamap();
570 $tce->process_cmdmap();
571 // If pages are being edited, we set an instruction about updating the page tree after this operation.
572 if ($tce->pagetreeNeedsRefresh
&& (isset($this->data
['pages']) ||
$beUser->workspace
!= 0 && !empty($this->data
))) {
573 BackendUtility
::setUpdateSignal('updatePageTree');
575 // If there was saved any new items, load them:
576 if (!empty($tce->substNEWwithIDs_table
)) {
577 // save the expanded/collapsed states for new inline records, if any
578 FormEngineUtility
::updateInlineView($this->uc
, $tce);
579 $newEditConf = array();
580 foreach ($this->editconf
as $tableName => $tableCmds) {
581 $keys = array_keys($tce->substNEWwithIDs_table
, $tableName);
583 foreach ($keys as $key) {
584 $editId = $tce->substNEWwithIDs
[$key];
585 // Check if the $editId isn't a child record of an IRRE action
586 if (!(is_array($tce->newRelatedIDs
[$tableName]) && in_array($editId, $tce->newRelatedIDs
[$tableName]))) {
587 // Translate new id to the workspace version:
588 if ($versionRec = BackendUtility
::getWorkspaceVersionOfRecord($beUser->workspace
, $tableName, $editId, 'uid')) {
589 $editId = $versionRec['uid'];
591 $newEditConf[$tableName][$editId] = 'edit';
593 // Traverse all new records and forge the content of ->editconf so we can continue to EDIT these records!
594 if ($tableName == 'pages' && $this->retUrl
!= BackendUtility
::getModuleUrl('dummy') && $this->returnNewPageId
) {
595 $this->retUrl
.= '&id=' . $tce->substNEWwithIDs
[$key];
599 $newEditConf[$tableName] = $tableCmds;
602 // Resetting editconf if newEditConf has values:
603 if (!empty($newEditConf)) {
604 $this->editconf
= $newEditConf;
606 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
607 $this->R_URL_getvars
['edit'] = $this->editconf
;
608 // Unsetting default values since we don't need them anymore.
609 unset($this->R_URL_getvars
['defVals']);
610 // Re-compile the store* values since editconf changed...
611 $this->compileStoreDat();
613 // See if any records was auto-created as new versions?
614 if (!empty($tce->autoVersionIdMap
)) {
615 $this->fixWSversioningInEditConf($tce->autoVersionIdMap
);
617 // If a document is saved and a new one is created right after.
618 if (isset($_POST['_savedoknew_x']) && is_array($this->editconf
)) {
619 // Finding the current table:
620 reset($this->editconf
);
621 $nTable = key($this->editconf
);
622 // Finding the first id, getting the records pid+uid
623 reset($this->editconf
[$nTable]);
624 $nUid = key($this->editconf
[$nTable]);
625 $nRec = BackendUtility
::getRecord($nTable, $nUid, 'pid,uid');
626 // Setting a blank editconf array for a new record:
627 $this->editconf
= array();
628 if ($this->getNewIconMode($nTable) == 'top') {
629 $this->editconf
[$nTable][$nRec['pid']] = 'new';
631 $this->editconf
[$nTable][-$nRec['uid']] = 'new';
633 // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
634 $this->R_URL_getvars
['edit'] = $this->editconf
;
635 // Re-compile the store* values since editconf changed...
636 $this->compileStoreDat();
638 // If a preview is requested
639 if (isset($_POST['_savedokview_x'])) {
640 // Get the first table and id of the data array from DataHandler
641 $table = reset(array_keys($this->data
));
642 $id = reset(array_keys($this->data
[$table]));
643 if (!MathUtility
::canBeInterpretedAsInteger($id)) {
644 $id = $tce->substNEWwithIDs
[$id];
646 // Store this information for later use
647 $this->previewData
['table'] = $table;
648 $this->previewData
['id'] = $id;
650 $tce->printLogErrorMessages(isset($_POST['_saveandclosedok_x']) ||
isset($_POST['_translation_savedok_x']) ?
$this->retUrl
: $this->R_URL_parts
['path'] . '?' . GeneralUtility
::implodeArrayForUrl('', $this->R_URL_getvars
));
652 // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
653 // because if not, we just get that element re-listed as new. And we don't want that!
654 if (isset($_POST['_saveandclosedok_x']) ||
isset($_POST['_translation_savedok_x']) ||
$this->closeDoc
< 0) {
655 $this->closeDocument(abs($this->closeDoc
));
660 * Initialize the normal module operation
664 public function init() {
665 $beUser = $this->getBackendUser();
666 // Setting more GPvars:
667 $this->popViewId
= GeneralUtility
::_GP('popViewId');
668 $this->popViewId_addParams
= GeneralUtility
::_GP('popViewId_addParams');
669 $this->viewUrl
= GeneralUtility
::_GP('viewUrl');
670 $this->editRegularContentFromId
= GeneralUtility
::_GP('editRegularContentFromId');
671 $this->recTitle
= GeneralUtility
::_GP('recTitle');
672 $this->noView
= GeneralUtility
::_GP('noView');
673 $this->perms_clause
= $beUser->getPagePermsClause(1);
674 // Set other internal variables:
675 $this->R_URL_getvars
['returnUrl'] = $this->retUrl
;
676 $this->R_URI
= $this->R_URL_parts
['path'] . '?' . ltrim(GeneralUtility
::implodeArrayForUrl('', $this->R_URL_getvars
), '&');
677 // Setting virtual document name
678 $this->MCONF
['name'] = 'xMOD_alt_doc.php';
679 // Create an instance of the document template object
680 $this->doc
= $GLOBALS['TBE_TEMPLATE'];
681 $pageRenderer = GeneralUtility
::makeInstance(PageRenderer
::class);
682 $pageRenderer->addInlineLanguageLabelFile('EXT:lang/locallang_alt_doc.xlf');
683 $pageRenderer->loadPrototype();
684 $this->doc
->backPath
= $GLOBALS['BACK_PATH'];
685 $this->doc
->setModuleTemplate('EXT:backend/Resources/Private/Templates/alt_doc.html');
686 $this->doc
->form
= '<form action="' . htmlspecialchars($this->R_URI
) . '" method="post" enctype="' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['form_enctype'] . '" name="editform" onsubmit="document.editform._scrollPosition.value=(document.documentElement.scrollTop || document.body.scrollTop); return TBE_EDITOR.checkSubmit(1);">';
687 // override the default jumpToUrl
688 $this->doc
->JScodeArray
['jumpToUrl'] = '
689 function jumpToUrl(URL,formEl) {
690 if (!TBE_EDITOR.isFormChanged()) {
691 window.location.href = URL;
692 } else if (formEl && formEl.type=="checkbox") {
693 formEl.checked = formEl.checked ? 0 : 1;
697 // define the window size of the element browser
698 $popupWindowWidth = 700;
699 $popupWindowHeight = 750;
700 $popupWindowSize = trim($beUser->getTSConfigVal('options.popupWindowSize'));
701 if (!empty($popupWindowSize)) {
702 list($popupWindowWidth, $popupWindowHeight) = GeneralUtility
::intExplode('x', $popupWindowSize);
704 $t3Configuration = array(
705 'PopupWindow' => array(
706 'width' => $popupWindowWidth,
707 'height' => $popupWindowHeight
711 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
713 // passwordDummy and decimalSign are used by tbe_editor.js and have to be declared here as
714 // TS object overwrites the object declared in tbe_editor.js
715 function typoSetup() { //
717 this.passwordDummy = "********";
718 this.PATH_typo3 = "";
719 this.decimalSign = ".";
721 var TS = new typoSetup();
724 function launchView(table,uid,bP) { //
725 var backPath= bP ? bP : "";
726 var thePreviewWindow="";
727 thePreviewWindow = window.open(backPath+' . GeneralUtility
::quoteJSvalue(BackendUtility
::getModuleUrl('show_item', array(), '') . '&table=') . '+encodeURIComponent(table)+"&uid="+encodeURIComponent(uid),"ShowItem"+TS.uniqueID,"height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0");
728 if (thePreviewWindow && thePreviewWindow.focus) {
729 thePreviewWindow.focus();
732 function deleteRecord(table,id,url) { //
734 ' . ($beUser->jsConfirmation(JsConfirmation
::DELETE
) ?
'confirm(' . GeneralUtility
::quoteJSvalue($this->getLanguageService()->getLL('deleteWarning')) . ')' : '1==1') . '
736 window.location.href = ' . GeneralUtility
::quoteJSvalue(BackendUtility
::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1' . BackendUtility
::getUrlToken('tceAction') . '&redirect="+escape(url)+"&vC=' . $beUser->veriCode() . '&prErr=1&uPT=1";
742 $previewCode = isset($_POST['_savedokview_x']) && $this->popViewId ?
$this->generatePreviewCode() : '';
744 $this->doc
->JScode
= $this->doc
->wrapScriptTags($javascript . $previewCode);
745 // Setting up the context sensitive menu:
746 $this->doc
->getContextMenuCode();
747 $this->doc
->bodyTagAdditions
= 'onload="window.scrollTo(0,' . MathUtility
::forceIntegerInRange(GeneralUtility
::_GP('_scrollPosition'), 0, 10000) . ');"';
749 $this->emitFunctionAfterSignal(__FUNCTION__
);
755 protected function generatePreviewCode() {
756 $currentPageId = MathUtility
::convertToPositiveInteger($this->popViewId
);
757 $table = $this->previewData
['table'];
758 $recordId = $this->previewData
['id'];
760 $pageTsConfig = BackendUtility
::getPagesTSconfig($currentPageId);
761 $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
762 ?
$pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
765 $recordArray = BackendUtility
::getRecord($table, $recordId);
767 // find the right preview page id
769 if (isset($previewConfiguration['previewPageId'])) {
770 $previewPageId = $previewConfiguration['previewPageId'];
772 // if no preview page was configured
773 if (!$previewPageId) {
774 $rootPageData = NULL
;
775 $rootLine = BackendUtility
::BEgetRootLine($currentPageId);
776 $currentPage = reset($rootLine);
777 if ((int)$currentPage['doktype'] === PageRepository
::DOKTYPE_DEFAULT
) {
778 // try the current page
779 $previewPageId = $currentPageId;
781 // or search for the root page
782 foreach ($rootLine as $page) {
783 if ($page['is_siteroot']) {
784 $rootPageData = $page;
788 $previewPageId = isset($rootPageData)
789 ?
(int)$rootPageData['uid']
799 $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
800 ?
$GLOBALS['TCA'][$table]['ctrl']['languageField']
802 if ($languageField && !empty($recordArray[$languageField])) {
803 $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
804 ?
$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
807 $l18nPointer && !empty($recordArray[$l18nPointer])
808 && isset($previewConfiguration['useDefaultLanguageRecord'])
809 && !$previewConfiguration['useDefaultLanguageRecord']
812 $recordId = $recordArray[$l18nPointer];
814 $linkParameters['L'] = $recordArray[$languageField];
817 // map record data to GET parameters
818 if (isset($previewConfiguration['fieldToParameterMap.'])) {
819 foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
820 $value = $recordArray[$field];
821 if ($field === 'uid') {
824 $linkParameters[$parameterName] = $value;
828 // add/override parameters by configuration
829 if (isset($previewConfiguration['additionalGetParameters.'])) {
830 $linkParameters = array_replace($linkParameters, $previewConfiguration['additionalGetParameters.']);
833 $this->popViewId
= $previewPageId;
834 $this->popViewId_addParams
= GeneralUtility
::implodeArrayForUrl('', $linkParameters, '', FALSE
, TRUE
);
836 $previewPageRootline = BackendUtility
::BEgetRootLine($this->popViewId
);
840 . BackendUtility
::viewOnClick($this->popViewId
, '', $previewPageRootline, '', $this->viewUrl
, $this->popViewId_addParams
, FALSE
)
844 . BackendUtility
::viewOnClick($this->popViewId
, '', $previewPageRootline, '', $this->viewUrl
, $this->popViewId_addParams
)
850 * Main module operation
854 public function main() {
857 if (is_array($this->editconf
)) {
858 // Initialize TCEforms (rendering the forms)
859 $this->tceforms
= GeneralUtility
::makeInstance(FormEngine
::class);
860 $this->tceforms
->doSaveFieldName
= 'doSave';
861 $this->tceforms
->localizationMode
= GeneralUtility
::inList('text,media', $this->localizationMode
) ?
$this->localizationMode
: '';
862 // text,media is keywords defined in TYPO3 Core API..., see "l10n_cat"
863 $this->tceforms
->returnUrl
= $this->R_URI
;
864 if ($this->editRegularContentFromId
) {
865 $this->editRegularContentFromId();
867 // Creating the editing form, wrap it with buttons, document selector etc.
868 $editForm = $this->makeEditForm();
870 $this->firstEl
= reset($this->elementsData
);
871 // Checking if the currently open document is stored in the list of "open documents" - if not, then add it:
872 if (($this->docDat
[1] !== $this->storeUrlMd5 ||
!isset($this->docHandler
[$this->storeUrlMd5
])) && !$this->dontStoreDocumentRef
) {
873 $this->docHandler
[$this->storeUrlMd5
] = array($this->storeTitle
, $this->storeArray
, $this->storeUrl
, $this->firstEl
);
874 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler
, $this->storeUrlMd5
));
875 BackendUtility
::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler
));
877 // Module configuration
878 $this->modTSconfig
= $this->viewId ? BackendUtility
::getModTSconfig($this->viewId
, 'mod.xMOD_alt_doc') : array();
879 $body = $this->tceforms
->printNeededJSFunctions_top();
880 $body .= $this->compileForm($editForm);
881 $body .= $this->tceforms
->printNeededJSFunctions();
885 // The page will show only if there is a valid page and if this page may be viewed by the user
886 $this->pageinfo
= BackendUtility
::readPageAccess($this->viewId
, $this->perms_clause
);
887 // Setting up the buttons and markers for docheader
888 $docHeaderButtons = $this->getButtons();
890 'LANGSELECTOR' => $this->langSelector(),
891 'EXTRAHEADER' => $this->extraFormHeaders(),
892 'CSH' => $docHeaderButtons['csh'],
895 // Build the <body> for the module
896 $this->content
= $this->doc
->startPage('TYPO3 Edit Document');
897 $this->content
.= $this->doc
->moduleBody($this->pageinfo
, $docHeaderButtons, $markers);
898 $this->content
.= $this->doc
->endPage();
899 $this->content
= $this->doc
->insertStylesAndJS($this->content
);
903 * Outputting the accumulated content to screen
907 public function printContent() {
911 /***************************
913 * Sub-content functions, rendering specific parts of the module content.
915 ***************************/
917 * Creates the editing form with TCEforms, based on the input from GPvars.
919 * @return string HTML form elements wrapped in tables
921 public function makeEditForm() {
922 // Initialize variables:
923 $this->elementsData
= array();
929 $beUser = $this->getBackendUser();
930 // Traverse the GPvar edit array
932 foreach ($this->editconf
as $table => $conf) {
933 if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
934 // Traverse the keys/comments of each table (keys can be a commalist of uids)
935 foreach ($conf as $cKey => $cmd) {
936 if ($cmd == 'edit' ||
$cmd == 'new') {
938 $ids = GeneralUtility
::trimExplode(',', $cKey, TRUE
);
940 foreach ($ids as $theUid) {
941 // Checking if the user has permissions? (Only working as a precaution,
942 // because the final permission check is always down in TCE. But it's
943 // good to notify the user on beforehand...)
944 // First, resetting flags.
946 $deniedAccessReason = '';
949 // If the command is to create a NEW record...:
951 // NOTICE: the id values in this case points to the page uid onto which the
952 // record should be create OR (if the id is negativ) to a record from the
953 // same table AFTER which to create the record.
955 // Find parent page on which the new record reside
956 // Less than zero - find parent page
958 $calcPRec = BackendUtility
::getRecord($table, abs($theUid));
959 $calcPRec = BackendUtility
::getRecord('pages', $calcPRec['pid']);
962 $calcPRec = BackendUtility
::getRecord('pages', abs($theUid));
964 // Now, calculate whether the user has access to creating new records on this position:
965 if (is_array($calcPRec)) {
966 // Permissions for the parent page
967 $CALC_PERMS = $beUser->calcPerms($calcPRec);
968 if ($table == 'pages') {
970 $hasAccess = $CALC_PERMS & Permission
::PAGE_NEW ?
1 : 0;
973 $hasAccess = $CALC_PERMS & Permission
::CONTENT_EDIT ?
1 : 0;
974 $this->viewId
= $calcPRec['uid'];
978 // Don't save this document title in the document selector if the document is new.
979 $this->dontStoreDocumentRef
= 1;
982 $calcPRec = BackendUtility
::getRecord($table, $theUid);
983 BackendUtility
::fixVersioningPid($table, $calcPRec);
984 if (is_array($calcPRec)) {
985 if ($table == 'pages') { // If pages:
986 $CALC_PERMS = $beUser->calcPerms($calcPRec);
987 $hasAccess = $CALC_PERMS & Permission
::PAGE_EDIT ?
1 : 0;
988 $deleteAccess = $CALC_PERMS & Permission
::PAGE_DELETE ?
1 : 0;
989 $this->viewId
= $calcPRec['uid'];
991 // Fetching pid-record first
992 $CALC_PERMS = $beUser->calcPerms(BackendUtility
::getRecord('pages', $calcPRec['pid']));
993 $hasAccess = $CALC_PERMS & Permission
::CONTENT_EDIT ?
1 : 0;
994 $deleteAccess = $CALC_PERMS & Permission
::CONTENT_EDIT ?
1 : 0;
995 $this->viewId
= $calcPRec['pid'];
996 // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
997 if ($GLOBALS['TCA'][$table]['ctrl']['languageField'] && $calcPRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']] > 0) {
998 $this->viewId_addParams
= '&L=' . $calcPRec[$GLOBALS['TCA'][$table]['ctrl']['languageField']];
1001 // Check internals regarding access:
1002 $isRootLevelRestrictionIgnored = BackendUtility
::isRootLevelRestrictionIgnored($table);
1003 if ($hasAccess ||
(int)$calcPRec['pid'] === 0 && $isRootLevelRestrictionIgnored) {
1004 $hasAccess = $beUser->recordEditAccessInternals($table, $calcPRec);
1005 $deniedAccessReason = $beUser->errorMsg
;
1011 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'])) {
1012 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/alt_doc.php']['makeEditForm_accessCheck'] as $_funcRef) {
1017 'hasAccess' => $hasAccess
1019 $hasAccess = GeneralUtility
::callUserFunction($_funcRef, $_params, $this);
1022 // AT THIS POINT we have checked the access status of the editing/creation of
1023 // records and we can now proceed with creating the form elements:
1025 /** @var DataPreprocessor $trData */
1026 $prevPageID = is_object($trData) ?
$trData->prevPageID
: '';
1027 $trData = GeneralUtility
::makeInstance(DataPreprocessor
::class);
1028 $trData->addRawData
= TRUE
;
1029 $trData->defVals
= $this->defVals
;
1030 $trData->lockRecords
= 1;
1031 $trData->prevPageID
= $prevPageID;
1033 $trData->fetchRecord($table, $theUid, $cmd == 'new' ?
'new' : '');
1034 $rec = reset($trData->regTableItems_data
);
1035 $rec['uid'] = $cmd == 'new' ?
uniqid('NEW', TRUE
) : $theUid;
1036 if ($cmd == 'new') {
1037 $rec['pid'] = $theUid == 'prev' ?
$thePrevUid : $theUid;
1039 $this->elementsData
[] = array(
1041 'uid' => $rec['uid'],
1042 'pid' => $rec['pid'],
1044 'deleteAccess' => $deleteAccess
1046 // Now, render the form:
1047 if (is_array($rec)) {
1048 // Setting visual path / title of form:
1049 $this->generalPathOfForm
= $this->tceforms
->getRecordPath($table, $rec);
1050 if (!$this->storeTitle
) {
1051 $this->storeTitle
= $this->recTitle ?
htmlspecialchars($this->recTitle
) : BackendUtility
::getRecordTitle($table, $rec, TRUE
);
1053 // Setting variables in TCEforms object:
1054 if (is_array($this->overrideVals
) && is_array($this->overrideVals
[$table])) {
1055 $this->tceforms
->hiddenFieldListArr
= array_keys($this->overrideVals
[$table]);
1057 // Create form for the record (either specific list of fields or the whole record):
1059 if ($this->columnsOnly
) {
1060 if (is_array($this->columnsOnly
)) {
1061 $panel .= $this->tceforms
->getListedFields($table, $rec, $this->columnsOnly
[$table]);
1063 $panel .= $this->tceforms
->getListedFields($table, $rec, $this->columnsOnly
);
1066 $panel .= $this->tceforms
->getMainFields($table, $rec);
1068 $panel = $this->tceforms
->wrapTotal($panel, $rec, $table);
1069 // Setting the pid value for new records:
1070 if ($cmd == 'new') {
1071 $panel .= '<input type="hidden" name="data[' . $table . '][' . $rec['uid'] . '][pid]" value="' . $rec['pid'] . '" />';
1074 // Display "is-locked" message:
1075 if ($lockInfo = BackendUtility
::isRecordLocked($table, $rec['uid'])) {
1076 /** @var $flashMessage \TYPO3\CMS\Core\Messaging\FlashMessage */
1077 $flashMessage = GeneralUtility
::makeInstance(FlashMessage
::class, htmlspecialchars($lockInfo['msg']), '', FlashMessage
::WARNING
);
1078 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1079 $flashMessageService = GeneralUtility
::makeInstance(FlashMessageService
::class);
1080 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
1081 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1082 $defaultFlashMessageQueue->enqueue($flashMessage);
1085 $editForm .= $panel;
1087 $thePrevUid = $rec['uid'];
1090 $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', TRUE
) . '<br /><br />' . ($deniedAccessReason ?
'Reason: ' . htmlspecialchars($deniedAccessReason) . '<br /><br />' : '');
1101 * Create the panel of buttons for submitting the form or otherwise perform operations.
1103 * @return array All available buttons as an assoc. array
1105 protected function getButtons() {
1106 $lang = $this->getLanguageService();
1116 'columns_only' => '',
1118 'translation_save' => '',
1119 'translation_saveclear' => ''
1121 // Render SAVE type buttons:
1122 // The action of each button is decided by its name attribute. (See doProcessData())
1123 if (!$this->errorC
&& !$GLOBALS['TCA'][$this->firstEl
['table']]['ctrl']['readOnly']) {
1125 $buttons['save'] = IconUtility
::getSpriteIcon('actions-document-save', array('html' => '<input type="image" name="_savedok" class="c-inputButton" src="clear.gif" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc', TRUE
) . '" />'));
1126 // SAVE / VIEW button:
1127 if ($this->viewId
&& !$this->noView
&& $this->getNewIconMode($this->firstEl
['table'], 'saveDocView')) {
1128 $buttons['save_view'] = IconUtility
::getSpriteIcon('actions-document-save-view', array('html' => '<input onclick="window.open(\'\', \'newTYPO3frontendWindow\');" type="image" class="c-inputButton" name="_savedokview" src="clear.gif" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDocShow', TRUE
) . '" />'));
1130 // SAVE / NEW button:
1131 if (count($this->elementsData
) === 1 && $this->getNewIconMode($this->firstEl
['table'])) {
1132 $buttons['save_new'] = IconUtility
::getSpriteIcon('actions-document-save-new', array('html' => '<input type="image" class="c-inputButton" name="_savedoknew" src="clear.gif" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveNewDoc', TRUE
) . '" />'));
1135 $buttons['save_close'] = IconUtility
::getSpriteIcon('actions-document-save-close', array('html' => '<input type="image" class="c-inputButton" name="_saveandclosedok" src="clear.gif" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc', TRUE
) . '" />'));
1136 // FINISH TRANSLATION / SAVE / CLOSE
1137 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1138 $buttons['translation_save'] = '<input type="image" class="c-inputButton" name="_translation_savedok" src="' . IconUtility
::skinImg($this->doc
->backPath
, 'sysext/t3skin/images/icons/actions/document-save-translation.png', '', 1) . '" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDoc', TRUE
) . '" /> ';
1139 $buttons['translation_saveclear'] = '<input type="image" class="c-inputButton" name="_translation_savedokclear" src="' . IconUtility
::skinImg($this->doc
->backPath
, 'sysext/t3skin/images/icons/actions/document-save-cleartranslationcache.png', '', 1) . '" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDocClear', TRUE
) . '" />';
1143 $buttons['close'] = '<a href="#" class="t3js-editform-close" title="' . $lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc', TRUE
) . '">' . IconUtility
::getSpriteIcon('actions-document-close') . '</a>';
1144 // DELETE + UNDO buttons:
1145 if (!$this->errorC
&& !$GLOBALS['TCA'][$this->firstEl
['table']]['ctrl']['readOnly'] && count($this->elementsData
) === 1) {
1146 if ($this->firstEl
['cmd'] != 'new' && MathUtility
::canBeInterpretedAsInteger($this->firstEl
['uid'])) {
1148 if ($this->firstEl
['deleteAccess'] && !$GLOBALS['TCA'][$this->firstEl
['table']]['ctrl']['readOnly'] && !$this->getNewIconMode($this->firstEl
['table'], 'disableDelete')) {
1149 $aOnClick = 'return deleteRecord(' . GeneralUtility
::quoteJSvalue($this->firstEl
['table']) . ',' . GeneralUtility
::quoteJSvalue($this->firstEl
['uid']) . ', ' . GeneralUtility
::quoteJSvalue($this->retUrl
) . ');';
1150 $buttons['delete'] = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '" title="' . $lang->getLL('deleteItem', TRUE
) . '">' . IconUtility
::getSpriteIcon('actions-edit-delete') . '</a>';
1153 $undoRes = $this->getDatabaseConnection()->exec_SELECTquery('tstamp', 'sys_history', 'tablename=' . $this->getDatabaseConnection()->fullQuoteStr($this->firstEl
['table'], 'sys_history') . ' AND recuid=' . (int)$this->firstEl
['uid'], '', 'tstamp DESC', '1');
1154 if ($undoButtonR = $this->getDatabaseConnection()->sql_fetch_assoc($undoRes)) {
1155 $aOnClick = 'window.location.href=' .
1156 GeneralUtility
::quoteJSvalue(
1157 BackendUtility
::getModuleUrl(
1160 'element' => $this->firstEl
['table'] . ':' . $this->firstEl
['uid'],
1161 'revert' => 'ALL_FIELDS',
1163 'returnUrl' => $this->R_URI
,
1166 ) . '; return false;';
1167 $buttons['undo'] = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '"' . ' title="' . htmlspecialchars(sprintf($lang->getLL('undoLastChange'), BackendUtility
::calcAge(($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']), $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')))) . '">' . IconUtility
::getSpriteIcon('actions-edit-undo') . '</a>';
1169 if ($this->getNewIconMode($this->firstEl
['table'], 'showHistory')) {
1170 $aOnClick = 'window.location.href=' .
1171 GeneralUtility
::quoteJSvalue(
1172 BackendUtility
::getModuleUrl(
1175 'element' => $this->firstEl
['table'] . ':' . $this->firstEl
['uid'],
1176 'returnUrl' => $this->R_URI
,
1179 ) . '; return false;';
1180 $buttons['history'] = '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '">' . IconUtility
::getSpriteIcon('actions-document-history-open') . '</a>';
1182 // If only SOME fields are shown in the form, this will link the user to the FULL form:
1183 if ($this->columnsOnly
) {
1184 $buttons['columns_only'] = '<a href="' . htmlspecialchars(($this->R_URI
. '&columnsOnly=')) . '" title="' . $lang->getLL('editWholeRecord', TRUE
) . '">' . IconUtility
::getSpriteIcon('actions-document-open') . '</a>';
1189 $buttons['csh'] = BackendUtility
::cshItem('xMOD_csh_corebe', 'TCEforms');
1190 $buttons['shortcut'] = $this->shortCutLink();
1191 $buttons['open_in_new_window'] = $this->openInNewWindowLink();
1196 * Returns the language switch/selector for editing,
1197 * show only when a single record is edited
1198 * - multiple records are too confusing
1200 * @return string The HTML
1202 public function langSelector() {
1204 if (count($this->elementsData
) === 1) {
1205 $langSelector = $this->languageSwitch($this->firstEl
['table'], $this->firstEl
['uid'], $this->firstEl
['pid']);
1207 return $langSelector;
1211 * Compiles the extra form headers if the tceforms
1213 * @return string The HTML
1215 public function extraFormHeaders() {
1216 $extraTemplate = '';
1217 if (is_array($this->tceforms
->extraFormHeaders
)) {
1218 $extraTemplate = HtmlParser
::getSubpart($this->doc
->moduleTemplate
, '###DOCHEADER_EXTRAHEADER###');
1219 $extraTemplate = HtmlParser
::substituteMarker($extraTemplate, '###EXTRAHEADER###', implode(LF
, $this->tceforms
->extraFormHeaders
));
1221 return $extraTemplate;
1225 * Put together the various elements (buttons, selectors, form) into a table
1227 * @param string $editForm HTML form.
1228 * @return string Composite HTML
1230 public function compileForm($editForm) {
1232 <!-- EDITING FORM -->
1235 <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl
) . '" />
1236 <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl
) . '" />';
1237 if ($this->returnNewPageId
) {
1238 $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1240 $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId
) . '" />';
1241 if ($this->viewId_addParams
) {
1242 $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams
) . '" />';
1245 <input type="hidden" name="closeDoc" value="0" />
1246 <input type="hidden" name="doSave" value="0" />
1247 <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1248 <input type="hidden" name="_scrollPosition" value="" />' . FormEngine
::getHiddenTokenField('editRecord');
1249 return $formContent;
1253 * Create shortcut icon
1257 public function shortCutLink() {
1258 if ($this->returnUrl
=== 'sysext/backend/Resources/Private/Templates/Close.html' ||
!$this->getBackendUser()->mayMakeShortcut()) {
1261 return $this->doc
->makeShortcutIcon('returnUrl,edit,defVals,overrideVals,columnsOnly,returnNewPageId,editRegularContentFromId,noView', '', $this->MCONF
['name'], 1);
1265 * Creates open-in-window link
1269 public function openInNewWindowLink() {
1270 if ($this->returnUrl
=== 'sysext/backend/Resources/Private/Templates/Close.html') {
1273 $aOnClick = 'vHWin=window.open(' . GeneralUtility
::quoteJSvalue(GeneralUtility
::linkThisScript(array('returnUrl' => 'sysext/backend/Resources/Private/Templates/Close.html'))) . ',' . GeneralUtility
::quoteJSvalue(md5($this->R_URI
)) . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1274 return '<a href="#" onclick="' . htmlspecialchars($aOnClick) . '" title="' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow', TRUE
) . '">' . IconUtility
::getSpriteIcon('actions-window-open') . '</a>';
1277 /***************************
1279 * Localization stuff
1281 ***************************/
1283 * Make selector box for creating new translation for a record or switching to edit the record in an existing language.
1284 * Displays only languages which are available for the current page.
1286 * @param string $table Table name
1287 * @param int $uid Uid for which to create a new language
1288 * @param int $pid Pid of the record
1289 * @return string <select> HTML element (if there were items for the box anyways...)
1291 public function languageSwitch($table, $uid, $pid = NULL
) {
1293 $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1294 $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1295 // Table editable and activated for languages?
1296 if ($this->getBackendUser()->check('tables_modify', $table) && $languageField && $transOrigPointerField && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']) {
1297 if (is_null($pid)) {
1298 $row = BackendUtility
::getRecord($table, $uid, 'pid');
1301 // Get all avalibale languages for the page
1302 $langRows = $this->getLanguages($pid);
1303 // Page available in other languages than default language?
1304 if (is_array($langRows) && count($langRows) > 1) {
1305 $rowsByLang = array();
1306 $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1307 // Get record in current language
1308 $rowCurrent = BackendUtility
::getLiveVersionOfRecord($table, $uid, $fetchFields);
1309 if (!is_array($rowCurrent)) {
1310 $rowCurrent = BackendUtility
::getRecord($table, $uid, $fetchFields);
1312 $currentLanguage = $rowCurrent[$languageField];
1313 // Disabled for records with [all] language!
1314 if ($currentLanguage > -1) {
1315 // Get record in default language if needed
1316 if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1317 $rowsByLang[0] = BackendUtility
::getLiveVersionOfRecord($table, $rowCurrent[$transOrigPointerField], $fetchFields);
1318 if (!is_array($rowsByLang[0])) {
1319 $rowsByLang[0] = BackendUtility
::getRecord($table, $rowCurrent[$transOrigPointerField], $fetchFields);
1322 $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1324 if ($rowCurrent[$transOrigPointerField] ||
$currentLanguage === '0') {
1325 // Get record in other languages to see what's already available
1326 $translations = $this->getDatabaseConnection()->exec_SELECTgetRows($fetchFields, $table, 'pid=' . (int)$pid . ' AND ' . $languageField . '>0' . ' AND ' . $transOrigPointerField . '=' . (int)$rowsByLang[0]['uid'] . BackendUtility
::deleteClause($table) . BackendUtility
::versioningPlaceholderClause($table));
1327 foreach ($translations as $row) {
1328 $rowsByLang[$row[$languageField]] = $row;
1331 $langSelItems = array();
1332 foreach ($langRows as $lang) {
1333 if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1334 $newTranslation = isset($rowsByLang[$lang['uid']]) ?
'' : ' [' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.new', TRUE
) . ']';
1335 // Create url for creating a localized record
1336 if ($newTranslation) {
1337 $redirectUrl = BackendUtility
::getModuleUrl('record_edit', array(
1338 'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1339 'returnUrl' => $this->retUrl
1340 )) . BackendUtility
::getUrlToken('editRecord');
1341 $href = $this->doc
->issueCommand('&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'], $redirectUrl);
1343 $href = BackendUtility
::getModuleUrl('record_edit', array(
1344 'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1345 'returnUrl' => $this->retUrl
1346 )) . BackendUtility
::getUrlToken('editRecord');
1348 $langSelItems[$lang['uid']] = '
1349 <option value="' . htmlspecialchars($href) . '"' . ($currentLanguage == $lang['uid'] ?
' selected="selected"' : '') . '>' . htmlspecialchars(($lang['title'] . $newTranslation)) . '</option>';
1352 // If any languages are left, make selector:
1353 if (count($langSelItems) > 1) {
1354 $onChange = 'if(this.options[this.selectedIndex].value){window.location.href=(this.options[this.selectedIndex].value);}';
1355 $content = $this->getLanguageService()->sL('LLL:EXT:lang/locallang_general.xlf:LGL.language', TRUE
) . ' <select name="_langSelector" onchange="' . htmlspecialchars($onChange) . '">
1356 ' . implode('', $langSelItems) . '
1366 * Redirects to FormEngine with new parameters to edit a just created localized record
1368 * @param string $justLocalized String passed by GET &justLocalized=
1371 public function localizationRedirect($justLocalized) {
1372 list($table, $orig_uid, $language) = explode(':', $justLocalized);
1373 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['languageField'] && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']) {
1374 $localizedRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('uid', $table, $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$language . ' AND ' . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$orig_uid . BackendUtility
::deleteClause($table) . BackendUtility
::versioningPlaceholderClause($table));
1375 if (is_array($localizedRecord)) {
1376 // Create parameters and finally run the classic page module for creating a new page translation
1377 $location = BackendUtility
::getModuleUrl('record_edit', array(
1378 'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1379 'returnUrl' => GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('returnUrl'))
1381 HttpUtility
::redirect($location . BackendUtility
::getUrlToken('editRecord'));
1387 * Returns sys_language records available for record translations on given page.
1389 * @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)
1390 * @return array Language records including faked record for default language
1392 public function getLanguages($id) {
1393 $modSharedTSconfig = BackendUtility
::getModTSconfig($id, 'mod.SHARED');
1394 // Fallback non sprite-configuration
1395 if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1396 $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace('.gif', '', $modSharedTSconfig['properties']['defaultLanguageFlag']);
1403 'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1404 ?
$modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sl('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1405 : $this->getLanguageService()->sl('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage'),
1406 'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1409 $exQ = $this->getBackendUser()->isAdmin() ?
'' : ' AND sys_language.hidden=0';
1411 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows('sys_language.*', 'pages_language_overlay,sys_language', 'pages_language_overlay.sys_language_uid=sys_language.uid AND pages_language_overlay.pid=' . (int)$id . BackendUtility
::deleteClause('pages_language_overlay') . $exQ, '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', 'sys_language.title');
1413 $rows = $this->getDatabaseConnection()->exec_SELECTgetRows('sys_language.*', 'sys_language', 'sys_language.hidden=0', '', 'sys_language.title');
1416 foreach ($rows as $row) {
1417 $languages[$row['uid']] = $row;
1423 /***************************
1427 ***************************/
1429 * Fix $this->editconf if versioning applies to any of the records
1431 * @param array|bool $mapArray Mapping between old and new ids if auto-versioning has been performed.
1434 public function fixWSversioningInEditConf($mapArray = FALSE
) {
1435 // Traverse the editConf array
1436 if (is_array($this->editconf
)) {
1438 foreach ($this->editconf
as $table => $conf) {
1439 if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1440 // Traverse the keys/comments of each table (keys can be a commalist of uids)
1442 foreach ($conf as $cKey => $cmd) {
1443 if ($cmd == 'edit') {
1444 // Traverse the ids:
1445 $ids = GeneralUtility
::trimExplode(',', $cKey, TRUE
);
1446 foreach ($ids as $idKey => $theUid) {
1447 if (is_array($mapArray)) {
1448 if ($mapArray[$table][$theUid]) {
1449 $ids[$idKey] = $mapArray[$table][$theUid];
1452 // Default, look for versions in workspace for record:
1453 $calcPRec = $this->getRecordForEdit($table, $theUid);
1454 if (is_array($calcPRec)) {
1455 // Setting UID again if it had changed, eg. due to workspace versioning.
1456 $ids[$idKey] = $calcPRec['uid'];
1460 // Add the possibly manipulated IDs to the new-build newConf array:
1461 $newConf[implode(',', $ids)] = $cmd;
1463 $newConf[$cKey] = $cmd;
1466 // Store the new conf array:
1467 $this->editconf
[$table] = $newConf;
1474 * Get record for editing.
1476 * @param string $table Table name
1477 * @param int $theUid Record UID
1478 * @return array Returns record to edit, FALSE if none
1480 public function getRecordForEdit($table, $theUid) {
1481 // Fetch requested record:
1482 $reqRecord = BackendUtility
::getRecord($table, $theUid, 'uid,pid');
1483 if (is_array($reqRecord)) {
1484 // If workspace is OFFLINE:
1485 if ($this->getBackendUser()->workspace
!= 0) {
1486 // Check for versioning support of the table:
1487 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1488 // If the record is already a version of "something" pass it by.
1489 if ($reqRecord['pid'] == -1) {
1490 // (If it turns out not to be a version of the current workspace there will be trouble, but that is handled inside TCEmain then and in the interface it would clearly be an error of links if the user accesses such a scenario)
1493 // The input record was online and an offline version must be found or made:
1494 // Look for version of this workspace:
1495 $versionRec = BackendUtility
::getWorkspaceVersionOfRecord($this->getBackendUser()->workspace
, $table, $reqRecord['uid'], 'uid,pid,t3ver_oid');
1496 return is_array($versionRec) ?
$versionRec : $reqRecord;
1499 // This means that editing cannot occur on this record because it was not supporting versioning which is required inside an offline workspace.
1503 // In ONLINE workspace, just return the originally requested record:
1507 // Return FALSE because the table/uid was not found anyway.
1513 * Function, which populates the internal editconf array with editing commands for all tt_content elements from the normal column in normal language from the page pointed to by $this->editRegularContentFromId
1516 * @deprecated since TYPO3 CMS 7, will be removed with TYPO3 CMS 8
1518 public function editRegularContentFromId() {
1519 GeneralUtility
::logDeprecatedFunction();
1520 $dbConnection = $this->getDatabaseConnection();
1521 $res = $dbConnection->exec_SELECTquery('uid', 'tt_content', 'pid=' . (int)$this->editRegularContentFromId
. BackendUtility
::deleteClause('tt_content') . BackendUtility
::versioningPlaceholderClause('tt_content') . ' AND colPos=0 AND sys_language_uid=0', '', 'sorting');
1522 if ($dbConnection->sql_num_rows($res)) {
1524 while ($ecRec = $dbConnection->sql_fetch_assoc($res)) {
1525 $ecUids[] = $ecRec['uid'];
1527 $this->editconf
['tt_content'][implode(',', $ecUids)] = 'edit';
1529 $dbConnection->sql_free_result($res);
1533 * Populates the variables $this->storeArray, $this->storeUrl, $this->storeUrlMd5
1538 public function compileStoreDat() {
1539 $this->storeArray
= GeneralUtility
::compileSelectedGetVarsFromArray('edit,defVals,overrideVals,columnsOnly,noView,editRegularContentFromId,workspace', $this->R_URL_getvars
);
1540 $this->storeUrl
= GeneralUtility
::implodeArrayForUrl('', $this->storeArray
);
1541 $this->storeUrlMd5
= md5($this->storeUrl
);
1545 * Function used to look for configuration of buttons in the form: Fx. disabling buttons or showing them at various positions.
1547 * @param string $table The table for which the configuration may be specific
1548 * @param string $key The option for look for. Default is checking if the saveDocNew button should be displayed.
1549 * @return string Return value fetched from USER TSconfig
1551 public function getNewIconMode($table, $key = 'saveDocNew') {
1552 $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1553 $output = trim(isset($TSconfig['properties'][$table]) ?
$TSconfig['properties'][$table] : $TSconfig['value']);
1558 * Handling the closing of a document
1560 * @param int $code Close code: 0/1 will redirect to $this->retUrl, 3 will clear the docHandler (thus closing all documents) and other values will call setDocument with ->retUrl
1563 public function closeDocument($code = 0) {
1564 // If current document is found in docHandler,
1565 // then unset it, possibly unset it ALL and finally, write it to the session data
1566 if (isset($this->docHandler
[$this->storeUrlMd5
])) {
1567 // add the closing document to the recent documents
1568 $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1569 if (!is_array($recentDocs)) {
1570 $recentDocs = array();
1572 $closedDoc = $this->docHandler
[$this->storeUrlMd5
];
1573 $recentDocs = array_merge(array($this->storeUrlMd5
=> $closedDoc), $recentDocs);
1574 if (count($recentDocs) > 8) {
1575 $recentDocs = array_slice($recentDocs, 0, 8);
1577 // remove it from the list of the open documents
1578 unset($this->docHandler
[$this->storeUrlMd5
]);
1580 $recentDocs = array_merge($this->docHandler
, $recentDocs);
1581 $this->docHandler
= array();
1583 $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1584 $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler
, $this->docDat
[1]));
1585 BackendUtility
::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler
));
1587 // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by other scripts, like wizard_add, to know which records was created or so...)
1588 if ($this->returnEditConf
&& $this->retUrl
!= BackendUtility
::getModuleUrl('dummy')) {
1589 $this->retUrl
.= '&returnEditConf=' . rawurlencode(json_encode($this->editconf
));
1591 // If code is NOT set OR set to 1, then make a header location redirect to $this->retUrl
1592 if (!$code ||
$code == 1) {
1593 HttpUtility
::redirect($this->retUrl
);
1595 $this->setDocument('', $this->retUrl
);
1600 * Redirects to the document pointed to by $currentDocFromHandlerMD5 OR $retUrl (depending on some internal calculations).
1601 * Most likely you will get a header-location redirect from this function.
1603 * @param string $currentDocFromHandlerMD5 Pointer to the document in the docHandler array
1604 * @param string $retUrl Alternative/Default retUrl
1607 public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '') {
1608 if ($retUrl === '') {
1611 if (!$this->modTSconfig
['properties']['disableDocSelector'] && is_array($this->docHandler
) && !empty($this->docHandler
)) {
1612 if (isset($this->docHandler
[$currentDocFromHandlerMD5])) {
1613 $setupArr = $this->docHandler
[$currentDocFromHandlerMD5];
1615 $setupArr = reset($this->docHandler
);
1618 $sParts = parse_url(GeneralUtility
::getIndpEnv('REQUEST_URI'));
1619 $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1622 HttpUtility
::redirect($retUrl);
1626 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1628 protected function getBackendUser() {
1629 return $GLOBALS['BE_USER'];
1633 * Returns LanguageService
1635 * @return \TYPO3\CMS\Lang\LanguageService
1637 protected function getLanguageService() {
1638 return $GLOBALS['LANG'];
1642 * Returns the database connection
1644 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
1646 protected function getDatabaseConnection() {
1647 return $GLOBALS['TYPO3_DB'];