TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
EditDocumentController.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Backend\Controller;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
46 
52 {
54  const DOCUMENT_CLOSE_MODE_REDIRECT = 1; // works like DOCUMENT_CLOSE_MODE_DEFAULT
57 
65  public $editconf;
66 
74  public $columnsOnly;
75 
82  public $defVals;
83 
90  public $overrideVals;
91 
98  public $returnUrl;
99 
105  public $closeDoc;
106 
114  public $doSave;
115 
121  public $data;
122 
126  public $cmd;
127 
131  public $mirror;
132 
138  public $cacheCmd;
139 
145  public $redirect;
146 
154 
158  public $vC;
159 
165  public $uc;
166 
172  public $popViewId;
173 
180 
186  public $viewUrl;
187 
193  public $recTitle;
194 
200  public $noView;
201 
206 
214 
220  protected $workspace;
221 
227  public $doc;
228 
234  public $template;
235 
241  public $content;
242 
250  public $retUrl;
251 
259  public $R_URL_parts;
260 
268 
274  public $R_URI;
275 
279  public $MCONF;
280 
284  public $pageinfo;
285 
292  public $storeTitle = '';
293 
300  public $storeArray;
301 
307  public $storeUrl;
308 
314  public $storeUrlMd5;
315 
321  public $docDat;
322 
331  public $docHandler;
332 
339 
345  public $firstEl;
346 
352  public $errorC;
353 
359  public $newC;
360 
367  public $viewId;
368 
375 
381  public $modTSconfig;
382 
387 
394 
399 
405  protected $previewData = [];
406 
410  public function __construct()
411  {
412  parent::__construct();
413  $GLOBALS['SOBE'] = $this;
414  $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf');
415  }
416 
422  protected function getSignalSlotDispatcher()
423  {
424  if (!isset($this->signalSlotDispatcher)) {
425  $this->signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
426  }
428  }
429 
435  protected function emitFunctionAfterSignal($signalName)
436  {
437  $this->getSignalSlotDispatcher()->dispatch(__CLASS__, $signalName . 'After', [$this]);
438  }
439 
445  public function preInit()
446  {
447  if (GeneralUtility::_GP('justLocalized')) {
448  $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
449  }
450  // Setting GPvars:
451  $this->editconf = GeneralUtility::_GP('edit');
452  $this->defVals = GeneralUtility::_GP('defVals');
453  $this->overrideVals = GeneralUtility::_GP('overrideVals');
454  $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
455  $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
456  $this->closeDoc = (int)GeneralUtility::_GP('closeDoc');
457  $this->doSave = GeneralUtility::_GP('doSave');
458  $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
459  $this->localizationMode = GeneralUtility::_GP('localizationMode');
460  $this->workspace = GeneralUtility::_GP('workspace');
461  $this->uc = GeneralUtility::_GP('uc');
462  // Setting override values as default if defVals does not exist.
463  if (!is_array($this->defVals) && is_array($this->overrideVals)) {
464  $this->defVals = $this->overrideVals;
465  }
466  // Setting return URL
467  $this->retUrl = $this->returnUrl ?: BackendUtility::getModuleUrl('dummy');
468  // Fix $this->editconf if versioning applies to any of the records
469  $this->fixWSversioningInEditConf();
470  // Make R_URL (request url) based on input GETvars:
471  $this->R_URL_parts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
472  $this->R_URL_getvars = GeneralUtility::_GET();
473  $this->R_URL_getvars['edit'] = $this->editconf;
474  // MAKE url for storing
475  $this->compileStoreDat();
476  // Get session data for the module:
477  $this->docDat = $this->getBackendUser()->getModuleData('FormEngine', 'ses');
478  $this->docHandler = $this->docDat[0];
479  // If a request for closing the document has been sent, act accordingly:
480  if ((int)$this->closeDoc > self::DOCUMENT_CLOSE_MODE_DEFAULT) {
481  $this->closeDocument($this->closeDoc);
482  }
483  // If NO vars are sent to the script, try to read first document:
484  // Added !is_array($this->editconf) because editConf must not be set either.
485  // Anyways I can't figure out when this situation here will apply...
486  if (is_array($this->R_URL_getvars) && count($this->R_URL_getvars) < 2 && !is_array($this->editconf)) {
487  $this->setDocument($this->docDat[1]);
488  }
489 
490  // Sets a temporary workspace, this request is based on
491  if ($this->workspace !== null) {
492  $this->getBackendUser()->setTemporaryWorkspace($this->workspace);
493  }
494 
495  $this->emitFunctionAfterSignal(__FUNCTION__);
496  }
497 
503  public function doProcessData()
504  {
505  $out = $this->doSave
506  || isset($_POST['_savedok'])
507  || isset($_POST['_saveandclosedok'])
508  || isset($_POST['_savedokview'])
509  || isset($_POST['_savedoknew'])
510  || isset($_POST['_translation_savedok_x'])
511  || isset($_POST['_translation_savedokclear_x']);
512  return $out;
513  }
514 
520  public function processData()
521  {
522  $beUser = $this->getBackendUser();
523  // GPvars specifically for processing:
524  $control = GeneralUtility::_GP('control');
525  $this->data = GeneralUtility::_GP('data');
526  $this->cmd = GeneralUtility::_GP('cmd');
527  $this->mirror = GeneralUtility::_GP('mirror');
528  $this->cacheCmd = GeneralUtility::_GP('cacheCmd');
529  $this->redirect = GeneralUtility::_GP('redirect');
530  $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId');
531  $this->vC = GeneralUtility::_GP('vC');
532  // See tce_db.php for relevate options here:
533  // Only options related to $this->data submission are included here.
535  $tce = GeneralUtility::makeInstance(DataHandler::class);
536 
537  if (!empty($control)) {
538  $tce->setControl($control);
539  }
540  if (isset($_POST['_translation_savedok_x'])) {
541  $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
542  }
543  if (isset($_POST['_translation_savedokclear_x'])) {
544  $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
545  $tce->updateModeL10NdiffDataClear = true;
546  }
547  // Setting default values specific for the user:
548  $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults');
549  if (is_array($TCAdefaultOverride)) {
550  $tce->setDefaultsFromUserTS($TCAdefaultOverride);
551  }
552  // Setting internal vars:
553  if ($beUser->uc['neverHideAtCopy']) {
554  $tce->neverHideAtCopy = 1;
555  }
556  // Loading DataHandler with data:
557  $tce->start($this->data, $this->cmd);
558  if (is_array($this->mirror)) {
559  $tce->setMirror($this->mirror);
560  }
561  // Checking referer / executing
562  $refInfo = parse_url(GeneralUtility::getIndpEnv('HTTP_REFERER'));
563  $httpHost = GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY');
564  if ($httpHost != $refInfo['host']
565  && $this->vC != $beUser->veriCode()
566  && !$GLOBALS['TYPO3_CONF_VARS']['SYS']['doNotCheckReferer']
567  ) {
568  $tce->log(
569  '',
570  0,
571  0,
572  0,
573  1,
574  'Referer host \'%s\' and server host \'%s\' did not match and veriCode was not valid either!',
575  1,
576  [$refInfo['host'], $httpHost]
577  );
578  debug('Error: Referer host did not match with server host.');
579  } else {
580  // Perform the saving operation with DataHandler:
581  $tce->process_uploads($_FILES);
582  $tce->process_datamap();
583  $tce->process_cmdmap();
584  // If pages are being edited, we set an instruction about updating the page tree after this operation.
585  if ($tce->pagetreeNeedsRefresh
586  && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
587  ) {
588  BackendUtility::setUpdateSignal('updatePageTree');
589  }
590  // If there was saved any new items, load them:
591  if (!empty($tce->substNEWwithIDs_table)) {
592  // save the expanded/collapsed states for new inline records, if any
593  FormEngineUtility::updateInlineView($this->uc, $tce);
594  $newEditConf = [];
595  foreach ($this->editconf as $tableName => $tableCmds) {
596  $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
597  if (!empty($keys)) {
598  foreach ($keys as $key) {
599  $editId = $tce->substNEWwithIDs[$key];
600  // Check if the $editId isn't a child record of an IRRE action
601  if (!(is_array($tce->newRelatedIDs[$tableName])
602  && in_array($editId, $tce->newRelatedIDs[$tableName]))
603  ) {
604  // Translate new id to the workspace version:
606  $beUser->workspace,
607  $tableName,
608  $editId,
609  'uid'
610  )) {
611  $editId = $versionRec['uid'];
612  }
613  $newEditConf[$tableName][$editId] = 'edit';
614  }
615  // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
616  // these records!
617  if ($tableName == 'pages'
618  && $this->retUrl != BackendUtility::getModuleUrl('dummy')
619  && $this->returnNewPageId
620  ) {
621  $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
622  }
623  }
624  } else {
625  $newEditConf[$tableName] = $tableCmds;
626  }
627  }
628  // Resetting editconf if newEditConf has values:
629  if (!empty($newEditConf)) {
630  $this->editconf = $newEditConf;
631  }
632  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
633  $this->R_URL_getvars['edit'] = $this->editconf;
634  // Unsetting default values since we don't need them anymore.
635  unset($this->R_URL_getvars['defVals']);
636  // Re-compile the store* values since editconf changed...
637  $this->compileStoreDat();
638  }
639  // See if any records was auto-created as new versions?
640  if (!empty($tce->autoVersionIdMap)) {
641  $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
642  }
643  // If a document is saved and a new one is created right after.
644  if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
645  $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT);
646  // Finding the current table:
647  reset($this->editconf);
648  $nTable = key($this->editconf);
649  // Finding the first id, getting the records pid+uid
650  reset($this->editconf[$nTable]);
651  $nUid = key($this->editconf[$nTable]);
652  $recordFields = 'pid,uid';
653  if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
654  $recordFields .= ',t3ver_oid';
655  }
656  $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
657  // Determine insertion mode ('top' is self-explaining,
658  // otherwise new elements are inserted after one using a negative uid)
659  $insertRecordOnTop = ($this->getNewIconMode($nTable) == 'top');
660  // Setting a blank editconf array for a new record:
661  $this->editconf = [];
662  // Determine related page ID for regular live context
663  if ($nRec['pid'] != -1) {
664  if ($insertRecordOnTop) {
665  $relatedPageId = $nRec['pid'];
666  } else {
667  $relatedPageId = -$nRec['uid'];
668  }
669  // Determine related page ID for workspace context
670  } else {
671  if ($insertRecordOnTop) {
672  // Fetch live version of workspace version since the pid value is always -1 in workspaces
673  $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
674  $relatedPageId = $liveRecord['pid'];
675  } else {
676  // Use uid of live version of workspace version
677  $relatedPageId = -$nRec['t3ver_oid'];
678  }
679  }
680  $this->editconf[$nTable][$relatedPageId] = 'new';
681  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
682  $this->R_URL_getvars['edit'] = $this->editconf;
683  // Re-compile the store* values since editconf changed...
684  $this->compileStoreDat();
685  }
686  // If a preview is requested
687  if (isset($_POST['_savedokview'])) {
688  // Get the first table and id of the data array from DataHandler
689  $table = reset(array_keys($this->data));
690  $id = reset(array_keys($this->data[$table]));
692  $id = $tce->substNEWwithIDs[$id];
693  }
694  // Store this information for later use
695  $this->previewData['table'] = $table;
696  $this->previewData['id'] = $id;
697  }
698  $tce->printLogErrorMessages(isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars));
699  }
700  // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
701  // because if not, we just get that element re-listed as new. And we don't want that!
702  if ((int)$this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
703  || isset($_POST['_saveandclosedok'])
704  || isset($_POST['_translation_savedok_x'])
705  ) {
706  $this->closeDocument(abs($this->closeDoc));
707  }
708  }
709 
715  public function init()
716  {
717  $beUser = $this->getBackendUser();
718  // Setting more GPvars:
719  $this->popViewId = GeneralUtility::_GP('popViewId');
720  $this->popViewId_addParams = GeneralUtility::_GP('popViewId_addParams');
721  $this->viewUrl = GeneralUtility::_GP('viewUrl');
722  $this->recTitle = GeneralUtility::_GP('recTitle');
723  $this->noView = GeneralUtility::_GP('noView');
724  $this->perms_clause = $beUser->getPagePermsClause(1);
725  // Set other internal variables:
726  $this->R_URL_getvars['returnUrl'] = $this->retUrl;
727  $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl(
728  '',
729  $this->R_URL_getvars
730  ), '&');
731  // Setting virtual document name
732  $this->MCONF['name'] = 'xMOD_alt_doc.php';
733 
734  // Create an instance of the document template object
735  $this->doc = $GLOBALS['TBE_TEMPLATE'];
736  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
737  $pageRenderer->addInlineLanguageLabelFile('EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf');
738  // override the default jumpToUrl
739  $this->moduleTemplate->addJavaScriptCode(
740  'jumpToUrl',
741  '
742  function jumpToUrl(URL,formEl) {
743  if (!TBE_EDITOR.isFormChanged()) {
744  window.location.href = URL;
745  } else if (formEl && formEl.type=="checkbox") {
746  formEl.checked = formEl.checked ? 0 : 1;
747  }
748  }
749 '
750  );
751  $t3Configuration = [];
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/Resources/Private/Language/locallang_core.xlf:csh_tooltip_loading')
760  ];
761  $generatedLabels = [];
762  $generatedLabels['core'] = $coreLabels;
763  $code = 'TYPO3.LLL = ' . json_encode($generatedLabels) . ';';
764  $filePath = 'typo3temp/assets/js/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 
775  $javascript = '
776  TYPO3.configuration = ' . json_encode($t3Configuration) . ';
777  // Object: TS:
778  // TS object overwrites the object declared in tbe_editor.js
779  function typoSetup() { //
780  this.uniqueID = "";
781  }
782  var TS = new typoSetup();
783 
784  // Info view:
785  function launchView(table,uid) { //
786  var thePreviewWindow = window.open(
787  ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
788  "ShowItem" + TS.uniqueID,
789  "height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
790  );
791  if (thePreviewWindow && thePreviewWindow.focus) {
792  thePreviewWindow.focus();
793  }
794  }
795  function deleteRecord(table,id,url) { //
796  window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&vC=' . $beUser->veriCode() . '&prErr=1&uPT=1";
797  }
798  ';
799 
800  $previewCode = isset($_POST['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '';
801  $this->moduleTemplate->addJavaScriptCode(
802  'PreviewCode',
803  $javascript . $previewCode
804  );
805  // Setting up the context sensitive menu:
806  $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
807 
808  $this->emitFunctionAfterSignal(__FUNCTION__);
809  }
810 
814  protected function generatePreviewCode()
815  {
816  $table = $this->previewData['table'];
817  $recordId = $this->previewData['id'];
818 
819  if ($table === 'pages') {
820  $currentPageId = $recordId;
821  } else {
822  $currentPageId = MathUtility::convertToPositiveInteger($this->popViewId);
823  }
824 
825  $pageTsConfig = BackendUtility::getPagesTSconfig($currentPageId);
826  $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
827  ? $pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
828  : [];
829 
830  $recordArray = BackendUtility::getRecord($table, $recordId);
831 
832  // find the right preview page id
833  $previewPageId = 0;
834  if (isset($previewConfiguration['previewPageId'])) {
835  $previewPageId = $previewConfiguration['previewPageId'];
836  }
837  // if no preview page was configured
838  if (!$previewPageId) {
839  $rootPageData = null;
840  $rootLine = BackendUtility::BEgetRootLine($currentPageId);
841  $currentPage = reset($rootLine);
842  // Allow all doktypes below 200
843  // This makes custom doktype work as well with opening a frontend page.
844  if ((int)$currentPage['doktype'] <= PageRepository::DOKTYPE_SPACER) {
845  // try the current page
846  $previewPageId = $currentPageId;
847  } else {
848  // or search for the root page
849  foreach ($rootLine as $page) {
850  if ($page['is_siteroot']) {
851  $rootPageData = $page;
852  break;
853  }
854  }
855  $previewPageId = isset($rootPageData)
856  ? (int)$rootPageData['uid']
857  : $currentPageId;
858  }
859  }
860 
861  $linkParameters = [
862  'no_cache' => 1,
863  ];
864 
865  // language handling
866  $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
867  ? $GLOBALS['TCA'][$table]['ctrl']['languageField']
868  : '';
869  if ($languageField && !empty($recordArray[$languageField])) {
870  $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
871  ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
872  : '';
873  if ($l18nPointer && !empty($recordArray[$l18nPointer])
874  && isset($previewConfiguration['useDefaultLanguageRecord'])
875  && !$previewConfiguration['useDefaultLanguageRecord']
876  ) {
877  // use parent record
878  $recordId = $recordArray[$l18nPointer];
879  }
880  $linkParameters['L'] = $recordArray[$languageField];
881  }
882 
883  // map record data to GET parameters
884  if (isset($previewConfiguration['fieldToParameterMap.'])) {
885  foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
886  $value = $recordArray[$field];
887  if ($field === 'uid') {
888  $value = $recordId;
889  }
890  $linkParameters[$parameterName] = $value;
891  }
892  }
893 
894  // add/override parameters by configuration
895  if (isset($previewConfiguration['additionalGetParameters.'])) {
896  $additionalGetParameters = [];
898  $additionalGetParameters,
899  $previewConfiguration['additionalGetParameters.']
900  );
901  $linkParameters = array_replace($linkParameters, $additionalGetParameters);
902  }
903 
904  $this->popViewId = $previewPageId;
905  $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true);
906 
907  $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
908  return '
909  if (window.opener) {
910  '
912  $this->popViewId,
913  '',
914  $previewPageRootline,
915  '',
916  $this->viewUrl,
917  $this->popViewId_addParams,
918  false
919  )
920  . '
921  } else {
922  '
924  $this->popViewId,
925  '',
926  $previewPageRootline,
927  '',
928  $this->viewUrl,
929  $this->popViewId_addParams
930  )
931  . '
932  }';
933  }
934 
944  protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
945  {
946  foreach ($typoScript as $key => $value) {
947  if (is_array($value)) {
948  $key = rtrim($key, '.');
949  $parameters[$key] = [];
950  $this->parseAdditionalGetParameters($parameters[$key], $value);
951  } else {
952  $parameters[$key] = $value;
953  }
954  }
955  }
956 
962  public function main()
963  {
964  $body = '';
965  // Begin edit:
966  if (is_array($this->editconf)) {
968  $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
969 
970  // Creating the editing form, wrap it with buttons, document selector etc.
971  $editForm = $this->makeEditForm();
972  if ($editForm) {
973  $this->firstEl = reset($this->elementsData);
974  // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
975  if (($this->docDat[1] !== $this->storeUrlMd5
976  || !isset($this->docHandler[$this->storeUrlMd5]))
977  && !$this->dontStoreDocumentRef
978  ) {
979  $this->docHandler[$this->storeUrlMd5] = [
984  ];
985  $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->storeUrlMd5]);
986  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
987  }
988  // Module configuration
989  $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig(
990  $this->viewId,
991  'mod.xMOD_alt_doc'
992  ) : [];
993  $body = $this->formResultCompiler->addCssFiles();
994  $body .= $this->compileForm($editForm);
995  $body .= $this->formResultCompiler->printNeededJSFunctions();
996  $body .= '</form>';
997  }
998  }
999  // Access check...
1000  // The page will show only if there is a valid page and if this page may be viewed by the user
1001  $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
1002  if ($this->pageinfo) {
1003  $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
1004  }
1005  // Setting up the buttons and markers for docheader
1006  $this->getButtons();
1007  $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
1008  $this->moduleTemplate->setContent($body);
1009  }
1010 
1011  /***************************
1012  *
1013  * Sub-content functions, rendering specific parts of the module content.
1014  *
1015  ***************************/
1021  public function makeEditForm()
1022  {
1023  // Initialize variables:
1024  $this->elementsData = [];
1025  $this->errorC = 0;
1026  $this->newC = 0;
1027  $editForm = '';
1028  $trData = null;
1029  $beUser = $this->getBackendUser();
1030  // Traverse the GPvar edit array
1031  // Tables:
1032  foreach ($this->editconf as $table => $conf) {
1033  if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
1034  // Traverse the keys/comments of each table (keys can be a commalist of uids)
1035  foreach ($conf as $cKey => $command) {
1036  if ($command == 'edit' || $command == 'new') {
1037  // Get the ids:
1038  $ids = GeneralUtility::trimExplode(',', $cKey, true);
1039  // Traverse the ids:
1040  foreach ($ids as $theUid) {
1041  // Don't save this document title in the document selector if the document is new.
1042  if ($command === 'new') {
1043  $this->dontStoreDocumentRef = 1;
1044  }
1045 
1047  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1049  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
1051  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1052 
1053  try {
1054  // Reset viewId - it should hold data of last entry only
1055  $this->viewId = 0;
1056  $this->viewId_addParams = '';
1057 
1058  $formDataCompilerInput = [
1059  'tableName' => $table,
1060  'vanillaUid' => (int)$theUid,
1061  'command' => $command,
1062  'returnUrl' => $this->R_URI,
1063  ];
1064  if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1065  $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1066  }
1067 
1068  $formData = $formDataCompiler->compile($formDataCompilerInput);
1069 
1070  // Set this->viewId if possible
1071  if ($command === 'new'
1072  && $table !== 'pages'
1073  && !empty($formData['parentPageRow']['uid'])
1074  ) {
1075  $this->viewId = $formData['parentPageRow']['uid'];
1076  } else {
1077  if ($table == 'pages') {
1078  $this->viewId = $formData['databaseRow']['uid'];
1079  } elseif (!empty($formData['parentPageRow']['uid'])) {
1080  $this->viewId = $formData['parentPageRow']['uid'];
1081  // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1082  if (!empty($formData['processedTca']['ctrl']['languageField'])
1083  && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1084  && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1085  ) {
1086  $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1087  }
1088  }
1089  }
1090 
1091  // Determine if delete button can be shown
1092  $deleteAccess = false;
1093  if ($command === 'edit') {
1094  $permission = $formData['userPermissionOnPage'];
1095  if ($formData['tableName'] === 'pages') {
1096  $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1097  } else {
1098  $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1099  }
1100  }
1101 
1102  // Display "is-locked" message:
1103  if ($command === 'edit') {
1104  $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1105  if ($lockInfo) {
1107  $flashMessage = GeneralUtility::makeInstance(
1108  FlashMessage::class,
1109  $lockInfo['msg'],
1110  '',
1112  );
1114  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1116  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1117  $defaultFlashMessageQueue->enqueue($flashMessage);
1118  }
1119  }
1120 
1121  // Record title
1122  if (!$this->storeTitle) {
1123  $this->storeTitle = $this->recTitle
1124  ? htmlspecialchars($this->recTitle)
1125  : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
1126  }
1127 
1128  $this->elementsData[] = [
1129  'table' => $table,
1130  'uid' => $formData['databaseRow']['uid'],
1131  'pid' => $formData['databaseRow']['pid'],
1132  'cmd' => $command,
1133  'deleteAccess' => $deleteAccess
1134  ];
1135 
1136  if ($command !== 'new') {
1137  BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1138  }
1139 
1140  // Set list if only specific fields should be rendered. This will trigger
1141  // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1142  if ($this->columnsOnly) {
1143  if (is_array($this->columnsOnly)) {
1144  $formData['fieldListToRender'] = $this->columnsOnly[$table];
1145  } else {
1146  $formData['fieldListToRender'] = $this->columnsOnly;
1147  }
1148  }
1149 
1150  $formData['renderType'] = 'outerWrapContainer';
1151  $formResult = $nodeFactory->create($formData)->render();
1152 
1153  $html = $formResult['html'];
1154 
1155  $formResult['html'] = '';
1156  $formResult['doSaveFieldName'] = 'doSave';
1157 
1158  // @todo: Put all the stuff into FormEngine as final "compiler" class
1159  // @todo: This is done here for now to not rewrite addCssFiles()
1160  // @todo: and printNeededJSFunctions() now
1161  $this->formResultCompiler->mergeResult($formResult);
1162 
1163  // Seems the pid is set as hidden field (again) at end?!
1164  if ($command == 'new') {
1165  // @todo: looks ugly
1166  $html .= LF
1167  . '<input type="hidden"'
1168  . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1169  . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1170  $this->newC++;
1171  }
1172 
1173  $editForm .= $html;
1174  } catch (AccessDeniedException $e) {
1175  $this->errorC++;
1176  // Try to fetch error message from "recordInternals" be user object
1177  // @todo: This construct should be logged and localized and de-uglified
1178  $message = $beUser->errorMsg;
1179  if (empty($message)) {
1180  // Create message from exception.
1181  $message = $e->getMessage() . ' ' . $e->getCode();
1182  }
1183  $editForm .= htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noEditPermission'))
1184  . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1185  }
1186  } // End of for each uid
1187  }
1188  }
1189  }
1190  }
1191  return $editForm;
1192  }
1193 
1199  protected function getButtons()
1200  {
1201  $lang = $this->getLanguageService();
1202  // Render SAVE type buttons:
1203  // The action of each button is decided by its name attribute. (See doProcessData())
1204  $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1205  if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1206  $saveSplitButton = $buttonBar->makeSplitButton();
1207  // SAVE button:
1208  $saveButton = $buttonBar->makeInputButton()
1209  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
1210  ->setName('_savedok')
1211  ->setValue('1')
1212  ->setForm('EditDocumentController')
1213  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
1214  $saveSplitButton->addItem($saveButton, true);
1215 
1216  // SAVE / VIEW button:
1217  if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1218  $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1219  if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1220  $excludeDokTypes = GeneralUtility::intExplode(
1221  ',',
1222  $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1223  true
1224  );
1225  } else {
1226  // exclude sysfolders, spacers and recycler by default
1227  $excludeDokTypes = [
1231  ];
1232  }
1233  if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1234  || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1235  ) {
1236  $saveAndOpenButton = $buttonBar->makeInputButton()
1237  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDocShow'))
1238  ->setName('_savedokview')
1239  ->setValue('1')
1240  ->setForm('EditDocumentController')
1241  ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1242  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1243  'actions-document-save-view',
1245  ));
1246  $saveSplitButton->addItem($saveAndOpenButton);
1247  }
1248  }
1249  // SAVE / NEW button:
1250  if (count($this->elementsData) === 1 && $this->getNewIconMode($this->firstEl['table'])) {
1251  $saveAndNewButton = $buttonBar->makeInputButton()
1252  ->setName('_savedoknew')
1253  ->setClasses('t3js-editform-submitButton')
1254  ->setValue('1')
1255  ->setForm('EditDocumentController')
1256  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveNewDoc'))
1257  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1258  'actions-document-save-new',
1260  ));
1261  $saveSplitButton->addItem($saveAndNewButton);
1262  }
1263  // SAVE / CLOSE
1264  $saveAndCloseButton = $buttonBar->makeInputButton()
1265  ->setName('_saveandclosedok')
1266  ->setClasses('t3js-editform-submitButton')
1267  ->setValue('1')
1268  ->setForm('EditDocumentController')
1269  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
1270  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1271  'actions-document-save-close',
1273  ));
1274  $saveSplitButton->addItem($saveAndCloseButton);
1275  // FINISH TRANSLATION / SAVE / CLOSE
1276  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1277  $saveTranslationButton = $buttonBar->makeInputButton()
1278  ->setName('_translation_savedok')
1279  ->setValue('1')
1280  ->setForm('EditDocumentController')
1281  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.translationSaveDoc'))
1282  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1283  'actions-document-save-cleartranslationcache',
1285  ));
1286  $saveSplitButton->addItem($saveTranslationButton);
1287  $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1288  ->setName('_translation_savedokclear')
1289  ->setValue('1')
1290  ->setForm('EditDocumentController')
1291  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.translationSaveDocClear'))
1292  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1293  'actions-document-save-cleartranslationcache',
1295  ));
1296  $saveSplitButton->addItem($saveAndClearTranslationButton);
1297  }
1298  $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1299  }
1300  // CLOSE button:
1301  $closeButton = $buttonBar->makeLinkButton()
1302  ->setHref('#')
1303  ->setClasses('t3js-editform-close')
1304  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
1305  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1306  'actions-document-close',
1308  ));
1309  $buttonBar->addButton($closeButton);
1310  // DELETE + UNDO buttons:
1311  if (!$this->errorC
1312  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1313  && count($this->elementsData) === 1
1314  ) {
1315  if ($this->firstEl['cmd'] !== 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1316  // Delete:
1317  if ($this->firstEl['deleteAccess']
1318  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1319  && !$this->getNewIconMode($this->firstEl['table'], 'disableDelete')
1320  ) {
1322  if ($this->firstEl['table'] === 'pages') {
1323  parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
1324  if (isset($queryParams['M'])
1325  && isset($queryParams['id'])
1326  && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1327  ) {
1328  // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1329  // tree from the outside to be able to mark the pid as active
1330  $returnUrl = BackendUtility::getModuleUrl($queryParams['M'], ['id' => 0]);
1331  }
1332  }
1333  $deleteButton = $buttonBar->makeLinkButton()
1334  ->setHref('#')
1335  ->setClasses('t3js-editform-delete-record')
1336  ->setTitle($lang->getLL('deleteItem'))
1337  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1338  'actions-edit-delete',
1340  ))
1341  ->setDataAttributes([
1342  'return-url' => $returnUrl,
1343  'uid' => $this->firstEl['uid'],
1344  'table' => $this->firstEl['table']
1345  ]);
1346  $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1347  }
1348  // Undo:
1349  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1350  ->getQueryBuilderForTable('sys_history');
1351 
1352  $undoButtonR = $queryBuilder->select('tstamp')
1353  ->from('sys_history')
1354  ->where(
1355  $queryBuilder->expr()->eq(
1356  'tablename',
1357  $queryBuilder->createNamedParameter($this->firstEl['table'], \PDO::PARAM_STR)
1358  ),
1359  $queryBuilder->expr()->eq(
1360  'recuid',
1361  $queryBuilder->createNamedParameter($this->firstEl['uid'], \PDO::PARAM_INT)
1362  )
1363  )
1364  ->orderBy('tstamp', 'DESC')
1365  ->setMaxResults(1)
1366  ->execute()
1367  ->fetch();
1368 
1369  if ($undoButtonR !== false) {
1370  $aOnClick = 'window.location.href=' .
1372  BackendUtility::getModuleUrl(
1373  'record_history',
1374  [
1375  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1376  'revert' => 'ALL_FIELDS',
1377  'returnUrl' => $this->R_URI,
1378  ]
1379  )
1380  ) . '; return false;';
1381 
1382  $undoButton = $buttonBar->makeLinkButton()
1383  ->setHref('#')
1384  ->setOnClick($aOnClick)
1385  ->setTitle(
1386  sprintf(
1387  $lang->getLL('undoLastChange'),
1389  ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1390  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
1391  )
1392  )
1393  )
1394  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1395  'actions-document-history-open',
1397  ));
1398  $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1399  }
1400  if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1401  $aOnClick = 'window.location.href=' .
1403  BackendUtility::getModuleUrl(
1404  'record_history',
1405  [
1406  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1407  'returnUrl' => $this->R_URI,
1408  ]
1409  )
1410  ) . '; return false;';
1411 
1412  $historyButton = $buttonBar->makeLinkButton()
1413  ->setHref('#')
1414  ->setOnClick($aOnClick)
1415  ->setTitle('Open history of this record')
1416  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1417  'actions-document-history-open',
1419  ));
1420  $buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1421  }
1422  // If only SOME fields are shown in the form, this will link the user to the FULL form:
1423  if ($this->columnsOnly) {
1424  $columnsOnlyButton = $buttonBar->makeLinkButton()
1425  ->setHref($this->R_URI . '&columnsOnly=')
1426  ->setTitle($lang->getLL('editWholeRecord'))
1427  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1428  'actions-open',
1430  ));
1431  $buttonBar->addButton($columnsOnlyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1432  }
1433  }
1434  }
1435  $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1436  $buttonBar->addButton($cshButton);
1437  $this->shortCutLink();
1438  $this->openInNewWindowLink();
1439  }
1440 
1447  public function compileForm($editForm)
1448  {
1449  $formContent = '
1450  <!-- EDITING FORM -->
1451  <form
1452  action="' . htmlspecialchars($this->R_URI) . '"
1453  method="post"
1454  enctype="multipart/form-data"
1455  name="editform"
1456  id="EditDocumentController"
1457  onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1458  ' . $editForm . '
1459 
1460  <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1461  <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1462  if ($this->returnNewPageId) {
1463  $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1464  }
1465  $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1466  if ($this->viewId_addParams) {
1467  $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1468  }
1469  $formContent .= '
1470  <input type="hidden" name="closeDoc" value="0" />
1471  <input type="hidden" name="doSave" value="0" />
1472  <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1473  <input type="hidden" name="_scrollPosition" value="" />';
1474  return $formContent;
1475  }
1476 
1480  public function shortCutLink()
1481  {
1482  if ($this->returnUrl !== ExtensionManagementUtility::siteRelPath('backend') . 'Resources/Private/Templates/Close.html') {
1483  $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1484  $shortCutButton->setModuleName($this->MCONF['name'])
1485  ->setGetVariables([
1486  'returnUrl',
1487  'edit',
1488  'defVals',
1489  'overrideVals',
1490  'columnsOnly',
1491  'returnNewPageId',
1492  'noView']);
1493  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1494  }
1495  }
1496 
1500  public function openInNewWindowLink()
1501  {
1502  $closeUrl = ExtensionManagementUtility::siteRelPath('backend') . 'Resources/Private/Templates/Close.html';
1503  if ($this->returnUrl !== $closeUrl) {
1504  $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(
1505  ['returnUrl' => PathUtility::getAbsoluteWebPath($closeUrl)]
1506  ))
1507  . ','
1508  . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1509  . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1510  $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1511  ->makeLinkButton()
1512  ->setHref('#')
1513  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.openInNewWindow'))
1514  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1515  ->setOnClick($aOnClick);
1516  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
1517  $openInNewWindowButton,
1519  );
1520  }
1521  }
1522 
1523  /***************************
1524  *
1525  * Localization stuff
1526  *
1527  ***************************/
1537  public function languageSwitch($table, $uid, $pid = null)
1538  {
1539  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1540  $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1541  // Table editable and activated for languages?
1542  if ($this->getBackendUser()->check('tables_modify', $table)
1543  && $languageField
1544  && $transOrigPointerField
1545  && $table !== 'pages_language_overlay'
1546  ) {
1547  if (is_null($pid)) {
1548  $row = BackendUtility::getRecord($table, $uid, 'pid');
1549  $pid = $row['pid'];
1550  }
1551  // Get all available languages for the page
1552  $langRows = $this->getLanguages($pid);
1553  // Page available in other languages than default language?
1554  if (is_array($langRows) && count($langRows) > 1) {
1555  $rowsByLang = [];
1556  $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1557  // Get record in current language
1558  $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1559  if (!is_array($rowCurrent)) {
1560  $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1561  }
1562  $currentLanguage = (int)$rowCurrent[$languageField];
1563  // Disabled for records with [all] language!
1564  if ($currentLanguage > -1) {
1565  // Get record in default language if needed
1566  if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1567  $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1568  $table,
1569  $rowCurrent[$transOrigPointerField],
1570  $fetchFields
1571  );
1572  if (!is_array($rowsByLang[0])) {
1573  $rowsByLang[0] = BackendUtility::getRecord(
1574  $table,
1575  $rowCurrent[$transOrigPointerField],
1576  $fetchFields
1577  );
1578  }
1579  } else {
1580  $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1581  }
1582  if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1583  // Get record in other languages to see what's already available
1584 
1585  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1586  ->getQueryBuilderForTable($table);
1587 
1588  $queryBuilder->getRestrictions()
1589  ->removeAll()
1590  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1591  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1592 
1593  $result = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fetchFields, true))
1594  ->from($table)
1595  ->where(
1596  $queryBuilder->expr()->eq(
1597  'pid',
1598  $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
1599  ),
1600  $queryBuilder->expr()->gt(
1601  $languageField,
1602  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1603  ),
1604  $queryBuilder->expr()->eq(
1605  $transOrigPointerField,
1606  $queryBuilder->createNamedParameter($rowsByLang[0]['uid'], \PDO::PARAM_INT)
1607  )
1608  )
1609  ->execute();
1610 
1611  while ($row = $result->fetch()) {
1612  $rowsByLang[$row[$languageField]] = $row;
1613  }
1614  }
1615  $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1616  $languageMenu->setIdentifier('_langSelector');
1617  $languageMenu->setLabel(htmlspecialchars($this->getLanguageService()->sL(
1618  'LLL:EXT:lang/Resources/Private/Language/locallang_general.xlf:LGL.language'
1619  )));
1620  foreach ($langRows as $lang) {
1621  if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1622  $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.new')) . ']';
1623  // Create url for creating a localized record
1624  $addOption = true;
1625  if ($newTranslation) {
1626  $redirectUrl = BackendUtility::getModuleUrl('record_edit', [
1627  'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1628  'returnUrl' => $this->retUrl
1629  ]);
1630 
1631  if ($currentLanguage === 0) {
1633  '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1634  $redirectUrl
1635  );
1636  } else {
1637  $addOption = false;
1638  }
1639  } else {
1640  $href = BackendUtility::getModuleUrl('record_edit', [
1641  'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1642  'returnUrl' => $this->retUrl
1643  ]);
1644  }
1645  if ($addOption) {
1646  $menuItem = $languageMenu->makeMenuItem()
1647  ->setTitle($lang['title'] . $newTranslation)
1648  ->setHref($href);
1649  if ((int)$lang['uid'] === $currentLanguage) {
1650  $menuItem->setActive(true);
1651  }
1652  $languageMenu->addMenuItem($menuItem);
1653  }
1654  }
1655  }
1656  $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1657  }
1658  }
1659  }
1660  }
1661 
1668  public function localizationRedirect($justLocalized)
1669  {
1670  list($table, $origUid, $language) = explode(':', $justLocalized);
1671  if ($GLOBALS['TCA'][$table]
1672  && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1673  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1674  ) {
1675  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1676  ->getQueryBuilderForTable($table);
1677  $queryBuilder->getRestrictions()
1678  ->removeAll()
1679  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1680  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1681 
1682  $localizedRecord = $queryBuilder->select('uid')
1683  ->from($table)
1684  ->where(
1685  $queryBuilder->expr()->eq(
1686  $GLOBALS['TCA'][$table]['ctrl']['languageField'],
1687  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1688  ),
1689  $queryBuilder->expr()->eq(
1690  $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
1691  $queryBuilder->createNamedParameter($origUid, \PDO::PARAM_INT)
1692  )
1693  )
1694  ->execute()
1695  ->fetch();
1696 
1697  if (is_array($localizedRecord)) {
1698  // Create parameters and finally run the classic page module for creating a new page translation
1699  $location = BackendUtility::getModuleUrl('record_edit', [
1700  'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1701  'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1702  ]);
1703  HttpUtility::redirect($location);
1704  }
1705  }
1706  }
1707 
1716  public function getLanguages($id)
1717  {
1718  $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1719  // Fallback non sprite-configuration
1720  if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1721  $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1722  '.gif',
1723  '',
1724  $modSharedTSconfig['properties']['defaultLanguageFlag']
1725  );
1726  }
1727  $languages = [
1728  0 => [
1729  'uid' => 0,
1730  'pid' => 0,
1731  'hidden' => 0,
1732  'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1733  ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1734  : $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage'),
1735  'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1736  ]
1737  ];
1738 
1739  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1740  ->getQueryBuilderForTable('sys_language');
1741 
1742  $queryBuilder->select('s.uid', 's.pid', 's.hidden', 's.title', 's.flag')
1743  ->from('sys_language', 's')
1744  ->groupBy('s.uid', 's.pid', 's.hidden', 's.title', 's.flag')
1745  ->orderBy('s.sorting');
1746 
1747  if ($id) {
1748  $queryBuilder->getRestrictions()
1749  ->removeAll()
1750  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1751  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1752 
1753  if (!$this->getBackendUser()->isAdmin()) {
1754  $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
1755  }
1756 
1757  // Add join with pages_languages_overlay table to only show active languages
1758  $queryBuilder->from('pages_language_overlay', 'o')
1759  ->where(
1760  $queryBuilder->expr()->eq('o.sys_language_uid', $queryBuilder->quoteIdentifier('s.uid')),
1761  $queryBuilder->expr()->eq('o.pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT))
1762  );
1763  }
1764 
1765  $result = $queryBuilder->execute();
1766  while ($row = $result->fetch()) {
1767  $languages[$row['uid']] = $row;
1768  }
1769 
1770  return $languages;
1771  }
1772 
1773  /***************************
1774  *
1775  * Other functions
1776  *
1777  ***************************/
1784  public function fixWSversioningInEditConf($mapArray = false)
1785  {
1786  // Traverse the editConf array
1787  if (is_array($this->editconf)) {
1788  // Tables:
1789  foreach ($this->editconf as $table => $conf) {
1790  if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1791  // Traverse the keys/comments of each table (keys can be a commalist of uids)
1792  $newConf = [];
1793  foreach ($conf as $cKey => $cmd) {
1794  if ($cmd == 'edit') {
1795  // Traverse the ids:
1796  $ids = GeneralUtility::trimExplode(',', $cKey, true);
1797  foreach ($ids as $idKey => $theUid) {
1798  if (is_array($mapArray)) {
1799  if ($mapArray[$table][$theUid]) {
1800  $ids[$idKey] = $mapArray[$table][$theUid];
1801  }
1802  } else {
1803  // Default, look for versions in workspace for record:
1804  $calcPRec = $this->getRecordForEdit($table, $theUid);
1805  if (is_array($calcPRec)) {
1806  // Setting UID again if it had changed, eg. due to workspace versioning.
1807  $ids[$idKey] = $calcPRec['uid'];
1808  }
1809  }
1810  }
1811  // Add the possibly manipulated IDs to the new-build newConf array:
1812  $newConf[implode(',', $ids)] = $cmd;
1813  } else {
1814  $newConf[$cKey] = $cmd;
1815  }
1816  }
1817  // Store the new conf array:
1818  $this->editconf[$table] = $newConf;
1819  }
1820  }
1821  }
1822  }
1823 
1831  public function getRecordForEdit($table, $theUid)
1832  {
1833  // Fetch requested record:
1834  $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1835  if (is_array($reqRecord)) {
1836  // If workspace is OFFLINE:
1837  if ($this->getBackendUser()->workspace != 0) {
1838  // Check for versioning support of the table:
1839  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1840  // If the record is already a version of "something" pass it by.
1841  if ($reqRecord['pid'] == -1) {
1842  // (If it turns out not to be a version of the current workspace there will be trouble, but
1843  // that is handled inside DataHandler then and in the interface it would clearly be an error of
1844  // links if the user accesses such a scenario)
1845  return $reqRecord;
1846  } else {
1847  // The input record was online and an offline version must be found or made:
1848  // Look for version of this workspace:
1850  $this->getBackendUser()->workspace,
1851  $table,
1852  $reqRecord['uid'],
1853  'uid,pid,t3ver_oid'
1854  );
1855  return is_array($versionRec) ? $versionRec : $reqRecord;
1856  }
1857  } else {
1858  // This means that editing cannot occur on this record because it was not supporting versioning
1859  // which is required inside an offline workspace.
1860  return false;
1861  }
1862  } else {
1863  // In ONLINE workspace, just return the originally requested record:
1864  return $reqRecord;
1865  }
1866  } else {
1867  // Return FALSE because the table/uid was not found anyway.
1868  return false;
1869  }
1870  }
1871 
1878  public function compileStoreDat()
1879  {
1881  'edit,defVals,overrideVals,columnsOnly,noView,workspace',
1882  $this->R_URL_getvars
1883  );
1884  $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1885  $this->storeUrlMd5 = md5($this->storeUrl);
1886  }
1887 
1896  public function getNewIconMode($table, $key = 'saveDocNew')
1897  {
1898  $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1899  $output = trim(isset($TSconfig['properties'][$table]) ? $TSconfig['properties'][$table] : $TSconfig['value']);
1900  return $output;
1901  }
1902 
1914  public function closeDocument($mode = self::DOCUMENT_CLOSE_MODE_DEFAULT)
1915  {
1916  $mode = (int)$mode;
1917  // If current document is found in docHandler,
1918  // then unset it, possibly unset it ALL and finally, write it to the session data
1919  if (isset($this->docHandler[$this->storeUrlMd5])) {
1920  // add the closing document to the recent documents
1921  $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1922  if (!is_array($recentDocs)) {
1923  $recentDocs = [];
1924  }
1925  $closedDoc = $this->docHandler[$this->storeUrlMd5];
1926  $recentDocs = array_merge([$this->storeUrlMd5 => $closedDoc], $recentDocs);
1927  if (count($recentDocs) > 8) {
1928  $recentDocs = array_slice($recentDocs, 0, 8);
1929  }
1930  // remove it from the list of the open documents
1931  unset($this->docHandler[$this->storeUrlMd5]);
1932  if ($mode === self::DOCUMENT_CLOSE_MODE_CLEAR_ALL) {
1933  $recentDocs = array_merge($this->docHandler, $recentDocs);
1934  $this->docHandler = [];
1935  }
1936  $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1937  $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->docDat[1]]);
1938  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1939  }
1940  if ($mode !== self::DOCUMENT_CLOSE_MODE_NO_REDIRECT) {
1941  // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
1942  // other scripts, like wizard_add, to know which records was created or so...)
1943  if ($this->returnEditConf && $this->retUrl != BackendUtility::getModuleUrl('dummy')) {
1944  $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
1945  }
1946 
1947  // If mode is NOT set (means 0) OR set to 1, then make a header location redirect to $this->retUrl
1948  if ($mode === self::DOCUMENT_CLOSE_MODE_DEFAULT || $mode === self::DOCUMENT_CLOSE_MODE_REDIRECT) {
1949  HttpUtility::redirect($this->retUrl);
1950  } else {
1951  $this->setDocument('', $this->retUrl);
1952  }
1953  }
1954  }
1955 
1965  public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
1966  {
1967  if ($retUrl === '') {
1968  return;
1969  }
1970  if (!$this->modTSconfig['properties']['disableDocSelector']
1971  && is_array($this->docHandler)
1972  && !empty($this->docHandler)
1973  ) {
1974  if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
1975  $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
1976  } else {
1977  $setupArr = reset($this->docHandler);
1978  }
1979  if ($setupArr[2]) {
1980  $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
1981  $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1982  }
1983  }
1985  }
1986 
1994  public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
1995  {
1997 
1998  // Preprocessing, storing data if submitted to
1999  $this->preInit();
2000 
2001  // Checks, if a save button has been clicked (or the doSave variable is sent)
2002  if ($this->doProcessData()) {
2003  $this->processData();
2004  }
2005 
2006  $this->init();
2007  $this->main();
2008 
2009  $response->getBody()->write($this->moduleTemplate->renderContent());
2010  return $response;
2011  }
2012 
2016  protected function getBackendUser()
2017  {
2018  return $GLOBALS['BE_USER'];
2019  }
2020 
2026  protected function getLanguageService()
2027  {
2028  return $GLOBALS['LANG'];
2029  }
2030 }
mainAction(ServerRequestInterface $request, ResponseInterface $response)
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
Definition: HttpUtility.php:76
static calcAge($seconds, $labels= 'min|hrs|days|yrs|min|hour|day|year')
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
parseAdditionalGetParameters(array &$parameters, array $typoScript)
static implodeArrayForUrl($name, array $theArray, $str= '', $skipBlank=false, $rawurlencodeParamName=false)
static getLinkToDataHandlerAction($parameters, $redirectUrl= '')
static viewOnClick($pageUid, $backPath= '', $rootLine=null, $anchorSection= '', $alternativeUrl= '', $additionalGetVars= '', $switchFocus=true)
static BEgetRootLine($uid, $clause= '', $workspaceOL=false)
static getRecord($table, $uid, $fields= '*', $where= '', $useDeleteClause=true)
static compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt=true)
static writeFileToTypo3tempDir($filepath, $content)
static linkThisScript(array $getParams=[])
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
setDocument($currentDocFromHandlerMD5= '', $retUrl= '')
closeDocument($mode=self::DOCUMENT_CLOSE_MODE_DEFAULT)
debug($variable= '', $name= '*variable *', $line= '*line *', $file= '*file *', $recursiveDepth=3, $debugLevel=E_DEBUG)
static getAbsoluteWebPath($targetPath)
Definition: PathUtility.php:40
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields= '*')
static convertToPositiveInteger($theInt)
Definition: MathUtility.php:55
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)