TYPO3 CMS  TYPO3_7-6
EditDocumentController.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
42 
48 {
50  const DOCUMENT_CLOSE_MODE_REDIRECT = 1; // works like DOCUMENT_CLOSE_MODE_DEFAULT
53 
61  public $editconf;
62 
70  public $columnsOnly;
71 
78  public $defVals;
79 
86  public $overrideVals;
87 
94  public $returnUrl;
95 
101  public $closeDoc;
102 
110  public $doSave;
111 
117  public $data;
118 
122  public $cmd;
123 
127  public $mirror;
128 
134  public $cacheCmd;
135 
141  public $redirect;
142 
150 
154  public $vC;
155 
161  public $uc;
162 
168  public $popViewId;
169 
176 
182  public $viewUrl;
183 
192 
198  public $recTitle;
199 
205  public $noView;
206 
211 
219 
225  protected $workspace;
226 
232  public $doc;
233 
239  public $template;
240 
246  public $content;
247 
255  public $retUrl;
256 
264  public $R_URL_parts;
265 
273 
279  public $R_URI;
280 
284  public $MCONF;
285 
289  public $pageinfo;
290 
297  public $storeTitle = '';
298 
305  public $storeArray;
306 
312  public $storeUrl;
313 
319  public $storeUrlMd5;
320 
326  public $docDat;
327 
336  public $docHandler;
337 
344 
350  public $firstEl;
351 
357  public $errorC;
358 
364  public $newC;
365 
372  public $viewId;
373 
380 
386  public $modTSconfig;
387 
392 
399 
404 
410  protected $previewData = [];
411 
415  public function __construct()
416  {
417  parent::__construct();
418  $this->moduleTemplate->setUiBlock(true);
419  $GLOBALS['SOBE'] = $this;
420  $this->getLanguageService()->includeLLFile('EXT:lang/locallang_alt_doc.xlf');
421  }
422 
428  protected function getSignalSlotDispatcher()
429  {
430  if (!isset($this->signalSlotDispatcher)) {
431  $this->signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
432  }
434  }
435 
441  protected function emitFunctionAfterSignal($signalName)
442  {
443  $this->getSignalSlotDispatcher()->dispatch(__CLASS__, $signalName . 'After', [$this]);
444  }
445 
451  public function preInit()
452  {
453  if (GeneralUtility::_GP('justLocalized')) {
454  $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
455  }
456  // Setting GPvars:
457  $this->editconf = GeneralUtility::_GP('edit');
458  $this->defVals = GeneralUtility::_GP('defVals');
459  $this->overrideVals = GeneralUtility::_GP('overrideVals');
460  $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
461  $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
462  $this->closeDoc = (int)GeneralUtility::_GP('closeDoc');
463  $this->doSave = GeneralUtility::_GP('doSave');
464  $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
465  $this->localizationMode = GeneralUtility::_GP('localizationMode');
466  $this->workspace = GeneralUtility::_GP('workspace');
467  $this->uc = GeneralUtility::_GP('uc');
468  // Setting override values as default if defVals does not exist.
469  if (!is_array($this->defVals) && is_array($this->overrideVals)) {
470  $this->defVals = $this->overrideVals;
471  }
472  // Setting return URL
473  $this->retUrl = $this->returnUrl ?: BackendUtility::getModuleUrl('dummy');
474  // Fix $this->editconf if versioning applies to any of the records
475  $this->fixWSversioningInEditConf();
476  // Make R_URL (request url) based on input GETvars:
477  $this->R_URL_parts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
478  $this->R_URL_getvars = GeneralUtility::_GET();
479  $this->R_URL_getvars['edit'] = $this->editconf;
480  // MAKE url for storing
481  $this->compileStoreDat();
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 ((int)$this->closeDoc > self::DOCUMENT_CLOSE_MODE_DEFAULT) {
487  $this->closeDocument($this->closeDoc);
488  }
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]);
494  }
495 
496  // Sets a temporary workspace, this request is based on
497  if ($this->workspace !== null) {
498  $this->getBackendUser()->setTemporaryWorkspace($this->workspace);
499  }
500 
501  $this->emitFunctionAfterSignal(__FUNCTION__);
502  }
503 
509  public function doProcessData()
510  {
511  $out = $this->doSave
512  || isset($_POST['_savedok'])
513  || isset($_POST['_saveandclosedok'])
514  || isset($_POST['_savedokview'])
515  || isset($_POST['_savedoknew'])
516  || isset($_POST['_translation_savedok_x'])
517  || isset($_POST['_translation_savedokclear_x']);
518  return $out;
519  }
520 
526  public function processData()
527  {
528  $beUser = $this->getBackendUser();
529  // GPvars specifically for processing:
530  $control = GeneralUtility::_GP('control');
531  $this->data = GeneralUtility::_GP('data');
532  $this->cmd = GeneralUtility::_GP('cmd');
533  $this->mirror = GeneralUtility::_GP('mirror');
534  $this->cacheCmd = GeneralUtility::_GP('cacheCmd');
535  $this->redirect = GeneralUtility::_GP('redirect');
536  $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId');
537  $this->vC = GeneralUtility::_GP('vC');
538  // See tce_db.php for relevate options here:
539  // Only options related to $this->data submission are included here.
541  $tce = GeneralUtility::makeInstance(DataHandler::class);
542  $tce->stripslashes_values = false;
543 
544  if (!empty($control)) {
545  $tce->setControl($control);
546  }
547  if (isset($_POST['_translation_savedok_x'])) {
548  $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
549  }
550  if (isset($_POST['_translation_savedokclear_x'])) {
551  $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
552  $tce->updateModeL10NdiffDataClear = true;
553  }
554  // Setting default values specific for the user:
555  $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults');
556  if (is_array($TCAdefaultOverride)) {
557  $tce->setDefaultsFromUserTS($TCAdefaultOverride);
558  }
559  // Setting internal vars:
560  if ($beUser->uc['neverHideAtCopy']) {
561  $tce->neverHideAtCopy = 1;
562  }
563  // Loading TCEmain with data:
564  $tce->start($this->data, $this->cmd);
565  if (is_array($this->mirror)) {
566  $tce->setMirror($this->mirror);
567  }
568  // Perform the saving operation with TCEmain:
569  $tce->process_uploads($_FILES);
570  $tce->process_datamap();
571  $tce->process_cmdmap();
572  // If pages are being edited, we set an instruction about updating the page tree after this operation.
573  if ($tce->pagetreeNeedsRefresh
574  && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
575  ) {
576  BackendUtility::setUpdateSignal('updatePageTree');
577  }
578  // If there was saved any new items, load them:
579  if (!empty($tce->substNEWwithIDs_table)) {
580  // save the expanded/collapsed states for new inline records, if any
581  FormEngineUtility::updateInlineView($this->uc, $tce);
582  $newEditConf = [];
583  foreach ($this->editconf as $tableName => $tableCmds) {
584  $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
585  if (!empty($keys)) {
586  foreach ($keys as $key) {
587  $editId = $tce->substNEWwithIDs[$key];
588  // Check if the $editId isn't a child record of an IRRE action
589  if (!(is_array($tce->newRelatedIDs[$tableName])
590  && in_array($editId, $tce->newRelatedIDs[$tableName]))
591  ) {
592  // Translate new id to the workspace version:
594  $beUser->workspace,
595  $tableName,
596  $editId,
597  'uid'
598  )) {
599  $editId = $versionRec['uid'];
600  }
601  $newEditConf[$tableName][$editId] = 'edit';
602  }
603  // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
604  // these records!
605  if ($tableName == 'pages'
606  && $this->retUrl != BackendUtility::getModuleUrl('dummy')
607  && $this->returnNewPageId
608  ) {
609  $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
610  }
611  }
612  } else {
613  $newEditConf[$tableName] = $tableCmds;
614  }
615  }
616  // Resetting editconf if newEditConf has values:
617  if (!empty($newEditConf)) {
618  $this->editconf = $newEditConf;
619  }
620  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
621  $this->R_URL_getvars['edit'] = $this->editconf;
622  // Unsetting default values since we don't need them anymore.
623  unset($this->R_URL_getvars['defVals']);
624  // Re-compile the store* values since editconf changed...
625  $this->compileStoreDat();
626  }
627  // See if any records was auto-created as new versions?
628  if (!empty($tce->autoVersionIdMap)) {
629  $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
630  }
631  // If a document is saved and a new one is created right after.
632  if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
633  $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT);
634  // Finding the current table:
635  reset($this->editconf);
636  $nTable = key($this->editconf);
637  // Finding the first id, getting the records pid+uid
638  reset($this->editconf[$nTable]);
639  $nUid = key($this->editconf[$nTable]);
640  $recordFields = 'pid,uid';
641  if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
642  $recordFields .= ',t3ver_oid';
643  }
644  $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
645  // Determine insertion mode ('top' is self-explaining,
646  // otherwise new elements are inserted after one using a negative uid)
647  $insertRecordOnTop = ($this->getNewIconMode($nTable) == 'top');
648  // Setting a blank editconf array for a new record:
649  $this->editconf = [];
650  // Determine related page ID for regular live context
651  if ($nRec['pid'] != -1) {
652  if ($insertRecordOnTop) {
653  $relatedPageId = $nRec['pid'];
654  } else {
655  $relatedPageId = -$nRec['uid'];
656  }
657  // Determine related page ID for workspace context
658  } else {
659  if ($insertRecordOnTop) {
660  // Fetch live version of workspace version since the pid value is always -1 in workspaces
661  $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
662  $relatedPageId = $liveRecord['pid'];
663  } else {
664  // Use uid of live version of workspace version
665  $relatedPageId = -$nRec['t3ver_oid'];
666  }
667  }
668  $this->editconf[$nTable][$relatedPageId] = 'new';
669  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
670  $this->R_URL_getvars['edit'] = $this->editconf;
671  // Re-compile the store* values since editconf changed...
672  $this->compileStoreDat();
673  }
674  // If a preview is requested
675  if (isset($_POST['_savedokview'])) {
676  // Get the first table and id of the data array from DataHandler
677  $table = reset(array_keys($this->data));
678  $id = reset(array_keys($this->data[$table]));
680  $id = $tce->substNEWwithIDs[$id];
681  }
682  // Store this information for later use
683  $this->previewData['table'] = $table;
684  $this->previewData['id'] = $id;
685  }
686  $tce->printLogErrorMessages(isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars));
687  // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
688  // because if not, we just get that element re-listed as new. And we don't want that!
689  if ((int)$this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
690  || isset($_POST['_saveandclosedok'])
691  || isset($_POST['_translation_savedok_x'])
692  ) {
693  $this->closeDocument(abs($this->closeDoc));
694  }
695  }
696 
702  public function init()
703  {
704  $beUser = $this->getBackendUser();
705  // Setting more GPvars:
706  $this->popViewId = GeneralUtility::_GP('popViewId');
707  $this->popViewId_addParams = GeneralUtility::_GP('popViewId_addParams');
708  $this->viewUrl = GeneralUtility::_GP('viewUrl');
709  $this->editRegularContentFromId = GeneralUtility::_GP('editRegularContentFromId');
710  $this->recTitle = GeneralUtility::_GP('recTitle');
711  $this->noView = GeneralUtility::_GP('noView');
712  $this->perms_clause = $beUser->getPagePermsClause(1);
713  // Set other internal variables:
714  $this->R_URL_getvars['returnUrl'] = $this->retUrl;
715  $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl(
716  '',
717  $this->R_URL_getvars
718  ), '&');
719  // Setting virtual document name
720  $this->MCONF['name'] = 'xMOD_alt_doc.php';
721 
722  // Create an instance of the document template object
723  $this->doc = $GLOBALS['TBE_TEMPLATE'];
724  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
725  $pageRenderer->addInlineLanguageLabelFile('EXT:lang/locallang_alt_doc.xlf');
726  // override the default jumpToUrl
727  $this->moduleTemplate->addJavaScriptCode(
728  'jumpToUrl',
729  '
730  function jumpToUrl(URL,formEl) {
731  if (!TBE_EDITOR.isFormChanged()) {
732  window.location.href = URL;
733  } else if (formEl && formEl.type=="checkbox") {
734  formEl.checked = formEl.checked ? 0 : 1;
735  }
736  }
737 '
738  );
739  // define the window size of the element browser
740  $popupWindowWidth = 700;
741  $popupWindowHeight = 750;
742  $popupWindowSize = trim($beUser->getTSConfigVal('options.popupWindowSize'));
743  if (!empty($popupWindowSize)) {
744  list($popupWindowWidth, $popupWindowHeight) = GeneralUtility::intExplode('x', $popupWindowSize);
745  }
746  $t3Configuration = [
747  'PopupWindow' => [
748  'width' => $popupWindowWidth,
749  'height' => $popupWindowHeight
750  ]
751  ];
752 
753  if (ExtensionManagementUtility::isLoaded('feedit') && (int)GeneralUtility::_GP('feEdit') === 1) {
754  // We have to load some locallang strings and push them into TYPO3.LLL if this request was
755  // triggered by feedit. Originally, this object is fed by BackendController which is not
756  // called here. This block of code is intended to be removed at a later point again.
757  $lang = $this->getLanguageService();
758  $coreLabels = [
759  'csh_tooltip_loading' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:csh_tooltip_loading')
760  ];
761  $generatedLabels = [];
762  $generatedLabels['core'] = $coreLabels;
763  $code = 'TYPO3.LLL = ' . json_encode($generatedLabels) . ';';
764  $filePath = 'typo3temp/Language/Backend-' . sha1($code) . '.js';
765  if (!file_exists(PATH_site . $filePath)) {
766  // writeFileToTypo3tempDir() returns NULL on success (please double-read!)
767  $error = GeneralUtility::writeFileToTypo3tempDir(PATH_site . $filePath, $code);
768  if ($error !== null) {
769  throw new \RuntimeException('Locallang JS file could not be written to ' . $filePath . '. Reason: ' . $error, 1446118286);
770  }
771  }
772  $pageRenderer->addJsFile('../' . $filePath);
773 
774  // define the window size of the popups within the RTE
775  $rtePopupWindowSize = trim($beUser->getTSConfigVal('options.rte.popupWindowSize'));
776  if (!empty($rtePopupWindowSize)) {
777  list($rtePopupWindowWidth, $rtePopupWindowHeight) = GeneralUtility::trimExplode('x', $rtePopupWindowSize);
778  }
779  $rtePopupWindowWidth = !empty($rtePopupWindowWidth) ? (int)$rtePopupWindowWidth : ($popupWindowWidth-200);
780  $rtePopupWindowHeight = !empty($rtePopupWindowHeight) ? (int)$rtePopupWindowHeight : ($popupWindowHeight-250);
781  $t3Configuration['RTEPopupWindow'] = [
782  'width' => $rtePopupWindowWidth,
783  'height' => $rtePopupWindowHeight
784  ];
785  }
786 
787  $javascript = '
788  TYPO3.configuration = ' . json_encode($t3Configuration) . ';
789  // Object: TS:
790  // passwordDummy and decimalSign are used by tbe_editor.js and have to be declared here as
791  // TS object overwrites the object declared in tbe_editor.js
792  function typoSetup() { //
793  this.uniqueID = "";
794  this.passwordDummy = "********";
795  this.PATH_typo3 = "";
796  this.decimalSign = ".";
797  }
798  var TS = new typoSetup();
799 
800  // Info view:
801  function launchView(table,uid,bP) { //
802  var backPath= bP ? bP : "";
803  var thePreviewWindow = window.open(
804  backPath+' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
805  "ShowItem" + TS.uniqueID,
806  "height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
807  );
808  if (thePreviewWindow && thePreviewWindow.focus) {
809  thePreviewWindow.focus();
810  }
811  }
812  function deleteRecord(table,id,url) { //
813  window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&vC=' . $beUser->veriCode() . '&prErr=1&uPT=1";
814  }
815  ';
816 
817  $previewCode = isset($_POST['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '';
818  $this->moduleTemplate->addJavaScriptCode(
819  'PreviewCode',
820  $javascript . $previewCode
821  );
822  // Setting up the context sensitive menu:
823  $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
824 
825  $this->emitFunctionAfterSignal(__FUNCTION__);
826  }
827 
831  protected function generatePreviewCode()
832  {
833  $table = $this->previewData['table'];
834  $recordId = $this->previewData['id'];
835 
836  if ($table === 'pages') {
837  $currentPageId = $recordId;
838  } else {
839  $currentPageId = MathUtility::convertToPositiveInteger($this->popViewId);
840  }
841 
842  $pageTsConfig = BackendUtility::getPagesTSconfig($currentPageId);
843  $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
844  ? $pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
845  : [];
846 
847  $recordArray = BackendUtility::getRecord($table, $recordId);
848 
849  // find the right preview page id
850  $previewPageId = 0;
851  if (isset($previewConfiguration['previewPageId'])) {
852  $previewPageId = $previewConfiguration['previewPageId'];
853  }
854  // if no preview page was configured
855  if (!$previewPageId) {
856  $rootPageData = null;
857  $rootLine = BackendUtility::BEgetRootLine($currentPageId);
858  $currentPage = reset($rootLine);
859  // Allow all doktypes below 200
860  // This makes custom doktype work as well with opening a frontend page.
861  if ((int)$currentPage['doktype'] <= PageRepository::DOKTYPE_SPACER) {
862  // try the current page
863  $previewPageId = $currentPageId;
864  } else {
865  // or search for the root page
866  foreach ($rootLine as $page) {
867  if ($page['is_siteroot']) {
868  $rootPageData = $page;
869  break;
870  }
871  }
872  $previewPageId = isset($rootPageData)
873  ? (int)$rootPageData['uid']
874  : $currentPageId;
875  }
876  }
877 
878  $linkParameters = [
879  'no_cache' => 1,
880  ];
881 
882  // language handling
883  $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
884  ? $GLOBALS['TCA'][$table]['ctrl']['languageField']
885  : '';
886  if ($languageField && !empty($recordArray[$languageField])) {
887  $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
888  ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
889  : '';
890  if ($l18nPointer && !empty($recordArray[$l18nPointer])
891  && isset($previewConfiguration['useDefaultLanguageRecord'])
892  && !$previewConfiguration['useDefaultLanguageRecord']
893  ) {
894  // use parent record
895  $recordId = $recordArray[$l18nPointer];
896  }
897  $linkParameters['L'] = $recordArray[$languageField];
898  }
899 
900  // map record data to GET parameters
901  if (isset($previewConfiguration['fieldToParameterMap.'])) {
902  foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
903  $value = $recordArray[$field];
904  if ($field === 'uid') {
905  $value = $recordId;
906  }
907  $linkParameters[$parameterName] = $value;
908  }
909  }
910 
911  // add/override parameters by configuration
912  if (isset($previewConfiguration['additionalGetParameters.'])) {
913  $additionalGetParameters = [];
915  $additionalGetParameters,
916  $previewConfiguration['additionalGetParameters.']
917  );
918  $linkParameters = array_replace($linkParameters, $additionalGetParameters);
919  }
920 
921  $this->popViewId = $previewPageId;
922  $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true);
923 
924  $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
925  return '
926  if (window.opener) {
927  '
929  $this->popViewId,
930  '',
931  $previewPageRootline,
932  '',
933  $this->viewUrl,
934  $this->popViewId_addParams,
935  false
936  )
937  . '
938  } else {
939  '
941  $this->popViewId,
942  '',
943  $previewPageRootline,
944  '',
945  $this->viewUrl,
946  $this->popViewId_addParams
947  )
948  . '
949  }';
950  }
951 
961  protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
962  {
963  foreach ($typoScript as $key => $value) {
964  if (is_array($value)) {
965  $key = rtrim($key, '.');
966  $parameters[$key] = [];
967  $this->parseAdditionalGetParameters($parameters[$key], $value);
968  } else {
969  $parameters[$key] = $value;
970  }
971  }
972  }
973 
979  public function main()
980  {
981  $body = '';
982  // Begin edit:
983  if (is_array($this->editconf)) {
985  $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
986 
987  if ($this->editRegularContentFromId) {
988  $this->editRegularContentFromId();
989  }
990  // Creating the editing form, wrap it with buttons, document selector etc.
991  $editForm = $this->makeEditForm();
992  if ($editForm) {
993  $this->firstEl = reset($this->elementsData);
994  // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
995  if (($this->docDat[1] !== $this->storeUrlMd5
996  || !isset($this->docHandler[$this->storeUrlMd5]))
997  && !$this->dontStoreDocumentRef
998  ) {
999  $this->docHandler[$this->storeUrlMd5] = [
1004  ];
1005  $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->storeUrlMd5]);
1006  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1007  }
1008  // Module configuration
1009  $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig(
1010  $this->viewId,
1011  'mod.xMOD_alt_doc'
1012  ) : [];
1013  $body = $this->formResultCompiler->JStop();
1014  $body .= $this->compileForm($editForm);
1015  $body .= $this->formResultCompiler->printNeededJSFunctions();
1016  $body .= '</form>';
1017  }
1018  }
1019  // Access check...
1020  // The page will show only if there is a valid page and if this page may be viewed by the user
1021  $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
1022  if ($this->pageinfo) {
1023  $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
1024  }
1025  // Setting up the buttons and markers for docheader
1026  $this->getButtons();
1027  $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
1028  $this->moduleTemplate->setContent($body);
1029  }
1030 
1037  public function printContent()
1038  {
1040  echo $this->content;
1041  }
1042 
1043  /***************************
1044  *
1045  * Sub-content functions, rendering specific parts of the module content.
1046  *
1047  ***************************/
1053  public function makeEditForm()
1054  {
1055  // Initialize variables:
1056  $this->elementsData = [];
1057  $this->errorC = 0;
1058  $this->newC = 0;
1059  $editForm = '';
1060  $trData = null;
1061  $beUser = $this->getBackendUser();
1062  // Traverse the GPvar edit array
1063  // Tables:
1064  foreach ($this->editconf as $table => $conf) {
1065  if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
1066  // Traverse the keys/comments of each table (keys can be a commalist of uids)
1067  foreach ($conf as $cKey => $command) {
1068  if ($command == 'edit' || $command == 'new') {
1069  // Get the ids:
1070  $ids = GeneralUtility::trimExplode(',', $cKey, true);
1071  // Traverse the ids:
1072  foreach ($ids as $theUid) {
1073  // Don't save this document title in the document selector if the document is new.
1074  if ($command === 'new') {
1075  $this->dontStoreDocumentRef = 1;
1076  }
1077 
1079  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1081  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
1083  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1084 
1085  try {
1086  // Reset viewId - it should hold data of last entry only
1087  $this->viewId = 0;
1088  $this->viewId_addParams = '';
1089 
1090  $formDataCompilerInput = [
1091  'tableName' => $table,
1092  'vanillaUid' => (int)$theUid,
1093  'command' => $command,
1094  'returnUrl' => $this->R_URI,
1095  ];
1096  if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1097  $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1098  }
1099 
1100  $formData = $formDataCompiler->compile($formDataCompilerInput);
1101 
1102  // Set this->viewId if possible
1103  if ($command === 'new'
1104  && $table !== 'pages'
1105  && !empty($formData['parentPageRow']['uid'])
1106  ) {
1107  $this->viewId = $formData['parentPageRow']['uid'];
1108  } else {
1109  if ($table == 'pages') {
1110  $this->viewId = $formData['databaseRow']['uid'];
1111  } elseif (!empty($formData['parentPageRow']['uid'])) {
1112  $this->viewId = $formData['parentPageRow']['uid'];
1113  // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1114  if (!empty($formData['processedTca']['ctrl']['languageField'])
1115  && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1116  && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1117  ) {
1118  $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1119  }
1120  }
1121  }
1122 
1123  // Determine if delete button can be shown
1124  $deleteAccess = false;
1125  if ($command === 'edit') {
1126  $permission = $formData['userPermissionOnPage'];
1127  if ($formData['tableName'] === 'pages') {
1128  $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1129  } else {
1130  $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1131  }
1132  }
1133 
1134  // Display "is-locked" message:
1135  if ($command === 'edit') {
1136  $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1137  if ($lockInfo) {
1139  $flashMessage = GeneralUtility::makeInstance(
1140  FlashMessage::class,
1141  $lockInfo['msg'],
1142  '',
1144  );
1146  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1148  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1149  $defaultFlashMessageQueue->enqueue($flashMessage);
1150  }
1151  }
1152 
1153  // Record title
1154  if (!$this->storeTitle) {
1155  $this->storeTitle = $this->recTitle
1156  ? htmlspecialchars($this->recTitle)
1157  : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
1158  }
1159 
1160  $this->elementsData[] = [
1161  'table' => $table,
1162  'uid' => $formData['databaseRow']['uid'],
1163  'pid' => $formData['databaseRow']['pid'],
1164  'cmd' => $command,
1165  'deleteAccess' => $deleteAccess
1166  ];
1167 
1168  if ($command !== 'new') {
1169  BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1170  }
1171 
1172  // Set list if only specific fields should be rendered. This will trigger
1173  // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1174  if ($this->columnsOnly) {
1175  if (is_array($this->columnsOnly)) {
1176  $formData['fieldListToRender'] = $this->columnsOnly[$table];
1177  } else {
1178  $formData['fieldListToRender'] = $this->columnsOnly;
1179  }
1180  }
1181 
1182  $formData['renderType'] = 'outerWrapContainer';
1183  $formResult = $nodeFactory->create($formData)->render();
1184 
1185  $html = $formResult['html'];
1186 
1187  $formResult['html'] = '';
1188  $formResult['doSaveFieldName'] = 'doSave';
1189 
1190  // @todo: Put all the stuff into FormEngine as final "compiler" class
1191  // @todo: This is done here for now to not rewrite JStop()
1192  // @todo: and printNeededJSFunctions() now
1193  $this->formResultCompiler->mergeResult($formResult);
1194 
1195  // Seems the pid is set as hidden field (again) at end?!
1196  if ($command == 'new') {
1197  // @todo: looks ugly
1198  $html .= LF
1199  . '<input type="hidden"'
1200  . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1201  . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1202  $this->newC++;
1203  }
1204 
1205  $editForm .= $html;
1206  } catch (AccessDeniedException $e) {
1207  $this->errorC++;
1208  // Try to fetch error message from "recordInternals" be user object
1209  // @todo: This construct should be logged and localized and de-uglified
1210  $message = $beUser->errorMsg;
1211  if (empty($message)) {
1212  // Create message from exception.
1213  $message = $e->getMessage() . ' ' . $e->getCode();
1214  }
1215  $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', true)
1216  . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1217  }
1218  } // End of for each uid
1219  }
1220  }
1221  }
1222  }
1223  return $editForm;
1224  }
1225 
1231  protected function getButtons()
1232  {
1233  $lang = $this->getLanguageService();
1234  // Render SAVE type buttons:
1235  // The action of each button is decided by its name attribute. (See doProcessData())
1236  $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1237  if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1238  $saveSplitButton = $buttonBar->makeSplitButton();
1239  // SAVE button:
1240  $saveButton = $buttonBar->makeInputButton()
1241  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc'))
1242  ->setName('_savedok')
1243  ->setValue('1')
1244  ->setForm('EditDocumentController')
1245  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
1246  $saveSplitButton->addItem($saveButton, true);
1247 
1248  // SAVE / VIEW button:
1249  if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1250  $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1251  if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1252  $excludeDokTypes = GeneralUtility::intExplode(
1253  ',',
1254  $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1255  true
1256  );
1257  } else {
1258  // exclude sysfolders, spacers and recycler by default
1259  $excludeDokTypes = [
1263  ];
1264  }
1265  if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1266  || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1267  ) {
1268  $saveAndOpenButton = $buttonBar->makeInputButton()
1269  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDocShow'))
1270  ->setName('_savedokview')
1271  ->setValue('1')
1272  ->setForm('EditDocumentController')
1273  ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1274  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1275  'actions-document-save-view',
1277  ));
1278  $saveSplitButton->addItem($saveAndOpenButton);
1279  }
1280  }
1281  // SAVE / NEW button:
1282  if (count($this->elementsData) === 1 && $this->getNewIconMode($this->firstEl['table'])) {
1283  $saveAndNewButton = $buttonBar->makeInputButton()
1284  ->setName('_savedoknew')
1285  ->setClasses('t3js-editform-submitButton')
1286  ->setValue('1')
1287  ->setForm('EditDocumentController')
1288  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveNewDoc'))
1289  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1290  'actions-document-save-new',
1292  ));
1293  $saveSplitButton->addItem($saveAndNewButton);
1294  }
1295  // SAVE / CLOSE
1296  $saveAndCloseButton = $buttonBar->makeInputButton()
1297  ->setName('_saveandclosedok')
1298  ->setClasses('t3js-editform-submitButton')
1299  ->setValue('1')
1300  ->setForm('EditDocumentController')
1301  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc'))
1302  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1303  'actions-document-save-close',
1305  ));
1306  $saveSplitButton->addItem($saveAndCloseButton);
1307  // FINISH TRANSLATION / SAVE / CLOSE
1308  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1309  $saveTranslationButton = $buttonBar->makeInputButton()
1310  ->setName('_translation_savedok')
1311  ->setValue('1')
1312  ->setForm('EditDocumentController')
1313  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDoc'))
1314  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1315  'actions-document-save-cleartranslationcache',
1317  ));
1318  $saveSplitButton->addItem($saveTranslationButton);
1319  $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1320  ->setName('_translation_savedokclear')
1321  ->setValue('1')
1322  ->setForm('EditDocumentController')
1323  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDocClear'))
1324  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1325  'actions-document-save-cleartranslationcache',
1327  ));
1328  $saveSplitButton->addItem($saveAndClearTranslationButton);
1329  }
1330  $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1331  }
1332  // CLOSE button:
1333  $closeButton = $buttonBar->makeLinkButton()
1334  ->setHref('#')
1335  ->setClasses('t3js-editform-close')
1336  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc'))
1337  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1338  'actions-document-close',
1340  ));
1341  $buttonBar->addButton($closeButton);
1342  // DELETE + UNDO buttons:
1343  if (!$this->errorC
1344  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1345  && count($this->elementsData) === 1
1346  ) {
1347  if ($this->firstEl['cmd'] !== 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1348  // Delete:
1349  if ($this->firstEl['deleteAccess']
1350  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1351  && !$this->getNewIconMode($this->firstEl['table'], 'disableDelete')
1352  ) {
1354  if ($this->firstEl['table'] === 'pages') {
1355  parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
1356  if (isset($queryParams['M'])
1357  && isset($queryParams['id'])
1358  && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1359  ) {
1360  // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1361  // tree from the outside to be able to mark the pid as active
1362  $returnUrl = BackendUtility::getModuleUrl($queryParams['M'], ['id' => 0]);
1363  }
1364  }
1365  $deleteButton = $buttonBar->makeLinkButton()
1366  ->setHref('#')
1367  ->setClasses('t3js-editform-delete-record')
1368  ->setTitle($lang->getLL('deleteItem'))
1369  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1370  'actions-edit-delete',
1372  ))
1373  ->setDataAttributes([
1374  'return-url' => $returnUrl,
1375  'uid' => $this->firstEl['uid'],
1376  'table' => $this->firstEl['table']
1377  ]);
1378  $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1379  }
1380  // Undo:
1381  $undoRes = $this->getDatabaseConnection()->exec_SELECTquery(
1382  'tstamp',
1383  'sys_history',
1384  'tablename='
1385  . $this->getDatabaseConnection()->fullQuoteStr($this->firstEl['table'], 'sys_history')
1386  . ' AND recuid='
1387  . (int)$this->firstEl['uid'],
1388  '',
1389  'tstamp DESC',
1390  '1'
1391  );
1392  if ($undoButtonR = $this->getDatabaseConnection()->sql_fetch_assoc($undoRes)) {
1393  $aOnClick = 'window.location.href=' .
1395  BackendUtility::getModuleUrl(
1396  'record_history',
1397  [
1398  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1399  'revert' => 'ALL_FIELDS',
1400  'sumUp' => -1,
1401  'returnUrl' => $this->R_URI,
1402  ]
1403  )
1404  ) . '; return false;';
1405 
1406  $undoButton = $buttonBar->makeLinkButton()
1407  ->setHref('#')
1408  ->setOnClick($aOnClick)
1409  ->setTitle(
1410  sprintf(
1411  $lang->getLL('undoLastChange'),
1413  ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1414  $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')
1415  )
1416  )
1417  )
1418  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1419  'actions-document-history-open',
1421  ));
1422  $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1423  }
1424  if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1425  $aOnClick = 'window.location.href=' .
1427  BackendUtility::getModuleUrl(
1428  'record_history',
1429  [
1430  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1431  'returnUrl' => $this->R_URI,
1432  ]
1433  )
1434  ) . '; return false;';
1435 
1436  $historyButton = $buttonBar->makeLinkButton()
1437  ->setHref('#')
1438  ->setOnClick($aOnClick)
1439  ->setTitle('Open history of this record')
1440  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1441  'actions-document-history-open',
1443  ));
1444  $buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1445  }
1446  // If only SOME fields are shown in the form, this will link the user to the FULL form:
1447  if ($this->columnsOnly) {
1448  $columnsOnlyButton = $buttonBar->makeLinkButton()
1449  ->setHref($this->R_URI . '&columnsOnly=')
1450  ->setTitle($lang->getLL('editWholeRecord'))
1451  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1452  'actions-document-open',
1454  ));
1455  $buttonBar->addButton($columnsOnlyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1456  }
1457  }
1458  }
1459  $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1460  $buttonBar->addButton($cshButton);
1461  $this->shortCutLink();
1462  $this->openInNewWindowLink();
1463  }
1464 
1471  public function compileForm($editForm)
1472  {
1473  $formContent = '
1474  <!-- EDITING FORM -->
1475  <form
1476  action="' . htmlspecialchars($this->R_URI) . '"
1477  method="post"
1478  enctype="multipart/form-data"
1479  name="editform"
1480  id="EditDocumentController"
1481  onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1482  ' . $editForm . '
1483 
1484  <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1485  <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1486  if ($this->returnNewPageId) {
1487  $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1488  }
1489  $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1490  if ($this->viewId_addParams) {
1491  $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1492  }
1493  $formContent .= '
1494  <input type="hidden" name="closeDoc" value="0" />
1495  <input type="hidden" name="doSave" value="0" />
1496  <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1497  <input type="hidden" name="_scrollPosition" value="" />';
1498  return $formContent;
1499  }
1500 
1504  public function shortCutLink()
1505  {
1506  if ($this->returnUrl !== GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Html/Close.html')) {
1507  $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1508  $shortCutButton->setModuleName($this->MCONF['name'])
1509  ->setGetVariables([
1510  'returnUrl',
1511  'edit',
1512  'defVals',
1513  'overrideVals',
1514  'columnsOnly',
1515  'returnNewPageId',
1516  'editRegularContentFromId',
1517  'noView']);
1518  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1519  }
1520  }
1521 
1525  public function openInNewWindowLink()
1526  {
1527  $closeUrl = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Html/Close.html');
1528  if ($this->returnUrl !== $closeUrl) {
1529  $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(
1530  ['returnUrl' => PathUtility::getAbsoluteWebPath($closeUrl)]
1531  ))
1532  . ','
1533  . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1534  . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1535  $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1536  ->makeLinkButton()
1537  ->setHref('#')
1538  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow'))
1539  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1540  ->setOnClick($aOnClick);
1541  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
1542  $openInNewWindowButton,
1544  );
1545  }
1546  }
1547 
1548  /***************************
1549  *
1550  * Localization stuff
1551  *
1552  ***************************/
1562  public function languageSwitch($table, $uid, $pid = null)
1563  {
1564  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1565  $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1566  // Table editable and activated for languages?
1567  if ($this->getBackendUser()->check('tables_modify', $table)
1568  && $languageField
1569  && $transOrigPointerField && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
1570  ) {
1571  if (is_null($pid)) {
1572  $row = BackendUtility::getRecord($table, $uid, 'pid');
1573  $pid = $row['pid'];
1574  }
1575  // Get all available languages for the page
1576  $langRows = $this->getLanguages($pid);
1577  // Page available in other languages than default language?
1578  if (is_array($langRows) && count($langRows) > 1) {
1579  $rowsByLang = [];
1580  $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1581  // Get record in current language
1582  $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1583  if (!is_array($rowCurrent)) {
1584  $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1585  }
1586  $currentLanguage = (int)$rowCurrent[$languageField];
1587  // Disabled for records with [all] language!
1588  if ($currentLanguage > -1) {
1589  // Get record in default language if needed
1590  if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1591  $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1592  $table,
1593  $rowCurrent[$transOrigPointerField],
1594  $fetchFields
1595  );
1596  if (!is_array($rowsByLang[0])) {
1597  $rowsByLang[0] = BackendUtility::getRecord(
1598  $table,
1599  $rowCurrent[$transOrigPointerField],
1600  $fetchFields
1601  );
1602  }
1603  } else {
1604  $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1605  }
1606  if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1607  // Get record in other languages to see what's already available
1608  $translations = $this->getDatabaseConnection()->exec_SELECTgetRows(
1609  $fetchFields,
1610  $table,
1611  'pid=' . (int)$pid . ' AND ' . $languageField . '>0' . ' AND ' . $transOrigPointerField . '=' . (int)$rowsByLang[0]['uid'] . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table)
1612  );
1613  foreach ($translations as $row) {
1614  $rowsByLang[$row[$languageField]] = $row;
1615  }
1616  }
1617  $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1618  $languageMenu->setIdentifier('_langSelector');
1619  $languageMenu->setLabel($this->getLanguageService()->sL(
1620  'LLL:EXT:lang/locallang_general.xlf:LGL.language',
1621  true
1622  ));
1623  foreach ($langRows as $lang) {
1624  if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1625  $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.new', true) . ']';
1626  // Create url for creating a localized record
1627  $addOption = true;
1628  if ($newTranslation) {
1629  $redirectUrl = BackendUtility::getModuleUrl('record_edit', [
1630  'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1631  'returnUrl' => $this->retUrl
1632  ]);
1633 
1634  if (array_key_exists(0, $rowsByLang)) {
1636  '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1637  $redirectUrl
1638  );
1639  } else {
1640  $addOption = false;
1641  }
1642  } else {
1643  $href = BackendUtility::getModuleUrl('record_edit', [
1644  'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1645  'returnUrl' => $this->retUrl
1646  ]);
1647  }
1648  if ($addOption) {
1649  $menuItem = $languageMenu->makeMenuItem()
1650  ->setTitle($lang['title'] . $newTranslation)
1651  ->setHref($href);
1652  if ((int)$lang['uid'] === $currentLanguage) {
1653  $menuItem->setActive(true);
1654  }
1655  $languageMenu->addMenuItem($menuItem);
1656  }
1657  }
1658  }
1659  $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1660  }
1661  }
1662  }
1663  }
1664 
1671  public function localizationRedirect($justLocalized)
1672  {
1673  list($table, $orig_uid, $language) = explode(':', $justLocalized);
1674  if ($GLOBALS['TCA'][$table]
1675  && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1676  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1677  ) {
1678  $localizedRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
1679  'uid',
1680  $table,
1681  $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$language . ' AND '
1682  . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$orig_uid
1684  );
1685  if (is_array($localizedRecord)) {
1686  // Create parameters and finally run the classic page module for creating a new page translation
1687  $location = BackendUtility::getModuleUrl('record_edit', [
1688  'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1689  'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1690  ]);
1691  HttpUtility::redirect($location);
1692  }
1693  }
1694  }
1695 
1704  public function getLanguages($id)
1705  {
1706  $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1707  // Fallback non sprite-configuration
1708  if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1709  $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1710  '.gif',
1711  '',
1712  $modSharedTSconfig['properties']['defaultLanguageFlag']
1713  );
1714  }
1715  $languages = [
1716  0 => [
1717  'uid' => 0,
1718  'pid' => 0,
1719  'hidden' => 0,
1720  'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1721  ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1722  : $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage'),
1723  'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1724  ]
1725  ];
1726  $exQ = $this->getBackendUser()->isAdmin() ? '' : ' AND sys_language.hidden=0';
1727  if ($id) {
1728  $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1729  'sys_language.*',
1730  'pages_language_overlay,sys_language',
1731  'pages_language_overlay.sys_language_uid=sys_language.uid
1732  AND pages_language_overlay.pid=' . (int)$id . BackendUtility::deleteClause('pages_language_overlay')
1733  . $exQ,
1734  'pages_language_overlay.sys_language_uid,
1735  sys_language.uid,
1736  sys_language.pid,
1737  sys_language.tstamp,
1738  sys_language.hidden,
1739  sys_language.title,
1740  sys_language.language_isocode,
1741  sys_language.static_lang_isocode,
1742  sys_language.flag',
1743  'sys_language.title'
1744  );
1745  } else {
1746  $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1747  'sys_language.*',
1748  'sys_language',
1749  'sys_language.hidden=0',
1750  '',
1751  'sys_language.title'
1752  );
1753  }
1754  if ($rows) {
1755  foreach ($rows as $row) {
1756  $languages[$row['uid']] = $row;
1757  }
1758  }
1759  return $languages;
1760  }
1761 
1762  /***************************
1763  *
1764  * Other functions
1765  *
1766  ***************************/
1773  public function fixWSversioningInEditConf($mapArray = false)
1774  {
1775  // Traverse the editConf array
1776  if (is_array($this->editconf)) {
1777  // Tables:
1778  foreach ($this->editconf as $table => $conf) {
1779  if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1780  // Traverse the keys/comments of each table (keys can be a commalist of uids)
1781  $newConf = [];
1782  foreach ($conf as $cKey => $cmd) {
1783  if ($cmd == 'edit') {
1784  // Traverse the ids:
1785  $ids = GeneralUtility::trimExplode(',', $cKey, true);
1786  foreach ($ids as $idKey => $theUid) {
1787  if (is_array($mapArray)) {
1788  if ($mapArray[$table][$theUid]) {
1789  $ids[$idKey] = $mapArray[$table][$theUid];
1790  }
1791  } else {
1792  // Default, look for versions in workspace for record:
1793  $calcPRec = $this->getRecordForEdit($table, $theUid);
1794  if (is_array($calcPRec)) {
1795  // Setting UID again if it had changed, eg. due to workspace versioning.
1796  $ids[$idKey] = $calcPRec['uid'];
1797  }
1798  }
1799  }
1800  // Add the possibly manipulated IDs to the new-build newConf array:
1801  $newConf[implode(',', $ids)] = $cmd;
1802  } else {
1803  $newConf[$cKey] = $cmd;
1804  }
1805  }
1806  // Store the new conf array:
1807  $this->editconf[$table] = $newConf;
1808  }
1809  }
1810  }
1811  }
1812 
1820  public function getRecordForEdit($table, $theUid)
1821  {
1822  // Fetch requested record:
1823  $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1824  if (is_array($reqRecord)) {
1825  // If workspace is OFFLINE:
1826  if ($this->getBackendUser()->workspace != 0) {
1827  // Check for versioning support of the table:
1828  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1829  // If the record is already a version of "something" pass it by.
1830  if ($reqRecord['pid'] == -1) {
1831  // (If it turns out not to be a version of the current workspace there will be trouble, but
1832  // that is handled inside TCEmain then and in the interface it would clearly be an error of
1833  // links if the user accesses such a scenario)
1834  return $reqRecord;
1835  } else {
1836  // The input record was online and an offline version must be found or made:
1837  // Look for version of this workspace:
1839  $this->getBackendUser()->workspace,
1840  $table,
1841  $reqRecord['uid'],
1842  'uid,pid,t3ver_oid'
1843  );
1844  return is_array($versionRec) ? $versionRec : $reqRecord;
1845  }
1846  } else {
1847  // This means that editing cannot occur on this record because it was not supporting versioning
1848  // which is required inside an offline workspace.
1849  return false;
1850  }
1851  } else {
1852  // In ONLINE workspace, just return the originally requested record:
1853  return $reqRecord;
1854  }
1855  } else {
1856  // Return FALSE because the table/uid was not found anyway.
1857  return false;
1858  }
1859  }
1860 
1868  public function editRegularContentFromId()
1869  {
1871  $dbConnection = $this->getDatabaseConnection();
1872  $res = $dbConnection->exec_SELECTquery(
1873  'uid',
1874  'tt_content',
1875  'pid=' . (int)$this->editRegularContentFromId . BackendUtility::deleteClause('tt_content')
1876  . BackendUtility::versioningPlaceholderClause('tt_content') . ' AND colPos=0 AND sys_language_uid=0',
1877  '',
1878  'sorting'
1879  );
1880  if ($dbConnection->sql_num_rows($res)) {
1881  $ecUids = [];
1882  while ($ecRec = $dbConnection->sql_fetch_assoc($res)) {
1883  $ecUids[] = $ecRec['uid'];
1884  }
1885  $this->editconf['tt_content'][implode(',', $ecUids)] = 'edit';
1886  }
1887  $dbConnection->sql_free_result($res);
1888  }
1889 
1896  public function compileStoreDat()
1897  {
1899  'edit,defVals,overrideVals,columnsOnly,noView,editRegularContentFromId,workspace',
1900  $this->R_URL_getvars
1901  );
1902  $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1903  $this->storeUrlMd5 = md5($this->storeUrl);
1904  }
1905 
1914  public function getNewIconMode($table, $key = 'saveDocNew')
1915  {
1916  $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1917  $output = trim(isset($TSconfig['properties'][$table]) ? $TSconfig['properties'][$table] : $TSconfig['value']);
1918  return $output;
1919  }
1920 
1932  public function closeDocument($mode = self::DOCUMENT_CLOSE_MODE_DEFAULT)
1933  {
1934  $mode = (int)$mode;
1935  // If current document is found in docHandler,
1936  // then unset it, possibly unset it ALL and finally, write it to the session data
1937  if (isset($this->docHandler[$this->storeUrlMd5])) {
1938  // add the closing document to the recent documents
1939  $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1940  if (!is_array($recentDocs)) {
1941  $recentDocs = [];
1942  }
1943  $closedDoc = $this->docHandler[$this->storeUrlMd5];
1944  $recentDocs = array_merge([$this->storeUrlMd5 => $closedDoc], $recentDocs);
1945  if (count($recentDocs) > 8) {
1946  $recentDocs = array_slice($recentDocs, 0, 8);
1947  }
1948  // remove it from the list of the open documents
1949  unset($this->docHandler[$this->storeUrlMd5]);
1950  if ($mode === self::DOCUMENT_CLOSE_MODE_CLEAR_ALL) {
1951  $recentDocs = array_merge($this->docHandler, $recentDocs);
1952  $this->docHandler = [];
1953  }
1954  $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1955  $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->docDat[1]]);
1956  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1957  }
1958  if ($mode !== self::DOCUMENT_CLOSE_MODE_NO_REDIRECT) {
1959  // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
1960  // other scripts, like wizard_add, to know which records was created or so...)
1961  if ($this->returnEditConf && $this->retUrl != BackendUtility::getModuleUrl('dummy')) {
1962  $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
1963  }
1964 
1965  // If mode is NOT set (means 0) OR set to 1, then make a header location redirect to $this->retUrl
1966  if ($mode === self::DOCUMENT_CLOSE_MODE_DEFAULT || $mode === self::DOCUMENT_CLOSE_MODE_REDIRECT) {
1967  HttpUtility::redirect($this->retUrl);
1968  } else {
1969  $this->setDocument('', $this->retUrl);
1970  }
1971  }
1972  }
1973 
1983  public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
1984  {
1985  if ($retUrl === '') {
1986  return;
1987  }
1988  if (!$this->modTSconfig['properties']['disableDocSelector']
1989  && is_array($this->docHandler)
1990  && !empty($this->docHandler)
1991  ) {
1992  if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
1993  $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
1994  } else {
1995  $setupArr = reset($this->docHandler);
1996  }
1997  if ($setupArr[2]) {
1998  $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
1999  $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
2000  }
2001  }
2003  }
2004 
2012  public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
2013  {
2015 
2016  // Preprocessing, storing data if submitted to
2017  $this->preInit();
2018 
2019  // Checks, if a save button has been clicked (or the doSave variable is sent)
2020  if ($this->doProcessData()) {
2021  $this->processData();
2022  }
2023 
2024  $this->init();
2025  $this->main();
2026 
2027  $response->getBody()->write($this->moduleTemplate->renderContent());
2028  return $response;
2029  }
2030 
2034  protected function getBackendUser()
2035  {
2036  return $GLOBALS['BE_USER'];
2037  }
2038 
2044  protected function getLanguageService()
2045  {
2046  return $GLOBALS['LANG'];
2047  }
2048 
2054  protected function getDatabaseConnection()
2055  {
2056  return $GLOBALS['TYPO3_DB'];
2057  }
2058 }
static compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt=true)
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static readPageAccess($id, $perms_clause)
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
mainAction(ServerRequestInterface $request, ResponseInterface $response)
parseAdditionalGetParameters(array &$parameters, array $typoScript)
static writeFileToTypo3tempDir($filepath, $content)
static getAbsoluteWebPath($targetPath)
Definition: PathUtility.php:40
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
static lockRecords($table='', $uid=0, $pid=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
static linkThisScript(array $getParams=[])
static calcAge($seconds, $labels=' min|hrs|days|yrs|min|hour|day|year')
setDocument($currentDocFromHandlerMD5='', $retUrl='')
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
closeDocument($mode=self::DOCUMENT_CLOSE_MODE_DEFAULT)
static setUpdateSignal($set='', $params='')
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
Definition: HttpUtility.php:76
static viewOnClick($pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
$uid
Definition: server.php:38
static getFileAbsFileName($filename, $onlyRelative=true, $relToTYPO3_mainDir=false)
static convertToPositiveInteger($theInt)
Definition: MathUtility.php:55
static getLiveVersionOfRecord($table, $uid, $fields=' *')
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static getLinkToDataHandlerAction($parameters, $redirectUrl='')
static deleteClause($table, $tableAlias='')