TYPO3  7.6
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 
19 use TYPO3\CMS\Backend\Form\Exception\AccessDeniedException;
41 
47 {
55  public $editconf;
56 
64  public $columnsOnly;
65 
72  public $defVals;
73 
80  public $overrideVals;
81 
88  public $returnUrl;
89 
95  public $closeDoc;
96 
104  public $doSave;
105 
111  public $data;
112 
116  public $cmd;
117 
121  public $mirror;
122 
128  public $cacheCmd;
129 
135  public $redirect;
136 
144 
148  public $vC;
149 
155  public $uc;
156 
162  public $popViewId;
163 
170 
176  public $viewUrl;
177 
186 
192  public $recTitle;
193 
199  public $noView;
200 
205 
213 
219  protected $workspace;
220 
226  public $doc;
227 
233  public $template;
234 
240  public $content;
241 
249  public $retUrl;
250 
258  public $R_URL_parts;
259 
267 
273  public $R_URI;
274 
278  public $MCONF;
279 
283  public $pageinfo;
284 
291  public $storeTitle = '';
292 
299  public $storeArray;
300 
306  public $storeUrl;
307 
313  public $storeUrlMd5;
314 
320  public $docDat;
321 
330  public $docHandler;
331 
338 
344  public $firstEl;
345 
351  public $errorC;
352 
358  public $newC;
359 
366  public $viewId;
367 
374 
380  public $modTSconfig;
381 
386 
393 
398 
404  protected $previewData = [];
405 
409  public function __construct()
410  {
411  parent::__construct();
412  $GLOBALS['SOBE'] = $this;
413  $this->getLanguageService()->includeLLFile('EXT:lang/locallang_alt_doc.xlf');
414  }
415 
421  protected function getSignalSlotDispatcher()
422  {
423  if (!isset($this->signalSlotDispatcher)) {
424  $this->signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
425  }
427  }
428 
434  protected function emitFunctionAfterSignal($signalName)
435  {
436  $this->getSignalSlotDispatcher()->dispatch(__CLASS__, $signalName . 'After', array($this));
437  }
438 
444  public function preInit()
445  {
446  if (GeneralUtility::_GP('justLocalized')) {
447  $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
448  }
449  // Setting GPvars:
450  $this->editconf = GeneralUtility::_GP('edit');
451  $this->defVals = GeneralUtility::_GP('defVals');
452  $this->overrideVals = GeneralUtility::_GP('overrideVals');
453  $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
454  $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
455  $this->closeDoc = GeneralUtility::_GP('closeDoc');
456  $this->doSave = GeneralUtility::_GP('doSave');
457  $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
458  $this->localizationMode = GeneralUtility::_GP('localizationMode');
459  $this->workspace = GeneralUtility::_GP('workspace');
460  $this->uc = GeneralUtility::_GP('uc');
461  // Setting override values as default if defVals does not exist.
462  if (!is_array($this->defVals) && is_array($this->overrideVals)) {
463  $this->defVals = $this->overrideVals;
464  }
465  // Setting return URL
466  $this->retUrl = $this->returnUrl ?: BackendUtility::getModuleUrl('dummy');
467  // Fix $this->editconf if versioning applies to any of the records
468  $this->fixWSversioningInEditConf();
469  // Make R_URL (request url) based on input GETvars:
470  $this->R_URL_parts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
471  $this->R_URL_getvars = GeneralUtility::_GET();
472  $this->R_URL_getvars['edit'] = $this->editconf;
473  // MAKE url for storing
474  $this->compileStoreDat();
475  // Get session data for the module:
476  $this->docDat = $this->getBackendUser()->getModuleData('FormEngine', 'ses');
477  $this->docHandler = $this->docDat[0];
478  // If a request for closing the document has been sent, act accordingly:
479  if ($this->closeDoc > 0) {
480  $this->closeDocument($this->closeDoc);
481  }
482  // If NO vars are sent to the script, try to read first document:
483  // Added !is_array($this->editconf) because editConf must not be set either.
484  // Anyways I can't figure out when this situation here will apply...
485  if (is_array($this->R_URL_getvars) && count($this->R_URL_getvars) < 2 && !is_array($this->editconf)) {
486  $this->setDocument($this->docDat[1]);
487  }
488 
489  // Sets a temporary workspace, this request is based on
490  if ($this->workspace !== null) {
491  $this->getBackendUser()->setTemporaryWorkspace($this->workspace);
492  }
493 
494  $this->emitFunctionAfterSignal(__FUNCTION__);
495  }
496 
502  public function doProcessData()
503  {
504  $out = $this->doSave
505  || isset($_POST['_savedok'])
506  || isset($_POST['_saveandclosedok'])
507  || isset($_POST['_savedokview'])
508  || isset($_POST['_savedoknew'])
509  || isset($_POST['_translation_savedok_x'])
510  || isset($_POST['_translation_savedokclear_x']);
511  return $out;
512  }
513 
519  public function processData()
520  {
521  $beUser = $this->getBackendUser();
522  // GPvars specifically for processing:
523  $control = GeneralUtility::_GP('control');
524  $this->data = GeneralUtility::_GP('data');
525  $this->cmd = GeneralUtility::_GP('cmd');
526  $this->mirror = GeneralUtility::_GP('mirror');
527  $this->cacheCmd = GeneralUtility::_GP('cacheCmd');
528  $this->redirect = GeneralUtility::_GP('redirect');
529  $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId');
530  $this->vC = GeneralUtility::_GP('vC');
531  // See tce_db.php for relevate options here:
532  // Only options related to $this->data submission are included here.
534  $tce = GeneralUtility::makeInstance(DataHandler::class);
535  $tce->stripslashes_values = false;
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 TCEmain 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  array($refInfo['host'], $httpHost)
577  );
578  debug('Error: Referer host did not match with server host.');
579  } else {
580  // Perform the saving operation with TCEmain:
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 = array();
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  // Finding the current table:
646  reset($this->editconf);
647  $nTable = key($this->editconf);
648  // Finding the first id, getting the records pid+uid
649  reset($this->editconf[$nTable]);
650  $nUid = key($this->editconf[$nTable]);
651  $nRec = BackendUtility::getRecord($nTable, $nUid, 'pid,uid');
652  // Setting a blank editconf array for a new record:
653  $this->editconf = array();
654  if ($this->getNewIconMode($nTable) == 'top') {
655  $this->editconf[$nTable][$nRec['pid']] = 'new';
656  } else {
657  $this->editconf[$nTable][-$nRec['uid']] = 'new';
658  }
659  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
660  $this->R_URL_getvars['edit'] = $this->editconf;
661  // Re-compile the store* values since editconf changed...
662  $this->compileStoreDat();
663  }
664  // If a preview is requested
665  if (isset($_POST['_savedokview'])) {
666  // Get the first table and id of the data array from DataHandler
667  $table = reset(array_keys($this->data));
668  $id = reset(array_keys($this->data[$table]));
670  $id = $tce->substNEWwithIDs[$id];
671  }
672  // Store this information for later use
673  $this->previewData['table'] = $table;
674  $this->previewData['id'] = $id;
675  }
676  $tce->printLogErrorMessages(isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) ? $this->retUrl : $this->R_URL_parts['path'] . '?' . GeneralUtility::implodeArrayForUrl('', $this->R_URL_getvars));
677  }
678  // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
679  // because if not, we just get that element re-listed as new. And we don't want that!
680  if (isset($_POST['_saveandclosedok']) || isset($_POST['_translation_savedok_x']) || $this->closeDoc < 0) {
681  $this->closeDocument(abs($this->closeDoc));
682  }
683  }
684 
690  public function init()
691  {
692  $beUser = $this->getBackendUser();
693  // Setting more GPvars:
694  $this->popViewId = GeneralUtility::_GP('popViewId');
695  $this->popViewId_addParams = GeneralUtility::_GP('popViewId_addParams');
696  $this->viewUrl = GeneralUtility::_GP('viewUrl');
697  $this->editRegularContentFromId = GeneralUtility::_GP('editRegularContentFromId');
698  $this->recTitle = GeneralUtility::_GP('recTitle');
699  $this->noView = GeneralUtility::_GP('noView');
700  $this->perms_clause = $beUser->getPagePermsClause(1);
701  // Set other internal variables:
702  $this->R_URL_getvars['returnUrl'] = $this->retUrl;
703  $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl(
704  '',
705  $this->R_URL_getvars
706  ), '&');
707  // Setting virtual document name
708  $this->MCONF['name'] = 'xMOD_alt_doc.php';
709 
710  // Create an instance of the document template object
711  $this->doc = $GLOBALS['TBE_TEMPLATE'];
712  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
713  $pageRenderer->addInlineLanguageLabelFile('EXT:lang/locallang_alt_doc.xlf');
714  // override the default jumpToUrl
715  $this->moduleTemplate->addJavaScriptCode(
716  'jumpToUrl',
717  '
718  function jumpToUrl(URL,formEl) {
719  if (!TBE_EDITOR.isFormChanged()) {
720  window.location.href = URL;
721  } else if (formEl && formEl.type=="checkbox") {
722  formEl.checked = formEl.checked ? 0 : 1;
723  }
724  }
725 '
726  );
727  // define the window size of the element browser
728  $popupWindowWidth = 700;
729  $popupWindowHeight = 750;
730  $popupWindowSize = trim($beUser->getTSConfigVal('options.popupWindowSize'));
731  if (!empty($popupWindowSize)) {
732  list($popupWindowWidth, $popupWindowHeight) = GeneralUtility::intExplode('x', $popupWindowSize);
733  }
734  $t3Configuration = array(
735  'PopupWindow' => array(
736  'width' => $popupWindowWidth,
737  'height' => $popupWindowHeight
738  )
739  );
740 
741  if (ExtensionManagementUtility::isLoaded('feedit') && (int)GeneralUtility::_GP('feEdit') === 1) {
742  // We have to load some locallang strings and push them into TYPO3.LLL if this request was
743  // triggered by feedit. Originally, this object is fed by BackendController which is not
744  // called here. This block of code is intended to be removed at a later point again.
745  $lang = $this->getLanguageService();
746  $coreLabels = array(
747  'csh_tooltip_loading' => $lang->sL('LLL:EXT:lang/locallang_core.xlf:csh_tooltip_loading')
748  );
749  $generatedLabels = array();
750  $generatedLabels['core'] = $coreLabels;
751  $code = 'TYPO3.LLL = ' . json_encode($generatedLabels) . ';';
752  $filePath = 'typo3temp/Language/Backend-' . sha1($code) . '.js';
753  if (!file_exists(PATH_site . $filePath)) {
754  // writeFileToTypo3tempDir() returns NULL on success (please double-read!)
755  $error = GeneralUtility::writeFileToTypo3tempDir(PATH_site . $filePath, $code);
756  if ($error !== null) {
757  throw new \RuntimeException('Locallang JS file could not be written to ' . $filePath . '. Reason: ' . $error, 1446118286);
758  }
759  }
760  $pageRenderer->addJsFile('../' . $filePath);
761 
762  // define the window size of the popups within the RTE
763  $rtePopupWindowSize = trim($beUser->getTSConfigVal('options.rte.popupWindowSize'));
764  if (!empty($rtePopupWindowSize)) {
765  list($rtePopupWindowWidth, $rtePopupWindowHeight) = GeneralUtility::trimExplode('x', $rtePopupWindowSize);
766  }
767  $rtePopupWindowWidth = !empty($rtePopupWindowWidth) ? (int)$rtePopupWindowWidth : ($popupWindowWidth-200);
768  $rtePopupWindowHeight = !empty($rtePopupWindowHeight) ? (int)$rtePopupWindowHeight : ($popupWindowHeight-250);
769  $t3Configuration['RTEPopupWindow'] = [
770  'width' => $rtePopupWindowWidth,
771  'height' => $rtePopupWindowHeight
772  ];
773  }
774 
775  $javascript = '
776  TYPO3.configuration = ' . json_encode($t3Configuration) . ';
777  // Object: TS:
778  // passwordDummy and decimalSign are used by tbe_editor.js and have to be declared here as
779  // TS object overwrites the object declared in tbe_editor.js
780  function typoSetup() { //
781  this.uniqueID = "";
782  this.passwordDummy = "********";
783  this.PATH_typo3 = "";
784  this.decimalSign = ".";
785  }
786  var TS = new typoSetup();
787 
788  // Info view:
789  function launchView(table,uid,bP) { //
790  var backPath= bP ? bP : "";
791  var thePreviewWindow = window.open(
792  backPath+' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
793  "ShowItem" + TS.uniqueID,
794  "height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
795  );
796  if (thePreviewWindow && thePreviewWindow.focus) {
797  thePreviewWindow.focus();
798  }
799  }
800  function deleteRecord(table,id,url) { //
801  window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&vC=' . $beUser->veriCode() . '&prErr=1&uPT=1";
802  }
803  ';
804 
805  $previewCode = isset($_POST['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '';
806  $this->moduleTemplate->addJavaScriptCode(
807  'PreviewCode',
808  $javascript . $previewCode
809  );
810  // Setting up the context sensitive menu:
811  $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
812 
813  $this->emitFunctionAfterSignal(__FUNCTION__);
814  }
815 
819  protected function generatePreviewCode()
820  {
821  $currentPageId = MathUtility::convertToPositiveInteger($this->popViewId);
822  $table = $this->previewData['table'];
823  $recordId = $this->previewData['id'];
824 
825  $pageTsConfig = BackendUtility::getPagesTSconfig($currentPageId);
826  $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
827  ? $pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
828  : array();
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  if ((int)$currentPage['doktype'] === PageRepository::DOKTYPE_DEFAULT) {
843  // try the current page
844  $previewPageId = $currentPageId;
845  } else {
846  // or search for the root page
847  foreach ($rootLine as $page) {
848  if ($page['is_siteroot']) {
849  $rootPageData = $page;
850  break;
851  }
852  }
853  $previewPageId = isset($rootPageData)
854  ? (int)$rootPageData['uid']
855  : $currentPageId;
856  }
857  }
858 
859  $linkParameters = [
860  'no_cache' => 1,
861  ];
862 
863  // language handling
864  $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
865  ? $GLOBALS['TCA'][$table]['ctrl']['languageField']
866  : '';
867  if ($languageField && !empty($recordArray[$languageField])) {
868  $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
869  ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
870  : '';
871  if ($l18nPointer && !empty($recordArray[$l18nPointer])
872  && isset($previewConfiguration['useDefaultLanguageRecord'])
873  && !$previewConfiguration['useDefaultLanguageRecord']
874  ) {
875  // use parent record
876  $recordId = $recordArray[$l18nPointer];
877  }
878  $linkParameters['L'] = $recordArray[$languageField];
879  }
880 
881  // map record data to GET parameters
882  if (isset($previewConfiguration['fieldToParameterMap.'])) {
883  foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
884  $value = $recordArray[$field];
885  if ($field === 'uid') {
886  $value = $recordId;
887  }
888  $linkParameters[$parameterName] = $value;
889  }
890  }
891 
892  // add/override parameters by configuration
893  if (isset($previewConfiguration['additionalGetParameters.'])) {
894  $additionalGetParameters = [];
896  $additionalGetParameters,
897  $previewConfiguration['additionalGetParameters.']
898  );
899  $linkParameters = array_replace($linkParameters, $additionalGetParameters);
900  }
901 
902  $this->popViewId = $previewPageId;
903  $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true);
904 
905  $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
906  return '
907  if (window.opener) {
908  '
910  $this->popViewId,
911  '',
912  $previewPageRootline,
913  '',
914  $this->viewUrl,
915  $this->popViewId_addParams,
916  false
917  )
918  . '
919  } else {
920  '
922  $this->popViewId,
923  '',
924  $previewPageRootline,
925  '',
926  $this->viewUrl,
927  $this->popViewId_addParams
928  )
929  . '
930  }';
931  }
932 
942  protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
943  {
944  foreach ($typoScript as $key => $value) {
945  if (is_array($value)) {
946  $key = rtrim($key, '.');
947  $parameters[$key] = [];
948  $this->parseAdditionalGetParameters($parameters[$key], $value);
949  } else {
950  $parameters[$key] = $value;
951  }
952  }
953  }
954 
960  public function main()
961  {
962  $body = '';
963  // Begin edit:
964  if (is_array($this->editconf)) {
966  $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
967 
968  if ($this->editRegularContentFromId) {
969  $this->editRegularContentFromId();
970  }
971  // Creating the editing form, wrap it with buttons, document selector etc.
972  $editForm = $this->makeEditForm();
973  if ($editForm) {
974  $this->firstEl = reset($this->elementsData);
975  // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
976  if (($this->docDat[1] !== $this->storeUrlMd5
977  || !isset($this->docHandler[$this->storeUrlMd5]))
978  && !$this->dontStoreDocumentRef
979  ) {
980  $this->docHandler[$this->storeUrlMd5] = array(
981  $this->storeTitle,
982  $this->storeArray,
983  $this->storeUrl,
984  $this->firstEl
985  );
986  $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler, $this->storeUrlMd5));
987  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
988  }
989  // Module configuration
990  $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig(
991  $this->viewId,
992  'mod.xMOD_alt_doc'
993  ) : array();
994  $body = $this->formResultCompiler->JStop();
995  $body .= $this->compileForm($editForm);
996  $body .= $this->formResultCompiler->printNeededJSFunctions();
997  $body .= '</form>';
998  }
999  }
1000  // Access check...
1001  // The page will show only if there is a valid page and if this page may be viewed by the user
1002  $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
1003  if ($this->pageinfo) {
1004  $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
1005  }
1006  // Setting up the buttons and markers for docheader
1007  $this->getButtons();
1008  $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
1009  $this->moduleTemplate->setContent($body);
1010  }
1011 
1018  public function printContent()
1019  {
1021  echo $this->content;
1022  }
1023 
1024  /***************************
1025  *
1026  * Sub-content functions, rendering specific parts of the module content.
1027  *
1028  ***************************/
1034  public function makeEditForm()
1035  {
1036  // Initialize variables:
1037  $this->elementsData = array();
1038  $this->errorC = 0;
1039  $this->newC = 0;
1040  $editForm = '';
1041  $trData = null;
1042  $beUser = $this->getBackendUser();
1043  // Traverse the GPvar edit array
1044  // Tables:
1045  foreach ($this->editconf as $table => $conf) {
1046  if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
1047  // Traverse the keys/comments of each table (keys can be a commalist of uids)
1048  foreach ($conf as $cKey => $command) {
1049  if ($command == 'edit' || $command == 'new') {
1050  // Get the ids:
1051  $ids = GeneralUtility::trimExplode(',', $cKey, true);
1052  // Traverse the ids:
1053  foreach ($ids as $theUid) {
1054  // Don't save this document title in the document selector if the document is new.
1055  if ($command === 'new') {
1056  $this->dontStoreDocumentRef = 1;
1057  }
1058 
1060  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1062  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
1064  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1065 
1066  try {
1067  // Reset viewId - it should hold data of last entry only
1068  $this->viewId = 0;
1069  $this->viewId_addParams = '';
1070 
1071  $formDataCompilerInput = [
1072  'tableName' => $table,
1073  'vanillaUid' => (int)$theUid,
1074  'command' => $command,
1075  'returnUrl' => $this->R_URI,
1076  ];
1077  if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1078  $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1079  }
1080 
1081  $formData = $formDataCompiler->compile($formDataCompilerInput);
1082 
1083  // Set this->viewId if possible
1084  if ($command === 'new'
1085  && $table !== 'pages'
1086  && !empty($formData['parentPageRow']['uid'])
1087  ) {
1088  $this->viewId = $formData['parentPageRow']['uid'];
1089  } else {
1090  if ($table == 'pages') {
1091  $this->viewId = $formData['databaseRow']['uid'];
1092  } elseif (!empty($formData['parentPageRow']['uid'])) {
1093  $this->viewId = $formData['parentPageRow']['uid'];
1094  // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1095  if (!empty($formData['processedTca']['ctrl']['languageField'])
1096  && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1097  && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1098  ) {
1099  $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1100  }
1101  }
1102  }
1103 
1104  // Determine if delete button can be shown
1105  $deleteAccess = false;
1106  if ($command === 'edit') {
1107  $permission = $formData['userPermissionOnPage'];
1108  if ($formData['tableName'] === 'pages') {
1109  $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1110  } else {
1111  $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1112  }
1113  }
1114 
1115  // Display "is-locked" message:
1116  if ($command === 'edit') {
1117  $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1118  if ($lockInfo) {
1120  $flashMessage = GeneralUtility::makeInstance(
1121  FlashMessage::class,
1122  htmlspecialchars($lockInfo['msg']),
1123  '',
1125  );
1127  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1129  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1130  $defaultFlashMessageQueue->enqueue($flashMessage);
1131  }
1132  }
1133 
1134  // Record title
1135  if (!$this->storeTitle) {
1136  $this->storeTitle = $this->recTitle
1137  ? htmlspecialchars($this->recTitle)
1138  : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
1139  }
1140 
1141  $this->elementsData[] = array(
1142  'table' => $table,
1143  'uid' => $formData['databaseRow']['uid'],
1144  'pid' => $formData['databaseRow']['pid'],
1145  'cmd' => $command,
1146  'deleteAccess' => $deleteAccess
1147  );
1148 
1149  if ($command !== 'new') {
1150  BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1151  }
1152 
1153  // Set list if only specific fields should be rendered. This will trigger
1154  // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1155  if ($this->columnsOnly) {
1156  if (is_array($this->columnsOnly)) {
1157  $formData['fieldListToRender'] = $this->columnsOnly[$table];
1158  } else {
1159  $formData['fieldListToRender'] = $this->columnsOnly;
1160  }
1161  }
1162 
1163  $formData['renderType'] = 'outerWrapContainer';
1164  $formResult = $nodeFactory->create($formData)->render();
1165 
1166  $html = $formResult['html'];
1167 
1168  $formResult['html'] = '';
1169  $formResult['doSaveFieldName'] = 'doSave';
1170 
1171  // @todo: Put all the stuff into FormEngine as final "compiler" class
1172  // @todo: This is done here for now to not rewrite JStop()
1173  // @todo: and printNeededJSFunctions() now
1174  $this->formResultCompiler->mergeResult($formResult);
1175 
1176  // Seems the pid is set as hidden field (again) at end?!
1177  if ($command == 'new') {
1178  // @todo: looks ugly
1179  $html .= LF
1180  . '<input type="hidden"'
1181  . ' name="data[' . $table . '][' . $formData['databaseRow']['uid'] . '][pid]"'
1182  . ' value="' . $formData['databaseRow']['pid'] . '" />';
1183  $this->newC++;
1184  }
1185 
1186  $editForm .= $html;
1187  } catch (AccessDeniedException $e) {
1188  $this->errorC++;
1189  // Try to fetch error message from "recordInternals" be user object
1190  // @todo: This construct should be logged and localized and de-uglified
1191  $message = $beUser->errorMsg;
1192  if (empty($message)) {
1193  // Create message from exception.
1194  $message = $e->getMessage() . ' ' . $e->getCode();
1195  }
1196  $editForm .= $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.noEditPermission', true)
1197  . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1198  }
1199  } // End of for each uid
1200  }
1201  }
1202  }
1203  }
1204  return $editForm;
1205  }
1206 
1212  protected function getButtons()
1213  {
1214  $lang = $this->getLanguageService();
1215  // Render SAVE type buttons:
1216  // The action of each button is decided by its name attribute. (See doProcessData())
1217  $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1218  if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1219  $saveSplitButton = $buttonBar->makeSplitButton();
1220  // SAVE button:
1221  $saveButton = $buttonBar->makeInputButton()
1222  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDoc', true))
1223  ->setName('_savedok')
1224  ->setValue('1')
1225  ->setForm('EditDocumentController')
1226  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
1227  $saveSplitButton->addItem($saveButton, true);
1228 
1229  // SAVE / VIEW button:
1230  if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1231  $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1232  if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1233  $excludeDokTypes = GeneralUtility::intExplode(
1234  ',',
1235  $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1236  true
1237  );
1238  } else {
1239  // exclude sysfolders, spacers and recycler by default
1240  $excludeDokTypes = array(
1244  );
1245  }
1246  if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1247  || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'].'.']['previewPageId'])
1248  ) {
1249  $saveAndOpenButton = $buttonBar->makeInputButton()
1250  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveDocShow', true))
1251  ->setName('_savedokview')
1252  ->setValue('1')
1253  ->setForm('EditDocumentController')
1254  ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1255  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1256  'actions-document-save-view',
1258  ));
1259  $saveSplitButton->addItem($saveAndOpenButton);
1260  }
1261  }
1262  // SAVE / NEW button:
1263  if (count($this->elementsData) === 1 && $this->getNewIconMode($this->firstEl['table'])) {
1264  $saveAndNewButton = $buttonBar->makeInputButton()
1265  ->setName('_savedoknew')
1266  ->setClasses('t3js-editform-submitButton')
1267  ->setValue('1')
1268  ->setForm('EditDocumentController')
1269  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveNewDoc', true))
1270  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1271  'actions-document-save-new',
1273  ));
1274  $saveSplitButton->addItem($saveAndNewButton);
1275  }
1276  // SAVE / CLOSE
1277  $saveAndCloseButton = $buttonBar->makeInputButton()
1278  ->setName('_saveandclosedok')
1279  ->setClasses('t3js-editform-submitButton')
1280  ->setValue('1')
1281  ->setForm('EditDocumentController')
1282  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.saveCloseDoc', true))
1283  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1284  'actions-document-save-close',
1286  ));
1287  $saveSplitButton->addItem($saveAndCloseButton);
1288  // FINISH TRANSLATION / SAVE / CLOSE
1289  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1290  $saveTranslationButton = $buttonBar->makeInputButton()
1291  ->setName('_translation_savedok')
1292  ->setValue('1')
1293  ->setForm('EditDocumentController')
1294  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDoc', true))
1295  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1296  'actions-document-save-cleartranslationcache',
1298  ));
1299  $saveSplitButton->addItem($saveTranslationButton);
1300  $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1301  ->setName('_translation_savedokclear')
1302  ->setValue('1')
1303  ->setForm('EditDocumentController')
1304  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.translationSaveDocClear', true))
1305  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1306  'actions-document-save-cleartranslationcache',
1308  ));
1309  $saveSplitButton->addItem($saveAndClearTranslationButton);
1310  }
1311  $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1312  }
1313  // CLOSE button:
1314  $closeButton = $buttonBar->makeLinkButton()
1315  ->setHref('#')
1316  ->setClasses('t3js-editform-close')
1317  ->setTitle($lang->sL('LLL:EXT:lang/locallang_core.xlf:rm.closeDoc', true))
1318  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1319  'actions-document-close',
1321  ));
1322  $buttonBar->addButton($closeButton);
1323  // DELETE + UNDO buttons:
1324  if (!$this->errorC
1325  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1326  && count($this->elementsData) === 1
1327  ) {
1328  if ($this->firstEl['cmd'] != 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1329  // Delete:
1330  if ($this->firstEl['deleteAccess']
1331  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1332  && !$this->getNewIconMode($this->firstEl['table'], 'disableDelete')
1333  ) {
1334  $deleteButton = $buttonBar->makeLinkButton()
1335  ->setHref('#')
1336  ->setClasses('t3js-editform-delete-record')
1337  ->setTitle($lang->getLL('deleteItem', true))
1338  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1339  'actions-edit-delete',
1341  ))
1342  ->setDataAttributes([
1343  'return-url' => $this->retUrl,
1344  'uid' => $this->firstEl['uid'],
1345  'table' => $this->firstEl['table']
1346  ]);
1347  $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1348  }
1349  // Undo:
1350  $undoRes = $this->getDatabaseConnection()->exec_SELECTquery(
1351  'tstamp',
1352  'sys_history',
1353  'tablename='
1354  . $this->getDatabaseConnection()->fullQuoteStr($this->firstEl['table'], 'sys_history')
1355  . ' AND recuid='
1356  . (int)$this->firstEl['uid'],
1357  '',
1358  'tstamp DESC',
1359  '1'
1360  );
1361  if ($undoButtonR = $this->getDatabaseConnection()->sql_fetch_assoc($undoRes)) {
1362  $aOnClick = 'window.location.href=' .
1364  BackendUtility::getModuleUrl(
1365  'record_history',
1366  array(
1367  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1368  'revert' => 'ALL_FIELDS',
1369  'sumUp' => -1,
1370  'returnUrl' => $this->R_URI,
1371  )
1372  )
1373  ) . '; return false;';
1374 
1375  $undoButton = $buttonBar->makeLinkButton()
1376  ->setHref('#')
1377  ->setOnClick(htmlspecialchars($aOnClick))
1378  ->setTitle(
1379  sprintf(
1380  $lang->getLL('undoLastChange'),
1382  ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1383  $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.minutesHoursDaysYears')
1384  )
1385  )
1386  )
1387  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1388  'actions-edit-undo',
1390  ));
1391  $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1392  }
1393  if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1394  $aOnClick = 'window.location.href=' .
1396  BackendUtility::getModuleUrl(
1397  'record_history',
1398  array(
1399  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1400  'returnUrl' => $this->R_URI,
1401  )
1402  )
1403  ) . '; return false;';
1404 
1405  $historyButton = $buttonBar->makeLinkButton()
1406  ->setHref('#')
1407  ->setOnClick($aOnClick)
1408  ->setTitle('Open history of this record')
1409  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1410  'actions-document-history-open',
1412  ));
1413  $buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1414  }
1415  // If only SOME fields are shown in the form, this will link the user to the FULL form:
1416  if ($this->columnsOnly) {
1417  $columnsOnlyButton = $buttonBar->makeLinkButton()
1418  ->setHref(htmlspecialchars(($this->R_URI . '&columnsOnly=')))
1419  ->setTitle($lang->getLL('editWholeRecord', true))
1420  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1421  'actions-document-open',
1423  ));
1424  $buttonBar->addButton($columnsOnlyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1425  }
1426  }
1427  }
1428  $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1429  $buttonBar->addButton($cshButton);
1430  $this->shortCutLink();
1431  $this->openInNewWindowLink();
1432  }
1433 
1440  public function compileForm($editForm)
1441  {
1442  $formContent = '
1443  <!-- EDITING FORM -->
1444  <form
1445  action="' . htmlspecialchars($this->R_URI) . '"
1446  method="post"
1447  enctype="multipart/form-data"
1448  name="editform"
1449  id="EditDocumentController"
1450  onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1451  ' . $editForm . '
1452 
1453  <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1454  <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1455  if ($this->returnNewPageId) {
1456  $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1457  }
1458  $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1459  if ($this->viewId_addParams) {
1460  $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1461  }
1462  $formContent .= '
1463  <input type="hidden" name="closeDoc" value="0" />
1464  <input type="hidden" name="doSave" value="0" />
1465  <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1466  <input type="hidden" name="_scrollPosition" value="" />';
1467  return $formContent;
1468  }
1469 
1473  public function shortCutLink()
1474  {
1475  if ($this->returnUrl !== 'sysext/backend/Resources/Private/Templates/Close.html') {
1476  $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1477  $shortCutButton->setModuleName($this->MCONF['name'])
1478  ->setGetVariables([
1479  'returnUrl',
1480  'edit',
1481  'defVals',
1482  'overrideVals',
1483  'columnsOnly',
1484  'returnNewPageId',
1485  'editRegularContentFromId',
1486  'noView']);
1487  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1488  }
1489  }
1490 
1494  public function openInNewWindowLink()
1495  {
1496  if ($this->returnUrl !== 'sysext/backend/Resources/Private/Templates/Close.html') {
1497  $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(
1498  array('returnUrl' => 'sysext/backend/Resources/Private/Templates/Close.html')
1499  ))
1500  . ','
1501  . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1502  . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1503  $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1504  ->makeLinkButton()
1505  ->setHref('#')
1506  ->setTitle($this->getLanguageService()->sL(
1507  'LLL:EXT:lang/locallang_core.xlf:labels.openInNewWindow',
1508  true
1509  ))
1510  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1511  ->setOnClick(htmlspecialchars($aOnClick));
1512  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
1513  $openInNewWindowButton,
1515  );
1516  }
1517  }
1518 
1519  /***************************
1520  *
1521  * Localization stuff
1522  *
1523  ***************************/
1533  public function languageSwitch($table, $uid, $pid = null)
1534  {
1535  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1536  $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1537  // Table editable and activated for languages?
1538  if ($this->getBackendUser()->check('tables_modify', $table)
1539  && $languageField
1540  && $transOrigPointerField && !$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerTable']
1541  ) {
1542  if (is_null($pid)) {
1543  $row = BackendUtility::getRecord($table, $uid, 'pid');
1544  $pid = $row['pid'];
1545  }
1546  // Get all available languages for the page
1547  $langRows = $this->getLanguages($pid);
1548  // Page available in other languages than default language?
1549  if (is_array($langRows) && count($langRows) > 1) {
1550  $rowsByLang = array();
1551  $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1552  // Get record in current language
1553  $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1554  if (!is_array($rowCurrent)) {
1555  $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1556  }
1557  $currentLanguage = (int)$rowCurrent[$languageField];
1558  // Disabled for records with [all] language!
1559  if ($currentLanguage > -1) {
1560  // Get record in default language if needed
1561  if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1562  $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1563  $table,
1564  $rowCurrent[$transOrigPointerField],
1565  $fetchFields
1566  );
1567  if (!is_array($rowsByLang[0])) {
1568  $rowsByLang[0] = BackendUtility::getRecord(
1569  $table,
1570  $rowCurrent[$transOrigPointerField],
1571  $fetchFields
1572  );
1573  }
1574  } else {
1575  $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1576  }
1577  if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1578  // Get record in other languages to see what's already available
1579  $translations = $this->getDatabaseConnection()->exec_SELECTgetRows(
1580  $fetchFields,
1581  $table,
1582  'pid=' . (int)$pid . ' AND ' . $languageField . '>0' . ' AND ' . $transOrigPointerField . '=' . (int)$rowsByLang[0]['uid'] . BackendUtility::deleteClause($table) . BackendUtility::versioningPlaceholderClause($table)
1583  );
1584  foreach ($translations as $row) {
1585  $rowsByLang[$row[$languageField]] = $row;
1586  }
1587  }
1588  $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1589  $languageMenu->setIdentifier('_langSelector');
1590  $languageMenu->setLabel($this->getLanguageService()->sL(
1591  'LLL:EXT:lang/locallang_general.xlf:LGL.language',
1592  true
1593  ));
1594  foreach ($langRows as $lang) {
1595  if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1596  $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_core.xlf:labels.new', true) . ']';
1597  // Create url for creating a localized record
1598  if ($newTranslation) {
1599  $redirectUrl = BackendUtility::getModuleUrl('record_edit', array(
1600  'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1601  'returnUrl' => $this->retUrl
1602  ));
1604  '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1605  $redirectUrl
1606  );
1607  } else {
1608  $href = BackendUtility::getModuleUrl('record_edit', array(
1609  'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1610  'returnUrl' => $this->retUrl
1611  ));
1612  }
1613  $menuItem = $languageMenu->makeMenuItem()
1614  ->setTitle($lang['title'] . $newTranslation)
1615  ->setHref($href);
1616  if ((int)$lang['uid'] === $currentLanguage) {
1617  $menuItem->setActive(true);
1618  }
1619  $languageMenu->addMenuItem($menuItem);
1620 
1621  }
1622  }
1623  $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1624  }
1625  }
1626  }
1627  }
1628 
1635  public function localizationRedirect($justLocalized)
1636  {
1637  list($table, $orig_uid, $language) = explode(':', $justLocalized);
1638  if ($GLOBALS['TCA'][$table]
1639  && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1640  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1641  ) {
1642  $localizedRecord = $this->getDatabaseConnection()->exec_SELECTgetSingleRow(
1643  'uid',
1644  $table,
1645  $GLOBALS['TCA'][$table]['ctrl']['languageField'] . '=' . (int)$language . ' AND '
1646  . $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] . '=' . (int)$orig_uid
1648  );
1649  if (is_array($localizedRecord)) {
1650  // Create parameters and finally run the classic page module for creating a new page translation
1651  $location = BackendUtility::getModuleUrl('record_edit', array(
1652  'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1653  'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1654  ));
1655  HttpUtility::redirect($location);
1656  }
1657  }
1658  }
1659 
1668  public function getLanguages($id)
1669  {
1670  $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1671  // Fallback non sprite-configuration
1672  if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1673  $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1674  '.gif',
1675  '',
1676  $modSharedTSconfig['properties']['defaultLanguageFlag']
1677  );
1678  }
1679  $languages = array(
1680  0 => array(
1681  'uid' => 0,
1682  'pid' => 0,
1683  'hidden' => 0,
1684  'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1685  ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1686  : $this->getLanguageService()->sL('LLL:EXT:lang/locallang_mod_web_list.xlf:defaultLanguage'),
1687  'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1688  )
1689  );
1690  $exQ = $this->getBackendUser()->isAdmin() ? '' : ' AND sys_language.hidden=0';
1691  if ($id) {
1692  $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1693  'sys_language.*',
1694  'pages_language_overlay,sys_language',
1695  'pages_language_overlay.sys_language_uid=sys_language.uid
1696  AND pages_language_overlay.pid=' . (int)$id . BackendUtility::deleteClause('pages_language_overlay')
1697  . $exQ,
1698  'pages_language_overlay.sys_language_uid,
1699  sys_language.uid,
1700  sys_language.pid,
1701  sys_language.tstamp,
1702  sys_language.hidden,
1703  sys_language.title,
1704  sys_language.language_isocode,
1705  sys_language.static_lang_isocode,
1706  sys_language.flag',
1707  'sys_language.title'
1708  );
1709  } else {
1710  $rows = $this->getDatabaseConnection()->exec_SELECTgetRows(
1711  'sys_language.*',
1712  'sys_language',
1713  'sys_language.hidden=0',
1714  '',
1715  'sys_language.title'
1716  );
1717  }
1718  if ($rows) {
1719  foreach ($rows as $row) {
1720  $languages[$row['uid']] = $row;
1721  }
1722  }
1723  return $languages;
1724  }
1725 
1726  /***************************
1727  *
1728  * Other functions
1729  *
1730  ***************************/
1737  public function fixWSversioningInEditConf($mapArray = false)
1738  {
1739  // Traverse the editConf array
1740  if (is_array($this->editconf)) {
1741  // Tables:
1742  foreach ($this->editconf as $table => $conf) {
1743  if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1744  // Traverse the keys/comments of each table (keys can be a commalist of uids)
1745  $newConf = array();
1746  foreach ($conf as $cKey => $cmd) {
1747  if ($cmd == 'edit') {
1748  // Traverse the ids:
1749  $ids = GeneralUtility::trimExplode(',', $cKey, true);
1750  foreach ($ids as $idKey => $theUid) {
1751  if (is_array($mapArray)) {
1752  if ($mapArray[$table][$theUid]) {
1753  $ids[$idKey] = $mapArray[$table][$theUid];
1754  }
1755  } else {
1756  // Default, look for versions in workspace for record:
1757  $calcPRec = $this->getRecordForEdit($table, $theUid);
1758  if (is_array($calcPRec)) {
1759  // Setting UID again if it had changed, eg. due to workspace versioning.
1760  $ids[$idKey] = $calcPRec['uid'];
1761  }
1762  }
1763  }
1764  // Add the possibly manipulated IDs to the new-build newConf array:
1765  $newConf[implode(',', $ids)] = $cmd;
1766  } else {
1767  $newConf[$cKey] = $cmd;
1768  }
1769  }
1770  // Store the new conf array:
1771  $this->editconf[$table] = $newConf;
1772  }
1773  }
1774  }
1775  }
1776 
1784  public function getRecordForEdit($table, $theUid)
1785  {
1786  // Fetch requested record:
1787  $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1788  if (is_array($reqRecord)) {
1789  // If workspace is OFFLINE:
1790  if ($this->getBackendUser()->workspace != 0) {
1791  // Check for versioning support of the table:
1792  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1793  // If the record is already a version of "something" pass it by.
1794  if ($reqRecord['pid'] == -1) {
1795  // (If it turns out not to be a version of the current workspace there will be trouble, but
1796  // that is handled inside TCEmain then and in the interface it would clearly be an error of
1797  // links if the user accesses such a scenario)
1798  return $reqRecord;
1799  } else {
1800  // The input record was online and an offline version must be found or made:
1801  // Look for version of this workspace:
1803  $this->getBackendUser()->workspace,
1804  $table,
1805  $reqRecord['uid'],
1806  'uid,pid,t3ver_oid'
1807  );
1808  return is_array($versionRec) ? $versionRec : $reqRecord;
1809  }
1810  } else {
1811  // This means that editing cannot occur on this record because it was not supporting versioning
1812  // which is required inside an offline workspace.
1813  return false;
1814  }
1815  } else {
1816  // In ONLINE workspace, just return the originally requested record:
1817  return $reqRecord;
1818  }
1819  } else {
1820  // Return FALSE because the table/uid was not found anyway.
1821  return false;
1822  }
1823  }
1824 
1832  public function editRegularContentFromId()
1833  {
1835  $dbConnection = $this->getDatabaseConnection();
1836  $res = $dbConnection->exec_SELECTquery(
1837  'uid',
1838  'tt_content',
1839  'pid=' . (int)$this->editRegularContentFromId . BackendUtility::deleteClause('tt_content')
1840  . BackendUtility::versioningPlaceholderClause('tt_content') . ' AND colPos=0 AND sys_language_uid=0',
1841  '',
1842  'sorting'
1843  );
1844  if ($dbConnection->sql_num_rows($res)) {
1845  $ecUids = array();
1846  while ($ecRec = $dbConnection->sql_fetch_assoc($res)) {
1847  $ecUids[] = $ecRec['uid'];
1848  }
1849  $this->editconf['tt_content'][implode(',', $ecUids)] = 'edit';
1850  }
1851  $dbConnection->sql_free_result($res);
1852  }
1853 
1860  public function compileStoreDat()
1861  {
1863  'edit,defVals,overrideVals,columnsOnly,noView,editRegularContentFromId,workspace',
1864  $this->R_URL_getvars
1865  );
1866  $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1867  $this->storeUrlMd5 = md5($this->storeUrl);
1868  }
1869 
1878  public function getNewIconMode($table, $key = 'saveDocNew')
1879  {
1880  $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
1881  $output = trim(isset($TSconfig['properties'][$table]) ? $TSconfig['properties'][$table] : $TSconfig['value']);
1882  return $output;
1883  }
1884 
1892  public function closeDocument($code = 0)
1893  {
1894  // If current document is found in docHandler,
1895  // then unset it, possibly unset it ALL and finally, write it to the session data
1896  if (isset($this->docHandler[$this->storeUrlMd5])) {
1897  // add the closing document to the recent documents
1898  $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
1899  if (!is_array($recentDocs)) {
1900  $recentDocs = array();
1901  }
1902  $closedDoc = $this->docHandler[$this->storeUrlMd5];
1903  $recentDocs = array_merge(array($this->storeUrlMd5 => $closedDoc), $recentDocs);
1904  if (count($recentDocs) > 8) {
1905  $recentDocs = array_slice($recentDocs, 0, 8);
1906  }
1907  // remove it from the list of the open documents
1908  unset($this->docHandler[$this->storeUrlMd5]);
1909  if ($code == '3') {
1910  $recentDocs = array_merge($this->docHandler, $recentDocs);
1911  $this->docHandler = array();
1912  }
1913  $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
1914  $this->getBackendUser()->pushModuleData('FormEngine', array($this->docHandler, $this->docDat[1]));
1915  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
1916  }
1917  // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
1918  // other scripts, like wizard_add, to know which records was created or so...)
1919  if ($this->returnEditConf && $this->retUrl != BackendUtility::getModuleUrl('dummy')) {
1920  $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
1921  }
1922  // If code is NOT set OR set to 1, then make a header location redirect to $this->retUrl
1923  if (!$code || $code == 1) {
1924  HttpUtility::redirect($this->retUrl);
1925  } else {
1926  $this->setDocument('', $this->retUrl);
1927  }
1928  }
1929 
1939  public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
1940  {
1941  if ($retUrl === '') {
1942  return;
1943  }
1944  if (!$this->modTSconfig['properties']['disableDocSelector']
1945  && is_array($this->docHandler)
1946  && !empty($this->docHandler)
1947  ) {
1948  if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
1949  $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
1950  } else {
1951  $setupArr = reset($this->docHandler);
1952  }
1953  if ($setupArr[2]) {
1954  $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
1955  $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
1956  }
1957  }
1959  }
1960 
1969  {
1971 
1972  // Preprocessing, storing data if submitted to
1973  $this->preInit();
1974 
1975  // Checks, if a save button has been clicked (or the doSave variable is sent)
1976  if ($this->doProcessData()) {
1977  $this->processData();
1978  }
1979 
1980  $this->init();
1981  $this->main();
1982 
1983  $response->getBody()->write($this->moduleTemplate->renderContent());
1984  return $response;
1985  }
1986 
1990  protected function getBackendUser()
1991  {
1992  return $GLOBALS['BE_USER'];
1993  }
1994 
2000  protected function getLanguageService()
2001  {
2002  return $GLOBALS['LANG'];
2003  }
2004 
2010  protected function getDatabaseConnection()
2011  {
2012  return $GLOBALS['TYPO3_DB'];
2013  }
2014 }