TYPO3 CMS  TYPO3_8-7
EditDocumentController.php
Go to the documentation of this file.
1 <?php
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
47 
53 {
55  const DOCUMENT_CLOSE_MODE_REDIRECT = 1; // works like DOCUMENT_CLOSE_MODE_DEFAULT
58 
66  public $editconf;
67 
75  public $columnsOnly;
76 
83  public $defVals;
84 
91  public $overrideVals;
92 
99  public $returnUrl;
100 
106  public $closeDoc;
107 
115  public $doSave;
116 
122  public $data;
123 
127  public $cmd;
128 
132  public $mirror;
133 
139  public $cacheCmd;
140 
146  public $redirect;
147 
155 
161  public $uc;
162 
168  public $popViewId;
169 
176 
182  public $viewUrl;
183 
189  public $recTitle;
190 
196  public $noView;
197 
202 
210 
216 
222  protected $workspace;
223 
229  public $doc;
230 
236  public $template;
237 
243  public $content;
244 
252  public $retUrl;
253 
261  public $R_URL_parts;
262 
270 
276  public $R_URI;
277 
281  public $MCONF;
282 
286  public $pageinfo;
287 
294  public $storeTitle = '';
295 
302  public $storeArray;
303 
309  public $storeUrl;
310 
316  public $storeUrlMd5;
317 
323  public $docDat;
324 
333  public $docHandler;
334 
341 
347  public $firstEl;
348 
354  public $errorC;
355 
361  public $newC;
362 
369  public $viewId;
370 
377 
383  public $modTSconfig;
384 
389 
396 
401 
407  protected $previewData = [];
408 
412  public function __construct()
413  {
414  parent::__construct();
415  $this->moduleTemplate->setUiBlock(true);
416  $GLOBALS['SOBE'] = $this;
417  $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf');
418  }
419 
425  protected function getSignalSlotDispatcher()
426  {
427  if (!isset($this->signalSlotDispatcher)) {
428  $this->signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class);
429  }
431  }
432 
438  protected function emitFunctionAfterSignal($signalName)
439  {
440  $this->getSignalSlotDispatcher()->dispatch(__CLASS__, $signalName . 'After', [$this]);
441  }
442 
446  public function preInit()
447  {
448  if (GeneralUtility::_GP('justLocalized')) {
449  $this->localizationRedirect(GeneralUtility::_GP('justLocalized'));
450  }
451  // Setting GPvars:
452  $this->editconf = GeneralUtility::_GP('edit');
453  $this->defVals = GeneralUtility::_GP('defVals');
454  $this->overrideVals = GeneralUtility::_GP('overrideVals');
455  $this->columnsOnly = GeneralUtility::_GP('columnsOnly');
456  $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'));
457  $this->closeDoc = (int)GeneralUtility::_GP('closeDoc');
458  $this->doSave = GeneralUtility::_GP('doSave');
459  $this->returnEditConf = GeneralUtility::_GP('returnEditConf');
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'])
511  || isset($_POST['_translation_savedokclear']);
512  return $out;
513  }
514 
518  public function processData()
519  {
520  $beUser = $this->getBackendUser();
521  // GPvars specifically for processing:
522  $control = GeneralUtility::_GP('control');
523  $this->data = GeneralUtility::_GP('data');
524  $this->cmd = GeneralUtility::_GP('cmd');
525  $this->mirror = GeneralUtility::_GP('mirror');
526  $this->cacheCmd = GeneralUtility::_GP('cacheCmd');
527  $this->redirect = GeneralUtility::_GP('redirect');
528  $this->returnNewPageId = GeneralUtility::_GP('returnNewPageId');
529  // See tce_db.php for relevate options here:
530  // Only options related to $this->data submission are included here.
532  $tce = GeneralUtility::makeInstance(DataHandler::class);
533 
534  if (!empty($control)) {
535  $tce->setControl($control);
536  }
537  if (isset($_POST['_translation_savedok'])) {
538  $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
539  }
540  if (isset($_POST['_translation_savedokclear'])) {
541  $tce->updateModeL10NdiffData = 'FORCE_FFUPD';
542  $tce->updateModeL10NdiffDataClear = true;
543  }
544  // Setting default values specific for the user:
545  $TCAdefaultOverride = $beUser->getTSConfigProp('TCAdefaults');
546  if (is_array($TCAdefaultOverride)) {
547  $tce->setDefaultsFromUserTS($TCAdefaultOverride);
548  }
549  // Setting internal vars:
550  if ($beUser->uc['neverHideAtCopy']) {
551  $tce->neverHideAtCopy = 1;
552  }
553  // Loading DataHandler with data:
554  $tce->start($this->data, $this->cmd);
555  if (is_array($this->mirror)) {
556  $tce->setMirror($this->mirror);
557  }
558  // Perform the saving operation with DataHandler:
559  $tce->process_uploads($_FILES);
560  $tce->process_datamap();
561  $tce->process_cmdmap();
562  // If pages are being edited, we set an instruction about updating the page tree after this operation.
563  if ($tce->pagetreeNeedsRefresh
564  && (isset($this->data['pages']) || $beUser->workspace != 0 && !empty($this->data))
565  ) {
566  BackendUtility::setUpdateSignal('updatePageTree');
567  }
568  // If there was saved any new items, load them:
569  if (!empty($tce->substNEWwithIDs_table)) {
570  // save the expanded/collapsed states for new inline records, if any
571  FormEngineUtility::updateInlineView($this->uc, $tce);
572  $newEditConf = [];
573  foreach ($this->editconf as $tableName => $tableCmds) {
574  $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
575  if (!empty($keys)) {
576  foreach ($keys as $key) {
577  $editId = $tce->substNEWwithIDs[$key];
578  // Check if the $editId isn't a child record of an IRRE action
579  if (!(is_array($tce->newRelatedIDs[$tableName])
580  && in_array($editId, $tce->newRelatedIDs[$tableName]))
581  ) {
582  // Translate new id to the workspace version:
584  $beUser->workspace,
585  $tableName,
586  $editId,
587  'uid'
588  )) {
589  $editId = $versionRec['uid'];
590  }
591  $newEditConf[$tableName][$editId] = 'edit';
592  }
593  // Traverse all new records and forge the content of ->editconf so we can continue to EDIT
594  // these records!
595  if ($tableName === 'pages'
596  && $this->retUrl != BackendUtility::getModuleUrl('dummy')
597  && $this->returnNewPageId
598  ) {
599  $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
600  }
601  }
602  } else {
603  $newEditConf[$tableName] = $tableCmds;
604  }
605  }
606  // Resetting editconf if newEditConf has values:
607  if (!empty($newEditConf)) {
608  $this->editconf = $newEditConf;
609  }
610  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
611  $this->R_URL_getvars['edit'] = $this->editconf;
612  // Unsetting default values since we don't need them anymore.
613  unset($this->R_URL_getvars['defVals']);
614  // Re-compile the store* values since editconf changed...
615  $this->compileStoreDat();
616  }
617  // See if any records was auto-created as new versions?
618  if (!empty($tce->autoVersionIdMap)) {
619  $this->fixWSversioningInEditConf($tce->autoVersionIdMap);
620  }
621  // If a document is saved and a new one is created right after.
622  if (isset($_POST['_savedoknew']) && is_array($this->editconf)) {
623  $this->closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT);
624  // Finding the current table:
625  reset($this->editconf);
626  $nTable = key($this->editconf);
627  // Finding the first id, getting the records pid+uid
628  reset($this->editconf[$nTable]);
629  $nUid = key($this->editconf[$nTable]);
630  $recordFields = 'pid,uid';
631  if (!empty($GLOBALS['TCA'][$nTable]['ctrl']['versioningWS'])) {
632  $recordFields .= ',t3ver_oid';
633  }
634  $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
635  // Determine insertion mode ('top' is self-explaining,
636  // otherwise new elements are inserted after one using a negative uid)
637  $insertRecordOnTop = ($this->getNewIconMode($nTable) === 'top');
638  // Setting a blank editconf array for a new record:
639  $this->editconf = [];
640  // Determine related page ID for regular live context
641  if ($nRec['pid'] != -1) {
642  if ($insertRecordOnTop) {
643  $relatedPageId = $nRec['pid'];
644  } else {
645  $relatedPageId = -$nRec['uid'];
646  }
647  } else {
648  // Determine related page ID for workspace context
649  if ($insertRecordOnTop) {
650  // Fetch live version of workspace version since the pid value is always -1 in workspaces
651  $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
652  $relatedPageId = $liveRecord['pid'];
653  } else {
654  // Use uid of live version of workspace version
655  $relatedPageId = -$nRec['t3ver_oid'];
656  }
657  }
658  $this->editconf[$nTable][$relatedPageId] = 'new';
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();
677  // || count($tce->substNEWwithIDs)... If any new items has been save, the document is CLOSED
678  // because if not, we just get that element re-listed as new. And we don't want that!
679  if ((int)$this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
680  || isset($_POST['_saveandclosedok'])
681  || isset($_POST['_translation_savedok'])
682  ) {
683  $this->closeDocument(abs($this->closeDoc));
684  }
685  }
686 
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->recTitle = GeneralUtility::_GP('recTitle');
698  $this->noView = GeneralUtility::_GP('noView');
699  $this->perms_clause = $beUser->getPagePermsClause(Permission::PAGE_SHOW);
700  // Set other internal variables:
701  $this->R_URL_getvars['returnUrl'] = $this->retUrl;
702  $this->R_URI = $this->R_URL_parts['path'] . '?' . ltrim(GeneralUtility::implodeArrayForUrl(
703  '',
704  $this->R_URL_getvars
705  ), '&');
706  // Setting virtual document name
707  $this->MCONF['name'] = 'xMOD_alt_doc.php';
708 
709  // Create an instance of the document template object
710  $this->doc = $GLOBALS['TBE_TEMPLATE'];
711  $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
712  $pageRenderer->addInlineLanguageLabelFile('EXT:lang/Resources/Private/Language/locallang_alt_doc.xlf');
713  // override the default jumpToUrl
714  $this->moduleTemplate->addJavaScriptCode(
715  'jumpToUrl',
716  '
717  function jumpToUrl(URL,formEl) {
718  if (!TBE_EDITOR.isFormChanged()) {
719  window.location.href = URL;
720  } else if (formEl && formEl.type=="checkbox") {
721  formEl.checked = formEl.checked ? 0 : 1;
722  }
723  }
724 '
725  );
726  $t3Configuration = [];
727 
728  $javascript = '
729  TYPO3.configuration = ' . json_encode($t3Configuration) . ';
730  // Object: TS:
731  // TS object overwrites the object declared in tbe_editor.js
732  function typoSetup() { //
733  this.uniqueID = "";
734  }
735  var TS = new typoSetup();
736 
737  // Info view:
738  function launchView(table,uid) { //
739  var thePreviewWindow = window.open(
740  ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('show_item') . '&table=') . ' + encodeURIComponent(table) + "&uid=" + encodeURIComponent(uid),
741  "ShowItem" + TS.uniqueID,
742  "height=300,width=410,status=0,menubar=0,resizable=0,location=0,directories=0,scrollbars=1,toolbar=0"
743  );
744  if (thePreviewWindow && thePreviewWindow.focus) {
745  thePreviewWindow.focus();
746  }
747  }
748  function deleteRecord(table,id,url) { //
749  window.location.href = ' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('tce_db') . '&cmd[') . '+table+"]["+id+"][delete]=1&redirect="+escape(url)+"&prErr=1&uPT=1";
750  }
751  ';
752 
753  $previewCode = isset($_POST['_savedokview']) && $this->popViewId ? $this->generatePreviewCode() : '';
754  $this->moduleTemplate->addJavaScriptCode(
755  'PreviewCode',
756  $javascript . $previewCode
757  );
758  // Setting up the context sensitive menu:
759  $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
760 
761  $this->emitFunctionAfterSignal(__FUNCTION__);
762  }
763 
767  protected function generatePreviewCode()
768  {
769  $table = $this->previewData['table'];
770  $recordId = $this->previewData['id'];
771 
772  if ($table === 'pages') {
773  $currentPageId = $recordId;
774  } else {
775  $currentPageId = MathUtility::convertToPositiveInteger($this->popViewId);
776  }
777 
778  $pageTsConfig = BackendUtility::getPagesTSconfig($currentPageId);
779  $previewConfiguration = isset($pageTsConfig['TCEMAIN.']['preview.'][$table . '.'])
780  ? $pageTsConfig['TCEMAIN.']['preview.'][$table . '.']
781  : [];
782 
783  $recordArray = BackendUtility::getRecord($table, $recordId);
784 
785  // find the right preview page id
786  $previewPageId = 0;
787  if (isset($previewConfiguration['previewPageId'])) {
788  $previewPageId = $previewConfiguration['previewPageId'];
789  }
790  // if no preview page was configured
791  if (!$previewPageId) {
792  $rootPageData = null;
793  $rootLine = BackendUtility::BEgetRootLine($currentPageId);
794  $currentPage = reset($rootLine);
795  // Allow all doktypes below 200
796  // This makes custom doktype work as well with opening a frontend page.
797  if ((int)$currentPage['doktype'] <= PageRepository::DOKTYPE_SPACER) {
798  // try the current page
799  $previewPageId = $currentPageId;
800  } else {
801  // or search for the root page
802  foreach ($rootLine as $page) {
803  if ($page['is_siteroot']) {
804  $rootPageData = $page;
805  break;
806  }
807  }
808  $previewPageId = isset($rootPageData)
809  ? (int)$rootPageData['uid']
810  : $currentPageId;
811  }
812  }
813 
814  $linkParameters = [];
815 
816  // language handling
817  $languageField = isset($GLOBALS['TCA'][$table]['ctrl']['languageField'])
818  ? $GLOBALS['TCA'][$table]['ctrl']['languageField']
819  : '';
820  if ($languageField && !empty($recordArray[$languageField])) {
821  $l18nPointer = isset($GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'])
822  ? $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
823  : '';
824  if ($l18nPointer && !empty($recordArray[$l18nPointer])
825  && isset($previewConfiguration['useDefaultLanguageRecord'])
826  && !$previewConfiguration['useDefaultLanguageRecord']
827  ) {
828  // use parent record
829  $recordId = $recordArray[$l18nPointer];
830  }
831  $linkParameters['L'] = $recordArray[$languageField];
832  }
833 
834  // map record data to GET parameters
835  if (isset($previewConfiguration['fieldToParameterMap.'])) {
836  foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
837  $value = $recordArray[$field];
838  if ($field === 'uid') {
839  $value = $recordId;
840  }
841  $linkParameters[$parameterName] = $value;
842  }
843  }
844 
845  // add/override parameters by configuration
846  if (isset($previewConfiguration['additionalGetParameters.'])) {
847  $additionalGetParameters = [];
849  $additionalGetParameters,
850  $previewConfiguration['additionalGetParameters.']
851  );
852  $linkParameters = array_replace($linkParameters, $additionalGetParameters);
853  }
854 
855  if (!empty($previewConfiguration['useCacheHash'])) {
857  $cacheHashCalculator = GeneralUtility::makeInstance(CacheHashCalculator::class);
858  $fullLinkParameters = GeneralUtility::implodeArrayForUrl('', array_merge($linkParameters, ['id' => $previewPageId]));
859  $cacheHashParameters = $cacheHashCalculator->getRelevantParameters($fullLinkParameters);
860  $linkParameters['cHash'] = $cacheHashCalculator->calculateCacheHash($cacheHashParameters);
861  } else {
862  $linkParameters['no_cache'] = 1;
863  }
864 
865  $this->popViewId = $previewPageId;
866  $this->popViewId_addParams = GeneralUtility::implodeArrayForUrl('', $linkParameters, '', false, true);
867 
868  $previewPageRootline = BackendUtility::BEgetRootLine($this->popViewId);
869  return '
870  if (window.opener) {
871  '
873  $this->popViewId,
874  '',
875  $previewPageRootline,
876  '',
877  $this->viewUrl,
878  $this->popViewId_addParams,
879  false
880  )
881  . '
882  } else {
883  '
885  $this->popViewId,
886  '',
887  $previewPageRootline,
888  '',
889  $this->viewUrl,
890  $this->popViewId_addParams
891  )
892  . '
893  }';
894  }
895 
905  protected function parseAdditionalGetParameters(array &$parameters, array $typoScript)
906  {
907  foreach ($typoScript as $key => $value) {
908  if (is_array($value)) {
909  $key = rtrim($key, '.');
910  $parameters[$key] = [];
911  $this->parseAdditionalGetParameters($parameters[$key], $value);
912  } else {
913  $parameters[$key] = $value;
914  }
915  }
916  }
917 
921  public function main()
922  {
923  $body = '';
924  // Begin edit:
925  if (is_array($this->editconf)) {
927  $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
928 
929  // Creating the editing form, wrap it with buttons, document selector etc.
930  $editForm = $this->makeEditForm();
931  if ($editForm) {
932  $this->firstEl = reset($this->elementsData);
933  // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
934  if (($this->docDat[1] !== $this->storeUrlMd5
935  || !isset($this->docHandler[$this->storeUrlMd5]))
936  && !$this->dontStoreDocumentRef
937  ) {
938  $this->docHandler[$this->storeUrlMd5] = [
943  ];
944  $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->storeUrlMd5]);
945  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
946  }
947  // Module configuration
948  $this->modTSconfig = $this->viewId ? BackendUtility::getModTSconfig(
949  $this->viewId,
950  'mod.xMOD_alt_doc'
951  ) : [];
952  $body = $this->formResultCompiler->addCssFiles();
953  $body .= $this->compileForm($editForm);
954  $body .= $this->formResultCompiler->printNeededJSFunctions();
955  $body .= '</form>';
956  }
957  }
958  // Access check...
959  // The page will show only if there is a valid page and if this page may be viewed by the user
960  $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause);
961  if ($this->pageinfo) {
962  $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
963  }
964  // Setting up the buttons and markers for docheader
965  $this->getButtons();
966  $this->languageSwitch($this->firstEl['table'], $this->firstEl['uid'], $this->firstEl['pid']);
967  $this->moduleTemplate->setContent($body);
968  }
969 
970  /***************************
971  *
972  * Sub-content functions, rendering specific parts of the module content.
973  *
974  ***************************/
980  public function makeEditForm()
981  {
982  // Initialize variables:
983  $this->elementsData = [];
984  $this->errorC = 0;
985  $this->newC = 0;
986  $editForm = '';
987  $trData = null;
988  $beUser = $this->getBackendUser();
989  // Traverse the GPvar edit array
990  // Tables:
991  foreach ($this->editconf as $table => $conf) {
992  if (is_array($conf) && $GLOBALS['TCA'][$table] && $beUser->check('tables_modify', $table)) {
993  // Traverse the keys/comments of each table (keys can be a commalist of uids)
994  foreach ($conf as $cKey => $command) {
995  if ($command === 'edit' || $command === 'new') {
996  // Get the ids:
997  $ids = GeneralUtility::trimExplode(',', $cKey, true);
998  // Traverse the ids:
999  foreach ($ids as $theUid) {
1000  // Don't save this document title in the document selector if the document is new.
1001  if ($command === 'new') {
1002  $this->dontStoreDocumentRef = 1;
1003  }
1004 
1005  try {
1006  $formDataGroup = GeneralUtility::makeInstance(TcaDatabaseRecord::class);
1007  $formDataCompiler = GeneralUtility::makeInstance(FormDataCompiler::class, $formDataGroup);
1008  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1009 
1010  // Reset viewId - it should hold data of last entry only
1011  $this->viewId = 0;
1012  $this->viewId_addParams = '';
1013 
1014  $formDataCompilerInput = [
1015  'tableName' => $table,
1016  'vanillaUid' => (int)$theUid,
1017  'command' => $command,
1018  'returnUrl' => $this->R_URI,
1019  ];
1020  if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1021  $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1022  }
1023 
1024  $formData = $formDataCompiler->compile($formDataCompilerInput);
1025 
1026  // Set this->viewId if possible
1027  if ($command === 'new'
1028  && $table !== 'pages'
1029  && !empty($formData['parentPageRow']['uid'])
1030  ) {
1031  $this->viewId = $formData['parentPageRow']['uid'];
1032  } else {
1033  if ($table === 'pages') {
1034  $this->viewId = $formData['databaseRow']['uid'];
1035  } elseif (!empty($formData['parentPageRow']['uid'])) {
1036  $this->viewId = $formData['parentPageRow']['uid'];
1037  // Adding "&L=xx" if the record being edited has a languageField with a value larger than zero!
1038  if (!empty($formData['processedTca']['ctrl']['languageField'])
1039  && is_array($formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']])
1040  && $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0] > 0
1041  ) {
1042  $this->viewId_addParams = '&L=' . $formData['databaseRow'][$formData['processedTca']['ctrl']['languageField']][0];
1043  }
1044  }
1045  }
1046 
1047  // Determine if delete button can be shown
1048  $deleteAccess = false;
1049  if ($command === 'edit') {
1050  $permission = $formData['userPermissionOnPage'];
1051  if ($formData['tableName'] === 'pages') {
1052  $deleteAccess = $permission & Permission::PAGE_DELETE ? true : false;
1053  } else {
1054  $deleteAccess = $permission & Permission::CONTENT_EDIT ? true : false;
1055  }
1056  }
1057 
1058  // Display "is-locked" message:
1059  if ($command === 'edit') {
1060  $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1061  if ($lockInfo) {
1063  $flashMessage = GeneralUtility::makeInstance(
1064  FlashMessage::class,
1065  $lockInfo['msg'],
1066  '',
1068  );
1070  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
1072  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1073  $defaultFlashMessageQueue->enqueue($flashMessage);
1074  }
1075  }
1076 
1077  // Record title
1078  if (!$this->storeTitle) {
1079  $this->storeTitle = $this->recTitle
1080  ? htmlspecialchars($this->recTitle)
1081  : BackendUtility::getRecordTitle($table, FormEngineUtility::databaseRowCompatibility($formData['databaseRow']), true);
1082  }
1083 
1084  $this->elementsData[] = [
1085  'table' => $table,
1086  'uid' => $formData['databaseRow']['uid'],
1087  'pid' => $formData['databaseRow']['pid'],
1088  'cmd' => $command,
1089  'deleteAccess' => $deleteAccess
1090  ];
1091 
1092  if ($command !== 'new') {
1093  BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1094  }
1095 
1096  // Set list if only specific fields should be rendered. This will trigger
1097  // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1098  if ($this->columnsOnly) {
1099  if (is_array($this->columnsOnly)) {
1100  $formData['fieldListToRender'] = $this->columnsOnly[$table];
1101  } else {
1102  $formData['fieldListToRender'] = $this->columnsOnly;
1103  }
1104  }
1105 
1106  $formData['renderType'] = 'outerWrapContainer';
1107  $formResult = $nodeFactory->create($formData)->render();
1108 
1109  $html = $formResult['html'];
1110 
1111  $formResult['html'] = '';
1112  $formResult['doSaveFieldName'] = 'doSave';
1113 
1114  // @todo: Put all the stuff into FormEngine as final "compiler" class
1115  // @todo: This is done here for now to not rewrite addCssFiles()
1116  // @todo: and printNeededJSFunctions() now
1117  $this->formResultCompiler->mergeResult($formResult);
1118 
1119  // Seems the pid is set as hidden field (again) at end?!
1120  if ($command === 'new') {
1121  // @todo: looks ugly
1122  $html .= LF
1123  . '<input type="hidden"'
1124  . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1125  . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1126  $this->newC++;
1127  }
1128 
1129  $editForm .= $html;
1130  } catch (AccessDeniedException $e) {
1131  $this->errorC++;
1132  // Try to fetch error message from "recordInternals" be user object
1133  // @todo: This construct should be logged and localized and de-uglified
1134  $message = $beUser->errorMsg;
1135  if (empty($message)) {
1136  // Create message from exception.
1137  $message = $e->getMessage() . ' ' . $e->getCode();
1138  }
1139  $editForm .= htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.noEditPermission'))
1140  . '<br /><br />' . htmlspecialchars($message) . '<br /><br />';
1141  } catch (DatabaseRecordException $e) {
1142  $editForm = '<div class="alert alert-warning">' . htmlspecialchars($e->getMessage()) . '</div>';
1143  }
1144  } // End of for each uid
1145  }
1146  }
1147  }
1148  }
1149  return $editForm;
1150  }
1151 
1157  protected function getButtons()
1158  {
1159  $lang = $this->getLanguageService();
1160  $isSavedRecord = (
1161  $this->firstEl['cmd'] !== 'new'
1162  && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])
1163  );
1164 
1165  $record = BackendUtility::getRecord($this->firstEl['table'], $this->firstEl['uid']);
1166  $TCActrl = $GLOBALS['TCA'][$this->firstEl['table']]['ctrl'];
1167 
1168  $sysLanguageUid = 0;
1169  if ($isSavedRecord
1170  && isset($TCActrl['languageField'], $record[$TCActrl['languageField']])) {
1171  $sysLanguageUid = (int)$record[$TCActrl['languageField']];
1172  } elseif (isset($this->defVals['sys_language_uid'])) {
1173  $sysLanguageUid = (int)$this->defVals['sys_language_uid'];
1174  }
1175 
1176  // Render SAVE type buttons:
1177  // The action of each button is decided by its name attribute. (See doProcessData())
1178  $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
1179  if (!$this->errorC && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']) {
1180  $saveSplitButton = $buttonBar->makeSplitButton();
1181  // SAVE button:
1182  $saveButton = $buttonBar->makeInputButton()
1183  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
1184  ->setName('_savedok')
1185  ->setValue('1')
1186  ->setForm('EditDocumentController')
1187  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL));
1188  $saveSplitButton->addItem($saveButton, true);
1189 
1190  // SAVE / VIEW button:
1191  if ($this->viewId && !$this->noView && $this->getNewIconMode($this->firstEl['table'], 'saveDocView')) {
1192  $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid']);
1193  if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1194  $excludeDokTypes = GeneralUtility::intExplode(
1195  ',',
1196  $pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'],
1197  true
1198  );
1199  } else {
1200  // exclude sysfolders, spacers and recycler by default
1201  $excludeDokTypes = [
1205  ];
1206  }
1207  if (!in_array((int)$this->pageinfo['doktype'], $excludeDokTypes, true)
1208  || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1209  ) {
1210  $saveAndOpenButton = $buttonBar->makeInputButton()
1211  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDocShow'))
1212  ->setName('_savedokview')
1213  ->setValue('1')
1214  ->setForm('EditDocumentController')
1215  ->setOnClick("window.open('', 'newTYPO3frontendWindow');")
1216  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1217  'actions-document-save-view',
1219  ));
1220  $saveSplitButton->addItem($saveAndOpenButton);
1221  }
1222  }
1223 
1224  // SAVE / NEW button:
1225  $showSaveAndNewButton = (
1226  count($this->elementsData) === 1
1227  && $this->getNewIconMode($this->firstEl['table'])
1228  );
1229  // Hide the button for tt_content when in connected translation mode
1230  // if the button is enabled in general and the record is a translation
1231  if ($showSaveAndNewButton && $sysLanguageUid > 0 && $this->firstEl['table'] === 'tt_content') {
1232  $showSaveAndNewButton = $this->isPageInFreeTranslationMode(
1233  (int)$this->pageinfo['uid'],
1234  !$isSavedRecord ? (int)$this->defVals['colPos'] : (int)$record['colPos'],
1235  $sysLanguageUid
1236  );
1237  }
1238  if ($showSaveAndNewButton) {
1239  $saveAndNewButton = $buttonBar->makeInputButton()
1240  ->setName('_savedoknew')
1241  ->setClasses('t3js-editform-submitButton')
1242  ->setValue('1')
1243  ->setForm('EditDocumentController')
1244  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveNewDoc'))
1245  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1246  'actions-document-save-new',
1248  ));
1249  $saveSplitButton->addItem($saveAndNewButton);
1250  }
1251  // SAVE / CLOSE
1252  $saveAndCloseButton = $buttonBar->makeInputButton()
1253  ->setName('_saveandclosedok')
1254  ->setClasses('t3js-editform-submitButton')
1255  ->setValue('1')
1256  ->setForm('EditDocumentController')
1257  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveCloseDoc'))
1258  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1259  'actions-document-save-close',
1261  ));
1262  $saveSplitButton->addItem($saveAndCloseButton);
1263  // FINISH TRANSLATION / SAVE / CLOSE
1264  if ($GLOBALS['TYPO3_CONF_VARS']['BE']['explicitConfirmationOfTranslation']) {
1265  $saveTranslationButton = $buttonBar->makeInputButton()
1266  ->setName('_translation_savedok')
1267  ->setValue('1')
1268  ->setForm('EditDocumentController')
1269  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.translationSaveDoc'))
1270  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1271  'actions-document-save-cleartranslationcache',
1273  ));
1274  $saveSplitButton->addItem($saveTranslationButton);
1275  $saveAndClearTranslationButton = $buttonBar->makeInputButton()
1276  ->setName('_translation_savedokclear')
1277  ->setValue('1')
1278  ->setForm('EditDocumentController')
1279  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.translationSaveDocClear'))
1280  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1281  'actions-document-save-cleartranslationcache',
1283  ));
1284  $saveSplitButton->addItem($saveAndClearTranslationButton);
1285  }
1286  $buttonBar->addButton($saveSplitButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
1287  }
1288  // CLOSE button:
1289  $closeButton = $buttonBar->makeLinkButton()
1290  ->setHref('#')
1291  ->setClasses('t3js-editform-close')
1292  ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
1293  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1294  'actions-close',
1296  ));
1297  $buttonBar->addButton($closeButton);
1298 
1299  // DELETE + UNDO buttons:
1300  if (!$this->errorC
1301  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1302  && count($this->elementsData) === 1
1303  ) {
1304  if ($this->firstEl['cmd'] !== 'new' && MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])) {
1305  // Delete:
1306  if ($this->firstEl['deleteAccess']
1307  && !$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly']
1308  && !$this->getDisableDelete()
1309  ) {
1311  if ($this->firstEl['table'] === 'pages') {
1312  parse_str((string)parse_url($returnUrl, PHP_URL_QUERY), $queryParams);
1313  if (isset($queryParams['M'])
1314  && isset($queryParams['id'])
1315  && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1316  ) {
1317  // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1318  // tree from the outside to be able to mark the pid as active
1319  $returnUrl = BackendUtility::getModuleUrl($queryParams['M'], ['id' => 0]);
1320  }
1321  }
1322  $deleteButton = $buttonBar->makeLinkButton()
1323  ->setHref('#')
1324  ->setClasses('t3js-editform-delete-record')
1325  ->setTitle($lang->getLL('deleteItem'))
1326  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1327  'actions-edit-delete',
1329  ))
1330  ->setDataAttributes([
1331  'return-url' => $returnUrl,
1332  'uid' => $this->firstEl['uid'],
1333  'table' => $this->firstEl['table']
1334  ]);
1335  $buttonBar->addButton($deleteButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1336  }
1337  // Undo:
1338  if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1339  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1340  ->getQueryBuilderForTable('sys_history');
1341 
1342  $undoButtonR = $queryBuilder->select('tstamp')
1343  ->from('sys_history')
1344  ->where(
1345  $queryBuilder->expr()->eq(
1346  'tablename',
1347  $queryBuilder->createNamedParameter($this->firstEl['table'], \PDO::PARAM_STR)
1348  ),
1349  $queryBuilder->expr()->eq(
1350  'recuid',
1351  $queryBuilder->createNamedParameter($this->firstEl['uid'], \PDO::PARAM_INT)
1352  )
1353  )
1354  ->orderBy('tstamp', 'DESC')
1355  ->setMaxResults(1)
1356  ->execute()
1357  ->fetch();
1358 
1359  if ($undoButtonR !== false) {
1360  $aOnClick = 'window.location.href=' .
1362  BackendUtility::getModuleUrl(
1363  'record_history',
1364  [
1365  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1366  'revert' => 'ALL_FIELDS',
1367  'returnUrl' => $this->R_URI,
1368  ]
1369  )
1370  ) . '; return false;';
1371 
1372  $undoButton = $buttonBar->makeLinkButton()
1373  ->setHref('#')
1374  ->setOnClick($aOnClick)
1375  ->setTitle(
1376  sprintf(
1377  $lang->getLL('undoLastChange'),
1379  ($GLOBALS['EXEC_TIME'] - $undoButtonR['tstamp']),
1380  $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears')
1381  )
1382  )
1383  )
1384  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1385  'actions-document-history-open',
1387  ));
1388  $buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1389  }
1390  }
1391  if ($this->getNewIconMode($this->firstEl['table'], 'showHistory')) {
1392  $aOnClick = 'window.location.href=' .
1394  BackendUtility::getModuleUrl(
1395  'record_history',
1396  [
1397  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1398  'returnUrl' => $this->R_URI,
1399  ]
1400  )
1401  ) . '; return false;';
1402 
1403  $historyButton = $buttonBar->makeLinkButton()
1404  ->setHref('#')
1405  ->setOnClick($aOnClick)
1406  ->setTitle('Open history of this record')
1407  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1408  'actions-document-history-open',
1410  ));
1411  $buttonBar->addButton($historyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1412  }
1413  // If only SOME fields are shown in the form, this will link the user to the FULL form:
1414  if ($this->columnsOnly) {
1415  $columnsOnlyButton = $buttonBar->makeLinkButton()
1416  ->setHref($this->R_URI . '&columnsOnly=')
1417  ->setTitle($lang->getLL('editWholeRecord'))
1418  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
1419  'actions-open',
1421  ));
1422  $buttonBar->addButton($columnsOnlyButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1423  }
1424  }
1425  }
1426  $cshButton = $buttonBar->makeHelpButton()->setModuleName('xMOD_csh_corebe')->setFieldName('TCEforms');
1427  $buttonBar->addButton($cshButton);
1428  $this->shortCutLink();
1429  $this->openInNewWindowLink();
1430  }
1431 
1440  protected function isPageInFreeTranslationMode(int $page, int $column, int $language): bool
1441  {
1442  $freeTranslationMode = false;
1443 
1444  if ($this->getConnectedContentElementTranslationsCount($page, $column, $language) === 0
1445  && $this->getStandAloneContentElementTranslationsCount($page, $column, $language) >= 0) {
1446  $freeTranslationMode = true;
1447  }
1448 
1449  return $freeTranslationMode;
1450  }
1451 
1460  protected function getConnectedContentElementTranslationsCount(int $page, int $column, int $language): int
1461  {
1462  $queryBuilder = $this->getQueryBuilderForTranslationMode($page, $column, $language);
1463 
1464  return (int)$queryBuilder
1465  ->andWhere(
1466  $queryBuilder->expr()->gt(
1467  $GLOBALS['TCA']['tt_content']['ctrl']['transOrigPointerField'],
1468  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1469  )
1470  )
1471  ->execute()
1472  ->fetchColumn(0);
1473  }
1474 
1483  protected function getStandAloneContentElementTranslationsCount(int $page, int $column, int $language): int
1484  {
1485  $queryBuilder = $this->getQueryBuilderForTranslationMode($page, $column, $language);
1486 
1487  return (int)$queryBuilder
1488  ->andWhere(
1489  $queryBuilder->expr()->eq(
1490  $GLOBALS['TCA']['tt_content']['ctrl']['transOrigPointerField'],
1491  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1492  )
1493  )
1494  ->execute()
1495  ->fetchColumn(0);
1496  }
1497 
1506  protected function getQueryBuilderForTranslationMode(int $page, int $column, int $language): QueryBuilder
1507  {
1508  $languageField = $GLOBALS['TCA']['tt_content']['ctrl']['languageField'];
1509 
1510  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1511  ->getQueryBuilderForTable('tt_content');
1512 
1513  $queryBuilder->getRestrictions()
1514  ->removeAll()
1515  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1516  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1517 
1518  return $queryBuilder
1519  ->count('uid')
1520  ->from('tt_content')
1521  ->where(
1522  $queryBuilder->expr()->eq(
1523  'pid',
1524  $queryBuilder->createNamedParameter($page, \PDO::PARAM_INT)
1525  ),
1526  $queryBuilder->expr()->eq(
1527  $languageField,
1528  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1529  ),
1530  $queryBuilder->expr()->eq(
1531  'colPos',
1532  $queryBuilder->createNamedParameter($column, \PDO::PARAM_INT)
1533  )
1534  );
1535  }
1536 
1543  public function compileForm($editForm)
1544  {
1545  $formContent = '
1546  <!-- EDITING FORM -->
1547  <form
1548  action="' . htmlspecialchars($this->R_URI) . '"
1549  method="post"
1550  enctype="multipart/form-data"
1551  name="editform"
1552  id="EditDocumentController"
1553  onsubmit="TBE_EDITOR.checkAndDoSubmit(1); return false;">
1554  ' . $editForm . '
1555 
1556  <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1557  <input type="hidden" name="viewUrl" value="' . htmlspecialchars($this->viewUrl) . '" />';
1558  if ($this->returnNewPageId) {
1559  $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1560  }
1561  $formContent .= '<input type="hidden" name="popViewId" value="' . htmlspecialchars($this->viewId) . '" />';
1562  if ($this->viewId_addParams) {
1563  $formContent .= '<input type="hidden" name="popViewId_addParams" value="' . htmlspecialchars($this->viewId_addParams) . '" />';
1564  }
1565  $formContent .= '
1566  <input type="hidden" name="closeDoc" value="0" />
1567  <input type="hidden" name="doSave" value="0" />
1568  <input type="hidden" name="_serialNumber" value="' . md5(microtime()) . '" />
1569  <input type="hidden" name="_scrollPosition" value="" />';
1570  return $formContent;
1571  }
1572 
1576  public function shortCutLink()
1577  {
1578  if ($this->returnUrl !== GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Html/Close.html')) {
1579  $shortCutButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->makeShortcutButton();
1580  $shortCutButton->setModuleName($this->MCONF['name'])
1581  ->setGetVariables([
1582  'returnUrl',
1583  'edit',
1584  'defVals',
1585  'overrideVals',
1586  'columnsOnly',
1587  'returnNewPageId',
1588  'noView']);
1589  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton($shortCutButton);
1590  }
1591  }
1592 
1596  public function openInNewWindowLink()
1597  {
1598  $closeUrl = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Html/Close.html');
1599  if ($this->returnUrl !== $closeUrl) {
1600  $aOnClick = 'vHWin=window.open(' . GeneralUtility::quoteJSvalue(GeneralUtility::linkThisScript(
1601  ['returnUrl' => PathUtility::getAbsoluteWebPath($closeUrl)]
1602  ))
1603  . ','
1604  . GeneralUtility::quoteJSvalue(md5($this->R_URI))
1605  . ',\'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1\');vHWin.focus();return false;';
1606  $openInNewWindowButton = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()
1607  ->makeLinkButton()
1608  ->setHref('#')
1609  ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.openInNewWindow'))
1610  ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-window-open', Icon::SIZE_SMALL))
1611  ->setOnClick($aOnClick);
1612  $this->moduleTemplate->getDocHeaderComponent()->getButtonBar()->addButton(
1613  $openInNewWindowButton,
1615  );
1616  }
1617  }
1618 
1625  protected function getDisableDelete(): bool
1626  {
1627  $disableDelete = false;
1628  if ($this->firstEl['table'] === 'sys_file_metadata') {
1629  $row = BackendUtility::getRecord('sys_file_metadata', $this->firstEl['uid'], 'sys_language_uid');
1630  $languageUid = $row['sys_language_uid'];
1631  if ($languageUid === 0) {
1632  $disableDelete = true;
1633  }
1634  } else {
1635  $disableDelete = (bool)$this->getNewIconMode($this->firstEl['table'], 'disableDelete');
1636  }
1637  return $disableDelete;
1638  }
1639 
1640  /***************************
1641  *
1642  * Localization stuff
1643  *
1644  ***************************/
1654  public function languageSwitch($table, $uid, $pid = null)
1655  {
1656  $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
1657  $transOrigPointerField = $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'];
1658  // Table editable and activated for languages?
1659  if ($this->getBackendUser()->check('tables_modify', $table)
1660  && $languageField
1661  && $transOrigPointerField
1662  && $table !== 'pages_language_overlay'
1663  ) {
1664  if (is_null($pid)) {
1665  $row = BackendUtility::getRecord($table, $uid, 'pid');
1666  $pid = $row['pid'];
1667  }
1668  // Get all available languages for the page
1669  $langRows = $this->getLanguages($pid);
1670  // Page available in other languages than default language?
1671  if (is_array($langRows) && count($langRows) > 1) {
1672  $rowsByLang = [];
1673  $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1674  // Get record in current language
1675  $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, $uid, $fetchFields);
1676  if (!is_array($rowCurrent)) {
1677  $rowCurrent = BackendUtility::getRecord($table, $uid, $fetchFields);
1678  }
1679  $currentLanguage = (int)$rowCurrent[$languageField];
1680  // Disabled for records with [all] language!
1681  if ($currentLanguage > -1) {
1682  // Get record in default language if needed
1683  if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1684  $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1685  $table,
1686  $rowCurrent[$transOrigPointerField],
1687  $fetchFields
1688  );
1689  if (!is_array($rowsByLang[0])) {
1690  $rowsByLang[0] = BackendUtility::getRecord(
1691  $table,
1692  $rowCurrent[$transOrigPointerField],
1693  $fetchFields
1694  );
1695  }
1696  } else {
1697  $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1698  }
1699  if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1700  // Get record in other languages to see what's already available
1701 
1702  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1703  ->getQueryBuilderForTable($table);
1704 
1705  $queryBuilder->getRestrictions()
1706  ->removeAll()
1707  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1708  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1709 
1710  $result = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fetchFields, true))
1711  ->from($table)
1712  ->where(
1713  $queryBuilder->expr()->eq(
1714  'pid',
1715  $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)
1716  ),
1717  $queryBuilder->expr()->gt(
1718  $languageField,
1719  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1720  ),
1721  $queryBuilder->expr()->eq(
1722  $transOrigPointerField,
1723  $queryBuilder->createNamedParameter($rowsByLang[0]['uid'], \PDO::PARAM_INT)
1724  )
1725  )
1726  ->execute();
1727 
1728  while ($row = $result->fetch()) {
1729  $rowsByLang[$row[$languageField]] = $row;
1730  }
1731  }
1732  $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1733  $languageMenu->setIdentifier('_langSelector');
1734  foreach ($langRows as $lang) {
1735  if ($this->getBackendUser()->checkLanguageAccess($lang['uid'])) {
1736  $newTranslation = isset($rowsByLang[$lang['uid']]) ? '' : ' [' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.new')) . ']';
1737  // Create url for creating a localized record
1738  $addOption = true;
1739  if ($newTranslation) {
1740  $redirectUrl = BackendUtility::getModuleUrl('record_edit', [
1741  'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $lang['uid'],
1742  'returnUrl' => $this->retUrl
1743  ]);
1744 
1745  if (array_key_exists(0, $rowsByLang)) {
1747  '&cmd[' . $table . '][' . $rowsByLang[0]['uid'] . '][localize]=' . $lang['uid'],
1748  $redirectUrl
1749  );
1750  } else {
1751  $addOption = false;
1752  }
1753  } else {
1754  $href = BackendUtility::getModuleUrl('record_edit', [
1755  'edit[' . $table . '][' . $rowsByLang[$lang['uid']]['uid'] . ']' => 'edit',
1756  'returnUrl' => $this->retUrl
1757  ]);
1758  }
1759  if ($addOption) {
1760  $menuItem = $languageMenu->makeMenuItem()
1761  ->setTitle($lang['title'] . $newTranslation)
1762  ->setHref($href);
1763  if ((int)$lang['uid'] === $currentLanguage) {
1764  $menuItem->setActive(true);
1765  }
1766  $languageMenu->addMenuItem($menuItem);
1767  }
1768  }
1769  }
1770  $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1771  }
1772  }
1773  }
1774  }
1775 
1781  public function localizationRedirect($justLocalized)
1782  {
1783  list($table, $origUid, $language) = explode(':', $justLocalized);
1784  $table = $table === 'pages' ? 'pages_language_overlay' : $table;
1785  if ($GLOBALS['TCA'][$table]
1786  && $GLOBALS['TCA'][$table]['ctrl']['languageField']
1787  && $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
1788  ) {
1789  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1790  ->getQueryBuilderForTable($table);
1791  $queryBuilder->getRestrictions()
1792  ->removeAll()
1793  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1794  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1795 
1796  $localizedRecord = $queryBuilder->select('uid')
1797  ->from($table)
1798  ->where(
1799  $queryBuilder->expr()->eq(
1800  $GLOBALS['TCA'][$table]['ctrl']['languageField'],
1801  $queryBuilder->createNamedParameter($language, \PDO::PARAM_INT)
1802  ),
1803  $queryBuilder->expr()->eq(
1804  $GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
1805  $queryBuilder->createNamedParameter($origUid, \PDO::PARAM_INT)
1806  )
1807  )
1808  ->execute()
1809  ->fetch();
1810 
1811  if (is_array($localizedRecord)) {
1812  // Create parameters and finally run the classic page module for creating a new page translation
1813  $location = BackendUtility::getModuleUrl('record_edit', [
1814  'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
1815  'returnUrl' => GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))
1816  ]);
1817  HttpUtility::redirect($location);
1818  }
1819  }
1820  }
1821 
1830  public function getLanguages($id)
1831  {
1832  $modSharedTSconfig = BackendUtility::getModTSconfig($id, 'mod.SHARED');
1833  // Fallback non sprite-configuration
1834  if (preg_match('/\\.gif$/', $modSharedTSconfig['properties']['defaultLanguageFlag'])) {
1835  $modSharedTSconfig['properties']['defaultLanguageFlag'] = str_replace(
1836  '.gif',
1837  '',
1838  $modSharedTSconfig['properties']['defaultLanguageFlag']
1839  );
1840  }
1841  $languages = [
1842  0 => [
1843  'uid' => 0,
1844  'pid' => 0,
1845  'hidden' => 0,
1846  'title' => $modSharedTSconfig['properties']['defaultLanguageLabel'] !== ''
1847  ? $modSharedTSconfig['properties']['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage') . ')'
1848  : $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage'),
1849  'flag' => $modSharedTSconfig['properties']['defaultLanguageFlag']
1850  ]
1851  ];
1852 
1853  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1854  ->getQueryBuilderForTable('sys_language');
1855 
1856  $queryBuilder->select('s.uid', 's.pid', 's.hidden', 's.title', 's.flag')
1857  ->from('sys_language', 's')
1858  ->groupBy('s.uid', 's.pid', 's.hidden', 's.title', 's.flag', 's.sorting')
1859  ->orderBy('s.sorting');
1860 
1861  if ($id) {
1862  $queryBuilder->getRestrictions()
1863  ->removeAll()
1864  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1865  ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1866 
1867  // Add join with pages_languages_overlay table to only show active languages
1868  $queryBuilder->from('pages_language_overlay', 'o')
1869  ->where(
1870  $queryBuilder->expr()->eq('o.sys_language_uid', $queryBuilder->quoteIdentifier('s.uid')),
1871  $queryBuilder->expr()->eq('o.pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT))
1872  );
1873 
1874  if (!$this->getBackendUser()->isAdmin()) {
1875  $queryBuilder->andWhere($queryBuilder->expr()->eq('s.hidden', 0));
1876  }
1877  }
1878 
1879  $result = $queryBuilder->execute();
1880  while ($row = $result->fetch()) {
1881  $languages[$row['uid']] = $row;
1882  }
1883 
1884  return $languages;
1885  }
1886 
1887  /***************************
1888  *
1889  * Other functions
1890  *
1891  ***************************/
1897  public function fixWSversioningInEditConf($mapArray = false)
1898  {
1899  // Traverse the editConf array
1900  if (is_array($this->editconf)) {
1901  // Tables:
1902  foreach ($this->editconf as $table => $conf) {
1903  if (is_array($conf) && $GLOBALS['TCA'][$table]) {
1904  // Traverse the keys/comments of each table (keys can be a commalist of uids)
1905  $newConf = [];
1906  foreach ($conf as $cKey => $cmd) {
1907  if ($cmd === 'edit') {
1908  // Traverse the ids:
1909  $ids = GeneralUtility::trimExplode(',', $cKey, true);
1910  foreach ($ids as $idKey => $theUid) {
1911  if (is_array($mapArray)) {
1912  if ($mapArray[$table][$theUid]) {
1913  $ids[$idKey] = $mapArray[$table][$theUid];
1914  }
1915  } else {
1916  // Default, look for versions in workspace for record:
1917  $calcPRec = $this->getRecordForEdit($table, $theUid);
1918  if (is_array($calcPRec)) {
1919  // Setting UID again if it had changed, eg. due to workspace versioning.
1920  $ids[$idKey] = $calcPRec['uid'];
1921  }
1922  }
1923  }
1924  // Add the possibly manipulated IDs to the new-build newConf array:
1925  $newConf[implode(',', $ids)] = $cmd;
1926  } else {
1927  $newConf[$cKey] = $cmd;
1928  }
1929  }
1930  // Store the new conf array:
1931  $this->editconf[$table] = $newConf;
1932  }
1933  }
1934  }
1935  }
1936 
1944  public function getRecordForEdit($table, $theUid)
1945  {
1946  // Fetch requested record:
1947  $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid');
1948  if (is_array($reqRecord)) {
1949  // If workspace is OFFLINE:
1950  if ($this->getBackendUser()->workspace != 0) {
1951  // Check for versioning support of the table:
1952  if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['versioningWS']) {
1953  // If the record is already a version of "something" pass it by.
1954  if ($reqRecord['pid'] == -1) {
1955  // (If it turns out not to be a version of the current workspace there will be trouble, but
1956  // that is handled inside DataHandler then and in the interface it would clearly be an error of
1957  // links if the user accesses such a scenario)
1958  return $reqRecord;
1959  }
1960  // The input record was online and an offline version must be found or made:
1961  // Look for version of this workspace:
1963  $this->getBackendUser()->workspace,
1964  $table,
1965  $reqRecord['uid'],
1966  'uid,pid,t3ver_oid'
1967  );
1968  return is_array($versionRec) ? $versionRec : $reqRecord;
1969  }
1970  // This means that editing cannot occur on this record because it was not supporting versioning
1971  // which is required inside an offline workspace.
1972  return false;
1973  }
1974  // In ONLINE workspace, just return the originally requested record:
1975  return $reqRecord;
1976  }
1977  // Return FALSE because the table/uid was not found anyway.
1978  return false;
1979  }
1980 
1986  public function compileStoreDat()
1987  {
1989  'edit,defVals,overrideVals,columnsOnly,noView,workspace',
1990  $this->R_URL_getvars
1991  );
1992  $this->storeUrl = GeneralUtility::implodeArrayForUrl('', $this->storeArray);
1993  $this->storeUrlMd5 = md5($this->storeUrl);
1994  }
1995 
2004  public function getNewIconMode($table, $key = 'saveDocNew')
2005  {
2006  $TSconfig = $this->getBackendUser()->getTSConfig('options.' . $key);
2007  $output = trim(isset($TSconfig['properties'][$table]) ? $TSconfig['properties'][$table] : $TSconfig['value']);
2008  return $output;
2009  }
2010 
2021  public function closeDocument($mode = self::DOCUMENT_CLOSE_MODE_DEFAULT)
2022  {
2023  $mode = (int)$mode;
2024  // If current document is found in docHandler,
2025  // then unset it, possibly unset it ALL and finally, write it to the session data
2026  if (isset($this->docHandler[$this->storeUrlMd5])) {
2027  // add the closing document to the recent documents
2028  $recentDocs = $this->getBackendUser()->getModuleData('opendocs::recent');
2029  if (!is_array($recentDocs)) {
2030  $recentDocs = [];
2031  }
2032  $closedDoc = $this->docHandler[$this->storeUrlMd5];
2033  $recentDocs = array_merge([$this->storeUrlMd5 => $closedDoc], $recentDocs);
2034  if (count($recentDocs) > 8) {
2035  $recentDocs = array_slice($recentDocs, 0, 8);
2036  }
2037  // remove it from the list of the open documents
2038  unset($this->docHandler[$this->storeUrlMd5]);
2039  if ($mode === self::DOCUMENT_CLOSE_MODE_CLEAR_ALL) {
2040  $recentDocs = array_merge($this->docHandler, $recentDocs);
2041  $this->docHandler = [];
2042  }
2043  $this->getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
2044  $this->getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->docDat[1]]);
2045  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
2046  }
2047  if ($mode !== self::DOCUMENT_CLOSE_MODE_NO_REDIRECT) {
2048  // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: (used by
2049  // other scripts, like wizard_add, to know which records was created or so...)
2050  if ($this->returnEditConf && $this->retUrl != BackendUtility::getModuleUrl('dummy')) {
2051  $this->retUrl .= '&returnEditConf=' . rawurlencode(json_encode($this->editconf));
2052  }
2053 
2054  // If mode is NOT set (means 0) OR set to 1, then make a header location redirect to $this->retUrl
2055  if ($mode === self::DOCUMENT_CLOSE_MODE_DEFAULT || $mode === self::DOCUMENT_CLOSE_MODE_REDIRECT) {
2056  HttpUtility::redirect($this->retUrl);
2057  } else {
2058  $this->setDocument('', $this->retUrl);
2059  }
2060  }
2061  }
2062 
2071  public function setDocument($currentDocFromHandlerMD5 = '', $retUrl = '')
2072  {
2073  if ($retUrl === '') {
2074  return;
2075  }
2076  if (!$this->modTSconfig['properties']['disableDocSelector']
2077  && is_array($this->docHandler)
2078  && !empty($this->docHandler)
2079  ) {
2080  if (isset($this->docHandler[$currentDocFromHandlerMD5])) {
2081  $setupArr = $this->docHandler[$currentDocFromHandlerMD5];
2082  } else {
2083  $setupArr = reset($this->docHandler);
2084  }
2085  if ($setupArr[2]) {
2086  $sParts = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
2087  $retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode($retUrl);
2088  }
2089  }
2091  }
2092 
2100  public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
2101  {
2103 
2104  // Preprocessing, storing data if submitted to
2105  $this->preInit();
2106 
2107  // Checks, if a save button has been clicked (or the doSave variable is sent)
2108  if ($this->doProcessData()) {
2109  $this->processData();
2110  }
2111 
2112  $this->init();
2113  $this->main();
2114 
2115  $response->getBody()->write($this->moduleTemplate->renderContent());
2116  return $response;
2117  }
2118 
2122  protected function getBackendUser()
2123  {
2124  return $GLOBALS['BE_USER'];
2125  }
2126 
2132  protected function getLanguageService()
2133  {
2134  return $GLOBALS['LANG'];
2135  }
2136 }
static compileSelectedGetVarsFromArray($varList, array $getArray, $GPvarAlt=true)
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
static intExplode($delimiter, $string, $removeEmptyValues=false, $limit=0)
static readPageAccess($id, $perms_clause)
static getWorkspaceVersionOfRecord($workspace, $table, $uid, $fields=' *')
mainAction(ServerRequestInterface $request, ResponseInterface $response)
parseAdditionalGetParameters(array &$parameters, array $typoScript)
isPageInFreeTranslationMode(int $page, int $column, int $language)
static viewOnClick( $pageUid, $backPath='', $rootLine=null, $anchorSection='', $alternativeUrl='', $additionalGetVars='', $switchFocus=true)
static calcAge($seconds, $labels='min|hrs|days|yrs|min|hour|day|year')
static getAbsoluteWebPath($targetPath)
Definition: PathUtility.php:40
static BEgetRootLine($uid, $clause='', $workspaceOL=false)
static getFileAbsFileName($filename, $_=null, $_2=null)
static lockRecords($table='', $uid=0, $pid=0)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
getQueryBuilderForTranslationMode(int $page, int $column, int $language)
static linkThisScript(array $getParams=[])
static makeInstance($className,... $constructorArguments)
setDocument($currentDocFromHandlerMD5='', $retUrl='')
static implodeArrayForUrl($name, array $theArray, $str='', $skipBlank=false, $rawurlencodeParamName=false)
closeDocument($mode=self::DOCUMENT_CLOSE_MODE_DEFAULT)
static setUpdateSignal($set='', $params='')
static getRecordTitle($table, $row, $prep=false, $forceResult=true)
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
static convertToPositiveInteger($theInt)
Definition: MathUtility.php:55
static getLiveVersionOfRecord($table, $uid, $fields=' *')
static getRecord($table, $uid, $fields=' *', $where='', $useDeleteClause=true)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static getLinkToDataHandlerAction($parameters, $redirectUrl='')
getConnectedContentElementTranslationsCount(int $page, int $column, int $language)
getStandAloneContentElementTranslationsCount(int $page, int $column, int $language)