‪TYPO3CMS  ‪main
EditDocumentController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\ServerRequestInterface;
40 use TYPO3\CMS\Backend\Utility\BackendUtility;
44 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
52 use TYPO3\CMS\Core\Imaging\IconSize;
70 
78 #[Controller]
80 {
81  protected const ‪DOCUMENT_CLOSE_MODE_DEFAULT = 0;
82  // works like DOCUMENT_CLOSE_MODE_DEFAULT
83  protected const ‪DOCUMENT_CLOSE_MODE_REDIRECT = 1;
84  protected const ‪DOCUMENT_CLOSE_MODE_CLEAR_ALL = 3;
86 
92  protected ‪$editconf = [];
93 
100  protected ‪$columnsOnly;
101 
107  protected ‪$defVals;
108 
114  protected ‪$overrideVals;
115 
122  protected ‪$returnUrl;
123 
131  protected ‪$retUrl;
132 
136  protected int ‪$closeDoc;
137 
145  protected ‪$doSave;
146 
152  protected ‪$data;
153 
159  protected ‪$cmd;
160 
166  protected ‪$mirror;
167 
174  protected ‪$returnNewPageId = false;
175 
181  protected ‪$popViewId;
182 
186  protected ‪$previewCode;
187 
193  protected ‪$recTitle;
194 
200  protected ‪$noView;
201 
205  protected ‪$perms_clause;
206 
212  protected ‪$returnEditConf;
213 
219  protected ‪$R_URL_parts;
220 
227  protected ‪$R_URL_getvars;
228 
234  protected ‪$R_URI;
235 
239  protected ‪$pageinfo;
240 
247  protected ‪$storeTitle = '';
248 
255  protected ‪$storeArray;
256 
262  protected ‪$storeUrl;
263 
269  protected ‪$storeUrlMd5;
270 
276  protected ‪$docDat;
277 
286  protected ‪$docHandler;
287 
293  protected ‪$elementsData;
294 
300  protected ‪$firstEl;
301 
307  protected ‪$errorC;
308 
315  protected ‪$viewId;
316 
320  protected ‪$formResultCompiler;
321 
327  protected ‪$dontStoreDocumentRef = 0;
328 
334  protected ‪$previewData = [];
335 
339  protected bool ‪$isSavedRecord = false;
340 
341  protected bool ‪$isPageInFreeTranslationMode = false;
342 
343  public function ‪__construct(
344  protected readonly EventDispatcherInterface $eventDispatcher,
345  protected readonly ‪IconFactory $iconFactory,
346  protected readonly ‪PageRenderer $pageRenderer,
347  protected readonly ‪UriBuilder $uriBuilder,
348  protected readonly ‪ModuleTemplateFactory $moduleTemplateFactory,
349  protected readonly ‪BackendEntryPointResolver $backendEntryPointResolver,
350  private readonly ‪FormDataCompiler $formDataCompiler,
351  ) {
352  }
353 
357  public function ‪mainAction(ServerRequestInterface $request): ResponseInterface
358  {
359  $view = $this->moduleTemplateFactory->create($request);
360  $view->setUiBlock(true);
361  $view->setTitle($this->‪getShortcutTitle($request));
362 
363  // Unlock all locked records
364  BackendUtility::lockRecords();
365  if ($response = $this->‪preInit($request)) {
366  return $response;
367  }
368 
369  // Process incoming data via DataHandler?
370  $parsedBody = $request->getParsedBody();
371  if ((
372  $this->doSave
373  || isset($parsedBody['_savedok'])
374  || isset($parsedBody['_saveandclosedok'])
375  || isset($parsedBody['_savedokview'])
376  || isset($parsedBody['_savedoknew'])
377  || isset($parsedBody['_duplicatedoc'])
378  )
379  && $request->getMethod() === 'POST'
380  && $response = $this->processData($view, $request)
381  ) {
382  return $response;
383  }
384 
385  $this->‪init($request);
386 
387  if ($request->getMethod() === 'POST') {
388  // In case save&view is requested, we have to add this information to the redirect
389  // URL, since the ImmediateAction will be added to the module body afterwards.
390  if (isset($parsedBody['_savedokview'])) {
391  $this->R_URI = rtrim($this->R_URI, '&') .
393  'showPreview' => true,
394  'popViewId' => $parsedBody['popViewId'] ?? $this->‪getPreviewPageId(),
395  ], (empty($this->R_URL_getvars) ? '?' : '&'));
396  }
397  return new RedirectResponse($this->R_URI, 302);
398  }
399 
400  $view->assign('bodyHtml', $this->‪main($view, $request));
401  return $view->renderResponse('Form/EditDocument');
402  }
403 
407  protected function ‪preInit(ServerRequestInterface $request): ?ResponseInterface
408  {
409  if ($response = $this->‪localizationRedirect($request)) {
410  return $response;
411  }
412 
413  $parsedBody = $request->getParsedBody();
414  $queryParams = $request->getQueryParams();
415 
416  $this->editconf = $parsedBody['edit'] ?? $queryParams['edit'] ?? [];
417  $this->defVals = $parsedBody['defVals'] ?? $queryParams['defVals'] ?? null;
418  $this->overrideVals = $parsedBody['overrideVals'] ?? $queryParams['overrideVals'] ?? null;
419  $this->columnsOnly = $parsedBody['columnsOnly'] ?? $queryParams['columnsOnly'] ?? null;
420  $this->returnUrl = GeneralUtility::sanitizeLocalUrl($parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '');
421  $this->closeDoc = (int)($parsedBody['closeDoc'] ?? $queryParams['closeDoc'] ?? self::DOCUMENT_CLOSE_MODE_DEFAULT);
422  $this->doSave = ($parsedBody['doSave'] ?? false) && $request->getMethod() === 'POST';
423  $this->returnEditConf = (bool)($parsedBody['returnEditConf'] ?? $queryParams['returnEditConf'] ?? false);
424 
425  // Set overrideVals as default values if defVals does not exist.
426  // @todo: Why?
427  if (!is_array($this->defVals) && is_array($this->overrideVals)) {
428  $this->defVals = ‪$this->overrideVals;
429  }
430  $this->‪addSlugFieldsToColumnsOnly($queryParams);
431 
432  // Set final return URL
433  $this->retUrl = $this->returnUrl ?: (string)$this->uriBuilder->buildUriFromRoute('dummy');
434 
435  // Change $this->editconf if versioning applies to any of the records
437 
438  // Prepare R_URL (request url)
439  $this->R_URL_parts = parse_url($request->getAttribute('normalizedParams')->getRequestUri()) ?: [];
440  $this->R_URL_getvars = $queryParams;
441  $this->R_URL_getvars['edit'] = ‪$this->editconf;
442 
443  // Prepare 'open documents' url, this is later modified again various times
444  $this->‪compileStoreData($request);
445  // Backend user session data of this module
446  $this->docDat = $this->‪getBackendUser()->getModuleData('FormEngine', 'ses');
447  $this->docHandler = $this->docDat[0] ?? [];
448 
449  // Close document if a request for closing the document has been sent
450  if ($this->closeDoc > self::DOCUMENT_CLOSE_MODE_DEFAULT) {
451  if ($response = $this->‪closeDocument($this->closeDoc, $request)) {
452  return $response;
453  }
454  }
455 
456  $event = new ‪BeforeFormEnginePageInitializedEvent($this, $request);
457  $this->eventDispatcher->dispatch($event);
458  return null;
459  }
460 
464  protected function ‪addSlugFieldsToColumnsOnly(array $queryParams): void
465  {
466  ‪$data = $queryParams['edit'] ?? [];
467  ‪$data = array_keys(‪$data);
468  $table = reset(‪$data);
469  if ($this->columnsOnly && $table !== false && isset(‪$GLOBALS['TCA'][$table])) {
470  ‪$fields = GeneralUtility::trimExplode(',', $this->columnsOnly, true);
471  foreach (‪$fields as $field) {
472  $postModifiers = ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['generatorOptions']['postModifiers'] ?? [];
473  if (isset(‪$GLOBALS['TCA'][$table]['columns'][$field])
474  && ‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['type'] === 'slug'
475  && (!is_array($postModifiers) || $postModifiers === [])
476  ) {
477  foreach (‪$GLOBALS['TCA'][$table]['columns'][$field]['config']['generatorOptions']['fields'] ?? [] as ‪$fields) {
478  $this->columnsOnly .= ',' . (is_array(‪$fields) ? implode(',', ‪$fields) : ‪$fields);
479  }
480  }
481  }
482  }
483  }
484 
488  protected function ‪processData(ModuleTemplate $view, ServerRequestInterface $request): ?ResponseInterface
489  {
490  $parsedBody = $request->getParsedBody();
491 
492  $beUser = $this->‪getBackendUser();
493 
494  // Processing related GET / POST vars
495  $this->data = $parsedBody['data'] ?? [];
496  $this->cmd = $parsedBody['cmd'] ?? [];
497  $this->mirror = $parsedBody['mirror'] ?? [];
498  $this->returnNewPageId = (bool)($parsedBody['returnNewPageId'] ?? false);
499 
500  // Only options related to $this->data submission are included here
501  $tce = GeneralUtility::makeInstance(DataHandler::class);
502 
503  $tce->setControl($parsedBody['control'] ?? []);
504 
505  // Set internal vars
506  if (isset($beUser->uc['neverHideAtCopy']) && $beUser->uc['neverHideAtCopy']) {
507  $tce->neverHideAtCopy = true;
508  }
509 
510  // Set default values fetched previously from GET / POST vars
511  if (is_array($this->defVals) && $this->defVals !== [] && is_array($tce->defaultValues)) {
512  $tce->defaultValues = array_merge_recursive($this->defVals, $tce->defaultValues);
513  }
514 
515  // Load DataHandler with data
516  $tce->start($this->data, $this->cmd);
517  if (is_array($this->mirror)) {
518  $tce->setMirror($this->mirror);
519  }
520 
521  // Perform the saving operation with DataHandler:
522  if ($this->doSave === true) {
523  $tce->process_datamap();
524  $tce->process_cmdmap();
525 
526  // Update the module menu for the current backend user, as they updated their UI language
527  $currentUserId = (int)($beUser->user[$beUser->userid_column] ?? 0);
528  if ($currentUserId
529  && (string)($this->data['be_users'][$currentUserId]['lang'] ?? '') !== ''
530  && $this->data['be_users'][$currentUserId]['lang'] !== $beUser->user['lang']
531  ) {
532  $newLanguageKey = $this->data['be_users'][$currentUserId]['lang'];
533  // Update the current backend user language as well
534  $beUser->user['lang'] = $newLanguageKey;
535  // Re-create LANG to have the current request updated the translated page as well
536  $this->‪getLanguageService()->init($newLanguageKey);
537  BackendUtility::setUpdateSignal('updateModuleMenu');
538  BackendUtility::setUpdateSignal('updateTopbar');
539  }
540  }
541  // If pages are being edited, we set an instruction about updating the page tree after this operation.
542  if ($tce->pagetreeNeedsRefresh
543  && (isset($this->data['pages']) || $beUser->workspace !== 0 && !empty($this->data))
544  ) {
545  BackendUtility::setUpdateSignal('updatePageTree');
546  }
547  // If there was saved any new items, load them:
548  if (!empty($tce->substNEWwithIDs_table)) {
549  // Save the expanded/collapsed states for new inline records, if any
550  $this->‪updateInlineView($request->getParsedBody()['uc'] ?? $request->getQueryParams()['uc'] ?? null, $tce);
551  $newEditConf = [];
552  foreach ($this->editconf as $tableName => $tableCmds) {
553  $keys = array_keys($tce->substNEWwithIDs_table, $tableName);
554  if (!empty($keys)) {
555  foreach ($keys as $key) {
556  $editId = $tce->substNEWwithIDs[$key];
557  // Check if the $editId isn't a child record of an IRRE action
558  if (!(is_array($tce->newRelatedIDs[$tableName] ?? null)
559  && in_array($editId, $tce->newRelatedIDs[$tableName]))
560  ) {
561  // Translate new id to the workspace version
562  if ($versionRec = BackendUtility::getWorkspaceVersionOfRecord(
563  $beUser->workspace,
564  $tableName,
565  $editId,
566  'uid'
567  )) {
568  $editId = $versionRec['uid'];
569  }
570  $newEditConf[$tableName][$editId] = 'edit';
571  }
572  // Traverse all new records and forge the content of ->editconf so we can continue to edit these records!
573  if ($tableName === 'pages'
574  && $this->retUrl !== (string)$this->uriBuilder->buildUriFromRoute('dummy')
575  && $this->retUrl !== $this->getCloseUrl()
576  && $this->returnNewPageId
577  ) {
578  $this->retUrl .= '&id=' . $tce->substNEWwithIDs[$key];
579  }
580  }
581  } else {
582  $newEditConf[$tableName] = $tableCmds;
583  }
584  }
585  // Reset editconf if newEditConf has values
586  if (!empty($newEditConf)) {
587  $this->editconf = $newEditConf;
588  }
589  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
590  $this->R_URL_getvars['edit'] = ‪$this->editconf;
591  // Unset default values since we don't need them anymore.
592  unset($this->R_URL_getvars['defVals']);
593  // Recompile the store* values since editconf changed
594  $this->‪compileStoreData($request);
595  }
596  // See if any records was auto-created as new versions?
597  if (!empty($tce->autoVersionIdMap)) {
598  $this->‪fixWSversioningInEditConf($tce->autoVersionIdMap);
599  }
600  // If a document is saved and a new one is created right after.
601  if (isset($parsedBody['_savedoknew']) && is_array($this->editconf)) {
602  if ($redirect = $this->‪closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT, $request)) {
603  return $redirect;
604  }
605  // Find the current table
606  reset($this->editconf);
607  $nTable = (string)key($this->editconf);
608  // Finding the first id, getting the records pid+uid
609  reset($this->editconf[$nTable]);
610  $nUid = (int)key($this->editconf[$nTable]);
611  $recordFields = 'pid,uid';
612  if (BackendUtility::isTableWorkspaceEnabled($nTable)) {
613  $recordFields .= ',t3ver_oid';
614  }
615  $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
616  // Determine insertion mode: 'top' is self-explaining,
617  // otherwise new elements are inserted after one using a negative uid
618  $insertRecordOnTop = ($this->‪getTsConfigOption($nTable, 'saveDocNew') === 'top');
619  // Setting a blank editconf array for a new record:
620  $this->editconf = [];
621  // Determine related page ID for regular live context
622  if ((int)($nRec['t3ver_oid'] ?? 0) === 0) {
623  if ($insertRecordOnTop) {
624  $relatedPageId = $nRec['pid'];
625  } else {
626  $relatedPageId = -$nRec['uid'];
627  }
628  } else {
629  // Determine related page ID for workspace context
630  if ($insertRecordOnTop) {
631  // Fetch live version of workspace version since the pid value is always -1 in workspaces
632  $liveRecord = BackendUtility::getRecord($nTable, $nRec['t3ver_oid'], $recordFields);
633  $relatedPageId = $liveRecord['pid'];
634  } else {
635  // Use uid of live version of workspace version
636  $relatedPageId = -$nRec['t3ver_oid'];
637  }
638  }
639  $this->editconf[$nTable][$relatedPageId] = 'new';
640  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
641  $this->R_URL_getvars['edit'] = ‪$this->editconf;
642  // Recompile the store* values since editconf changed...
643  $this->‪compileStoreData($request);
644  }
645 
646  // Explicitly require a save operation
647  if ($this->doSave) {
648  $erroneousRecords = $tce->printLogErrorMessages();
649  $messages = [];
650  $table = (string)key($this->editconf);
651  $uidList = GeneralUtility::intExplode(',', (string)key($this->editconf[$table]));
652 
653  foreach ($uidList as ‪$uid) {
654  ‪$uid = (int)abs(‪$uid);
655  if (!in_array($table . '.' . ‪$uid, $erroneousRecords, true)) {
656  $realUidInPayload = ($tceSubstId = array_search(‪$uid, $tce->substNEWwithIDs, true)) !== false ? $tceSubstId : ‪$uid;
657  $row = $this->data[$table][‪$uid] ?? $this->data[$table][$realUidInPayload] ?? null;
658  if ($row === null) {
659  continue;
660  }
661  // Ensure, uid is always available to make labels with foreign table lookups possible
662  $row['uid'] ??= $realUidInPayload;
663  // If the label column of the record is not available, fetch it from database.
664  // This is the when EditDocumentController is booted in single field mode (e.g.
665  // Template module > 'info/modify' > edit 'setup' field) or in case the field is
666  // not in "showitem" or is set to readonly (e.g. "file" in sys_file_metadata).
667  $labelArray = [‪$GLOBALS['TCA'][$table]['ctrl']['label'] ?? null];
668  $labelAltArray = GeneralUtility::trimExplode(',', ‪$GLOBALS['TCA'][$table]['ctrl']['label_alt'] ?? '', true);
669  $labelFields = array_unique(array_filter(array_merge($labelArray, $labelAltArray)));
670  foreach ($labelFields as $labelField) {
671  if (!isset($row[$labelField])) {
672  $tmpRecord = BackendUtility::getRecord($table, ‪$uid, implode(',', $labelFields));
673  if ($tmpRecord !== null) {
674  $row = array_merge($row, $tmpRecord);
675  }
676  break;
677  }
678  }
679  $recordTitle = ‪GeneralUtility::fixed_lgd_cs(BackendUtility::getRecordTitle($table, $row), (int)$this->‪getBackendUser()->uc['titleLen']);
680  $messages[] = sprintf($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.record_saved.message'), $recordTitle);
681  }
682  }
683 
684  // Add messages to the flash message container only if the request is a save action (excludes "duplicate")
685  if ($messages !== []) {
686  $label = $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.record_saved.title.plural');
687  if (count($messages) === 1) {
688  $label = $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.record_saved.title.singular');
689  }
690  if (count($messages) > 10) {
691  $messages = [sprintf($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:notification.mass_saving.message'), count($messages))];
692  }
693  $flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
694  $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier(‪FlashMessageQueue::NOTIFICATION_QUEUE);
695  $flashMessage = GeneralUtility::makeInstance(
696  FlashMessage::class,
697  implode(LF, $messages),
698  $label,
699  ContextualFeedbackSeverity::OK,
700  true
701  );
702  $defaultFlashMessageQueue->enqueue($flashMessage);
703  }
704  }
705 
706  // If a document should be duplicated.
707  if (isset($parsedBody['_duplicatedoc']) && is_array($this->editconf)) {
708  $this->‪closeDocument(self::DOCUMENT_CLOSE_MODE_NO_REDIRECT, $request);
709  // Find current table
710  reset($this->editconf);
711  $nTable = (string)key($this->editconf);
712  // Find the first id, getting the records pid+uid
713  reset($this->editconf[$nTable]);
714  $nUid = key($this->editconf[$nTable]);
716  $nUid = $tce->substNEWwithIDs[$nUid];
717  }
718 
719  $recordFields = 'pid,uid';
720  if (BackendUtility::isTableWorkspaceEnabled($nTable)) {
721  $recordFields .= ',t3ver_oid';
722  }
723  $nRec = BackendUtility::getRecord($nTable, $nUid, $recordFields);
724 
725  // Setting a blank editconf array for a new record:
726  $this->editconf = [];
727 
728  if ((int)($nRec['t3ver_oid'] ?? 0) === 0) {
729  $relatedPageId = -$nRec['uid'];
730  } else {
731  $relatedPageId = -$nRec['t3ver_oid'];
732  }
733 
734  $duplicateTce = GeneralUtility::makeInstance(DataHandler::class);
735 
736  $duplicateCmd = [
737  $nTable => [
738  $nUid => [
739  'copy' => $relatedPageId,
740  ],
741  ],
742  ];
743 
744  $duplicateTce->start([], $duplicateCmd);
745  $duplicateTce->process_cmdmap();
746 
747  $duplicateMappingArray = $duplicateTce->copyMappingArray;
748  $duplicateUid = $duplicateMappingArray[$nTable][$nUid];
749 
750  if ($nTable === 'pages') {
751  BackendUtility::setUpdateSignal('updatePageTree');
752  }
753 
754  $this->editconf[$nTable][$duplicateUid] = 'edit';
755  // Finally, set the editconf array in the "getvars" so they will be passed along in URLs as needed.
756  $this->R_URL_getvars['edit'] = ‪$this->editconf;
757  // Recompile the store* values since editconf changed...
758  $this->‪compileStoreData($request);
759 
760  // Inform the user of the duplication
761  $view->addFlashMessage($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.recordDuplicated'));
762  }
763 
764  if ($this->closeDoc < self::DOCUMENT_CLOSE_MODE_DEFAULT
765  || isset($parsedBody['_saveandclosedok'])
766  ) {
767  // Redirect if element should be closed after save
768  return $this->‪closeDocument((int)abs($this->closeDoc), $request);
769  }
770  return null;
771  }
772 
776  protected function ‪init(ServerRequestInterface $request): void
777  {
778  $parsedBody = $request->getParsedBody();
779  $queryParams = $request->getQueryParams();
780 
781  $beUser = $this->‪getBackendUser();
782 
783  $this->popViewId = (int)($parsedBody['popViewId'] ?? $queryParams['popViewId'] ?? 0);
784  $this->recTitle = (string)($parsedBody['recTitle'] ?? $queryParams['recTitle'] ?? '');
785  $this->noView = (bool)($parsedBody['noView'] ?? $queryParams['noView'] ?? false);
786  $this->perms_clause = $beUser->getPagePermsClause(‪Permission::PAGE_SHOW);
787 
788  // Preview code is implicit only generated for GET requests, having the query
789  // parameters "popViewId" (the preview page id) and "showPreview" set.
790  if ($this->popViewId && ($queryParams['showPreview'] ?? false)) {
791  // Generate the preview code (markup), which is added to the module body later
792  $this->previewCode = $this->‪generatePreviewCode();
793  // After generating the preview code, those params should not longer be applied to the form
794  // action, as this would otherwise always refresh the preview window on saving the record.
795  unset($this->R_URL_getvars['showPreview'], $this->R_URL_getvars['popViewId']);
796  }
797 
798  // Set other internal variables:
799  $this->R_URL_getvars['returnUrl'] = ‪$this->retUrl;
800  $this->R_URI = $this->R_URL_parts['path'] . ‪HttpUtility::buildQueryString($this->R_URL_getvars, '?');
801 
802  $this->pageRenderer->getJavaScriptRenderer()->includeTaggedImports('backend.form');
803  $this->pageRenderer->addInlineLanguageLabelFile('EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf');
804 
805  $event = new ‪AfterFormEnginePageInitializedEvent($this, $request);
806  $this->eventDispatcher->dispatch($event);
807  }
808 
812  protected function ‪generatePreviewCode(): ?string
813  {
814  $array_keys = array_keys($this->editconf);
815  $this->previewData['table'] = reset($array_keys) ?: null;
816  $array_keys = array_keys($this->editconf[$this->previewData['table']]);
817  $this->previewData['id'] = reset($array_keys) ?: null;
818 
819  $previewPageId = $this->‪getPreviewPageId();
820  $anchorSection = $this->‪getPreviewUrlAnchorSection();
821  $previewPageRootLine = BackendUtility::BEgetRootLine($previewPageId);
822  $previewUrlParameters = $this->‪getPreviewUrlParameters($previewPageId);
823 
824  return ‪PreviewUriBuilder::create($previewPageId)
825  ->withRootLine($previewPageRootLine)
826  ->withSection($anchorSection)
827  ->withAdditionalQueryParameters($previewUrlParameters)
828  ->buildImmediateActionElement([‪PreviewUriBuilder::OPTION_SWITCH_FOCUS => null]);
829  }
830 
834  protected function ‪getPreviewUrlParameters(int $previewPageId): string
835  {
836  $linkParameters = [];
837  $table = ($this->previewData['table'] ?? '') ?: ($this->firstEl['table'] ?? '');
838  $recordId = ($this->previewData['id'] ?? '') ?: ($this->firstEl['uid'] ?? '');
839  $previewConfiguration = BackendUtility::getPagesTSconfig($previewPageId)['TCEMAIN.']['preview.'][$table . '.'] ?? [];
840  $recordArray = BackendUtility::getRecord($table, $recordId);
841 
842  // language handling
843  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
844  if ($languageField && !empty($recordArray[$languageField])) {
845  $recordId = $this->‪resolvePreviewRecordId($table, $recordArray, $previewConfiguration);
846  $language = $recordArray[$languageField];
847  if ($language > 0) {
848  $linkParameters['_language'] = $language;
849  }
850  }
851 
852  // Always use live workspace record uid for the preview
853  if (BackendUtility::isTableWorkspaceEnabled($table) && ($recordArray['t3ver_oid'] ?? 0) > 0) {
854  $recordId = $recordArray['t3ver_oid'];
855  }
856 
857  // map record data to GET parameters
858  if (isset($previewConfiguration['fieldToParameterMap.'])) {
859  foreach ($previewConfiguration['fieldToParameterMap.'] as $field => $parameterName) {
860  $value = $recordArray[$field] ?? '';
861  if ($field === 'uid') {
862  $value = $recordId;
863  }
864  $linkParameters[$parameterName] = $value;
865  }
866  }
867 
868  // add/override parameters by configuration
869  if (isset($previewConfiguration['additionalGetParameters.'])) {
870  $linkParameters = array_replace(
871  $linkParameters,
872  GeneralUtility::removeDotsFromTS($previewConfiguration['additionalGetParameters.'])
873  );
874  }
875 
876  return ‪HttpUtility::buildQueryString($linkParameters, '&');
877  }
878 
879  protected function ‪resolvePreviewRecordId(string $table, array $recordArray, array $previewConfiguration): int
880  {
881  $l10nPointer = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '';
882  if ($l10nPointer
883  && !empty($recordArray[$l10nPointer])
884  && (
885  // not set -> default to true
886  !isset($previewConfiguration['useDefaultLanguageRecord'])
887  // or set -> use value
888  || $previewConfiguration['useDefaultLanguageRecord']
889  )
890  ) {
891  return (int)$recordArray[$l10nPointer];
892  }
893  return (int)$recordArray['uid'];
894  }
895 
899  protected function ‪getPreviewUrlAnchorSection(): string
900  {
901  $table = ($this->previewData['table'] ?? '') ?: ($this->firstEl['table'] ?? '');
902  $recordId = ($this->previewData['id'] ?? '') ?: ($this->firstEl['uid'] ?? '');
903 
904  return $table === 'tt_content' ? '#c' . (int)$recordId : '';
905  }
906 
910  protected function ‪getPreviewPageId(): int
911  {
912  $previewPageId = 0;
913  $table = ($this->previewData['table'] ?? '') ?: ($this->firstEl['table'] ?? '');
914  $recordId = ($this->previewData['id'] ?? '') ?: ($this->firstEl['uid'] ?? '');
915  $pageId = $this->popViewId ?: ‪$this->viewId;
916 
917  if ($table === 'pages') {
918  $currentPageId = (int)$recordId;
919  } else {
920  $currentPageId = ‪MathUtility::convertToPositiveInteger($pageId);
921  }
922 
923  $previewConfiguration = BackendUtility::getPagesTSconfig($currentPageId)['TCEMAIN.']['preview.'][$table . '.'] ?? [];
924 
925  if (isset($previewConfiguration['previewPageId'])) {
926  $previewPageId = (int)$previewConfiguration['previewPageId'];
927  }
928  // if no preview page was configured
929  if (!$previewPageId) {
930  $rootPageData = null;
931  $rootLine = BackendUtility::BEgetRootLine($currentPageId);
932  $currentPage = (array)(reset($rootLine) ?: []);
933  if ($this->‪canViewDoktype($currentPage)) {
934  // try the current page
935  $previewPageId = $currentPageId;
936  } else {
937  // or search for the root page
938  foreach ($rootLine as $page) {
939  if ($page['is_siteroot']) {
940  $rootPageData = $page;
941  break;
942  }
943  }
944  $previewPageId = isset($rootPageData)
945  ? (int)$rootPageData['uid']
946  : $currentPageId;
947  }
948  }
949 
950  $this->popViewId = $previewPageId;
951 
952  return $previewPageId;
953  }
954 
958  protected function ‪canViewDoktype(array $currentPage): bool
959  {
960  if (!isset($currentPage['uid']) || !($currentPage['doktype'] ?? false)) {
961  // In case the current page record is invalid, the element can not be viewed
962  return false;
963  }
964 
965  return !in_array((int)$currentPage['doktype'], [
968  ], true);
969  }
970 
974  protected function ‪main(ModuleTemplate $view, ServerRequestInterface $request): string
975  {
976  $body = $this->previewCode ?? '';
977  // Begin edit
978  if (is_array($this->editconf)) {
979  $this->formResultCompiler = GeneralUtility::makeInstance(FormResultCompiler::class);
980 
981  // Creating the editing form, wrap it with buttons, document selector etc.
982  $editForm = $this->‪makeEditForm($request, $view);
983  if ($editForm) {
984  $this->firstEl = $this->elementsData ? reset($this->elementsData) : null;
985  // Checking if the currently open document is stored in the list of "open documents" - if not, add it:
986  if ((($this->docDat[1] ?? null) !== $this->storeUrlMd5 || !isset($this->docHandler[$this->storeUrlMd5]))
987  && !$this->dontStoreDocumentRef
988  ) {
989  $this->docHandler[‪$this->storeUrlMd5] = [
995  ];
996  $this->‪getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->storeUrlMd5]);
997  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
998  }
999  $body .= $this->formResultCompiler->addCssFiles();
1000  $body .= $this->‪compileForm($editForm);
1001  $body .= $this->formResultCompiler->printNeededJSFunctions();
1002  $body .= '</form>';
1003  }
1004  }
1005 
1006  if ($this->firstEl === null) {
1007  // In case firstEl is null, no edit form could be created. Therefore, add an
1008  // info box and remove the spinner, since it will never be resolved by FormEngine.
1009  $view->setUiBlock(false);
1010  $body .= $this->‪getInfobox(
1011  $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:noEditForm.message'),
1012  $this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:noEditForm'),
1013  );
1014  }
1015 
1016  // Access check...
1017  // The page will show only if there is a valid page and if this page may be viewed by the user
1018  $this->pageinfo = BackendUtility::readPageAccess($this->viewId, $this->perms_clause) ?: [];
1019  // Setting up the buttons and markers for doc header
1020  $this->‪resolveMetaInformation($view);
1021  $this->‪getButtons($view, $request);
1022 
1023  // Create language switch options if the record is already persisted, and it is a single record to edit
1024  if ($this->isSavedRecord && $this->‪isSingleRecordView()) {
1026  $view,
1027  (string)($this->firstEl['table'] ?? ''),
1028  (int)($this->firstEl['uid'] ?? 0),
1029  isset($this->firstEl['pid']) ? (int)$this->firstEl['pid'] : null
1030  );
1031  }
1032 
1033  return $body;
1034  }
1035 
1036  protected function ‪resolveMetaInformation(ModuleTemplate $view): void
1037  {
1038  $file = null;
1039  if (($this->firstEl['table'] ?? '') === 'sys_file_metadata' && (int)($this->firstEl['uid'] ?? 0) > 0) {
1040  $fileUid = (int)(BackendUtility::getRecord('sys_file_metadata', (int)$this->firstEl['uid'], 'file')['file'] ?? 0);
1041  try {
1042  $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject($fileUid);
1043  } catch (FileDoesNotExistException|InsufficientUserPermissionsException $e) {
1044  // do nothing when file is not accessible
1045  }
1046  }
1047  if ($file instanceof FileInterface) {
1048  $view->getDocHeaderComponent()->setMetaInformationForResource($file);
1049  } elseif ($this->pageinfo !== []) {
1050  $view->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
1051  }
1052  }
1053 
1059  protected function ‪makeEditForm(ServerRequestInterface $request, ModuleTemplate $view): string
1060  {
1061  // Initialize variables
1062  $this->elementsData = [];
1063  $this->errorC = 0;
1064  $editForm = '';
1065  $beUser = $this->‪getBackendUser();
1066  // Traverse the GPvar edit array tables
1067  foreach ($this->editconf as $table => $conf) {
1068  if (!is_array($conf) || !(‪$GLOBALS['TCA'][$table] ?? false)) {
1069  // Skip for invalid config or in case no TCA exists
1070  continue;
1071  }
1072  if (!$beUser->check('tables_modify', $table)) {
1073  // Skip in case the user has insufficient permissions and increment the error counter
1074  $this->errorC++;
1075  continue;
1076  }
1077  // Traverse the keys/comments of each table (keys can be a comma list of uids)
1078  foreach ($conf as $cKey => $command) {
1079  if ($command !== 'edit' && $command !== 'new') {
1080  // Skip if invalid command
1081  continue;
1082  }
1083  // Get the ids:
1084  $ids = GeneralUtility::trimExplode(',', (string)$cKey, true);
1085  // Traverse the ids:
1086  foreach ($ids as $theUid) {
1087  // Don't save this document title in the document selector if the document is new.
1088  if ($command === 'new') {
1089  $this->dontStoreDocumentRef = 1;
1090  }
1091 
1092  try {
1093  $nodeFactory = GeneralUtility::makeInstance(NodeFactory::class);
1094 
1095  // Reset viewId - it should hold data of last entry only
1096  $this->viewId = 0;
1097 
1098  $formDataCompilerInput = [
1099  'request' => $request,
1100  'tableName' => $table,
1101  'vanillaUid' => (int)$theUid,
1102  'command' => $command,
1103  'returnUrl' => $this->R_URI,
1104  ];
1105  if (is_array($this->overrideVals) && is_array($this->overrideVals[$table])) {
1106  $formDataCompilerInput['overrideValues'] = $this->overrideVals[$table];
1107  }
1108  if (!empty($this->defVals) && is_array($this->defVals)) {
1109  $formDataCompilerInput['defaultValues'] = ‪$this->defVals;
1110  }
1111 
1112  $formData = $this->formDataCompiler->compile($formDataCompilerInput, GeneralUtility::makeInstance(TcaDatabaseRecord::class));
1113 
1114  // Set this->viewId if possible
1115  if ($command === 'new'
1116  && $table !== 'pages'
1117  && !empty($formData['parentPageRow']['uid'])
1118  ) {
1119  $this->viewId = $formData['parentPageRow']['uid'];
1120  } else {
1121  if ($table === 'pages') {
1122  $this->viewId = $formData['databaseRow']['uid'];
1123  } elseif (!empty($formData['parentPageRow']['uid'])) {
1124  $this->viewId = $formData['parentPageRow']['uid'];
1125  }
1126  }
1127 
1128  // Determine if delete button can be shown
1129  $permission = new Permission($formData['userPermissionOnPage']);
1130  if ($formData['tableName'] === 'pages') {
1131  $deleteAccess = $permission->get(‪Permission::PAGE_DELETE);
1132  } else {
1133  $deleteAccess = $permission->get(‪Permission::CONTENT_EDIT);
1134  }
1135 
1136  // Display "is-locked" message
1137  if ($command === 'edit') {
1138  $lockInfo = BackendUtility::isRecordLocked($table, $formData['databaseRow']['uid']);
1139  if ($lockInfo) {
1140  $view->addFlashMessage($lockInfo['msg'], '', ContextualFeedbackSeverity::WARNING);
1141  }
1142  }
1143 
1144  // Record title
1145  if (!$this->storeTitle) {
1146  $this->storeTitle = htmlspecialchars($this->recTitle ?: ($formData['recordTitle'] ?? ''));
1147  }
1148 
1149  $this->elementsData[] = [
1150  'table' => $table,
1151  'uid' => $formData['databaseRow']['uid'],
1152  'pid' => $formData['databaseRow']['pid'],
1153  'cmd' => $command,
1154  'deleteAccess' => $deleteAccess,
1155  ];
1156 
1157  if ($command !== 'new') {
1158  BackendUtility::lockRecords($table, $formData['databaseRow']['uid'], $table === 'tt_content' ? $formData['databaseRow']['pid'] : 0);
1159  }
1160 
1161  // Set list if only specific fields should be rendered. This will trigger
1162  // ListOfFieldsContainer instead of FullRecordContainer in OuterWrapContainer
1163  if ($this->columnsOnly) {
1164  if (is_array($this->columnsOnly)) {
1165  $formData['fieldListToRender'] = $this->columnsOnly[$table];
1166  } else {
1167  $formData['fieldListToRender'] = ‪$this->columnsOnly;
1168  }
1169  }
1170 
1171  $formData['renderType'] = 'outerWrapContainer';
1172  $formResult = $nodeFactory->create($formData)->render();
1173 
1174  $html = $formResult['html'];
1175 
1176  $formResult['html'] = '';
1177  $formResult['doSaveFieldName'] = 'doSave';
1178 
1179  // @todo: Put all the stuff into FormEngine as final "compiler" class
1180  // @todo: This is done here for now to not rewrite addCssFiles()
1181  // @todo: and printNeededJSFunctions() now
1182  $this->formResultCompiler->mergeResult($formResult);
1183 
1184  // Seems the pid is set as hidden field (again) at end?!
1185  if ($command === 'new') {
1186  // @todo: looks ugly
1187  $html .= LF
1188  . '<input type="hidden"'
1189  . ' name="data[' . htmlspecialchars($table) . '][' . htmlspecialchars($formData['databaseRow']['uid']) . '][pid]"'
1190  . ' value="' . (int)$formData['databaseRow']['pid'] . '" />';
1191  }
1192 
1193  $editForm .= $html;
1194  } catch (AccessDeniedException $e) {
1195  $this->errorC++;
1196  // Try to fetch error message from "recordInternals" be user object
1197  // @todo: This construct should be logged and localized and de-uglified
1198  $message = (!empty($beUser->errorMsg)) ? $beUser->errorMsg : $e->getMessage() . ' ' . $e->getCode();
1199  $title = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.noEditPermission');
1200  $editForm .= $this->‪getInfobox($message, $title);
1202  $editForm .= $this->‪getInfobox($e->getMessage());
1203  }
1204  } // End of for each uid
1205  }
1206  }
1207  return $editForm;
1208  }
1209 
1213  protected function ‪getInfobox(string $message, ?string $title = null): string
1214  {
1215  return '<div class="callout callout-danger">' .
1216  '<div class="media">' .
1217  '<div class="media-left">' .
1218  '<span class="icon-emphasized">' .
1219  $this->iconFactory->getIcon('actions-close', IconSize::SMALL)->render() .
1220  '</span>' .
1221  '</div>' .
1222  '<div class="media-body">' .
1223  ($title ? '<h4 class="callout-title">' . htmlspecialchars($title) . '</h4>' : '') .
1224  '<div class="callout-body">' . htmlspecialchars($message) . '</div>' .
1225  '</div>' .
1226  '</div>' .
1227  '</div>';
1228  }
1229 
1233  protected function ‪getButtons(ModuleTemplate $view, ServerRequestInterface $request): void
1234  {
1235  $buttonBar = $view->getDocHeaderComponent()->getButtonBar();
1236  if (!empty($this->firstEl)) {
1237  ‪$record = BackendUtility::getRecord($this->firstEl['table'], $this->firstEl['uid']);
1238  $TCActrl = ‪$GLOBALS['TCA'][$this->firstEl['table']]['ctrl'];
1239 
1240  $this->‪setIsSavedRecord();
1241 
1242  $sysLanguageUid = 0;
1243  if (
1244  $this->isSavedRecord
1245  && isset($TCActrl['languageField'], ‪$record[$TCActrl['languageField']])
1246  ) {
1247  $sysLanguageUid = (int)‪$record[$TCActrl['languageField']];
1248  } elseif (isset($this->defVals['sys_language_uid'])) {
1249  $sysLanguageUid = (int)$this->defVals['sys_language_uid'];
1250  }
1251 
1252  $l18nParent = isset($TCActrl['transOrigPointerField'], ‪$record[$TCActrl['transOrigPointerField']])
1253  ? (int)‪$record[$TCActrl['transOrigPointerField']]
1254  : 0;
1255 
1256  $this->‪setIsPageInFreeTranslationMode(‪$record, $sysLanguageUid);
1257 
1259 
1260  // Show buttons when table is not read-only
1261  if (
1262  !$this->errorC
1263  && !(‪$GLOBALS['TCA'][$this->firstEl['table']]['ctrl']['readOnly'] ?? false)
1264  ) {
1267  if ($this->firstEl['cmd'] !== 'new') {
1269  $buttonBar,
1271  4,
1272  $sysLanguageUid,
1273  $l18nParent
1274  );
1276  $buttonBar,
1278  5,
1279  $sysLanguageUid,
1280  $l18nParent
1281  );
1282  }
1286  }
1287  }
1288 
1291  }
1292 
1296  protected function ‪setIsSavedRecord(): void
1297  {
1298  $this->isSavedRecord = (
1299  $this->firstEl['cmd'] !== 'new'
1300  && ‪MathUtility::canBeInterpretedAsInteger($this->firstEl['uid'])
1301  );
1302  }
1303 
1307  protected function ‪isInconsistentLanguageHandlingAllowed(): bool
1308  {
1309  $allowInconsistentLanguageHandling = BackendUtility::getPagesTSconfig(
1310  $this->pageinfo['uid'] ?? 0
1311  )['mod']['web_layout']['allowInconsistentLanguageHandling'] ?? ['value' => '0'];
1312 
1313  return $allowInconsistentLanguageHandling['value'] === '1';
1314  }
1315 
1319  protected function ‪setIsPageInFreeTranslationMode(?array ‪$record, int $sysLanguageUid): void
1320  {
1321  if ($this->firstEl['table'] === 'tt_content') {
1322  if (!$this->isSavedRecord) {
1323  $this->isPageInFreeTranslationMode = $this->‪getFreeTranslationMode(
1324  (int)($this->pageinfo['uid'] ?? 0),
1325  (int)($this->defVals['colPos'] ?? 0),
1326  $sysLanguageUid
1327  );
1328  } else {
1329  $this->isPageInFreeTranslationMode = $this->‪getFreeTranslationMode(
1330  (int)($this->pageinfo['uid'] ?? 0),
1331  (int)(‪$record['colPos'] ?? 0),
1332  $sysLanguageUid
1333  );
1334  }
1335  }
1336  }
1337 
1341  protected function ‪getFreeTranslationMode(int $page, int $column, int $language): bool
1342  {
1343  $freeTranslationMode = false;
1344  if ($this->‪getConnectedContentElementTranslationsCount($page, $column, $language) === 0
1345  && $this->‪getStandAloneContentElementTranslationsCount($page, $column, $language) >= 0
1346  ) {
1347  $freeTranslationMode = true;
1348  }
1349  return $freeTranslationMode;
1350  }
1351 
1355  protected function ‪registerCloseButtonToButtonBar(‪ButtonBar $buttonBar, string $position, int $group): void
1356  {
1357  $closeButton = $buttonBar->‪makeLinkButton()
1358  ->‪setHref('#')
1359  ->‪setClasses('t3js-editform-close')
1360  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
1361  ->setShowLabelText(true)
1362  ->setIcon($this->iconFactory->getIcon('actions-close', IconSize::SMALL));
1363  $buttonBar->‪addButton($closeButton, $position, $group);
1364  }
1365 
1369  protected function ‪registerSaveButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group): void
1370  {
1371  $saveButton = $buttonBar->makeInputButton()
1372  ->setForm('EditDocumentController')
1373  ->setIcon($this->iconFactory->getIcon('actions-document-save', IconSize::SMALL))
1374  ->setName('_savedok')
1375  ->setShowLabelText(true)
1376  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
1377  ->setValue('1');
1378  $buttonBar->addButton($saveButton, $position, $group);
1379  }
1380 
1384  protected function ‪registerViewButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group): void
1385  {
1386  if ($this->viewId // Pid to show the record
1387  && !$this->noView // Passed parameter
1388  && !empty($this->firstEl['table']) // No table
1389  // @TODO: TsConfig option should change to viewDoc
1390  && $this->‪getTsConfigOption($this->firstEl['table'], 'saveDocView')
1391  ) {
1392  $pagesTSconfig = BackendUtility::getPagesTSconfig($this->pageinfo['uid'] ?? 0);
1393  if (isset($pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'])) {
1394  $excludeDokTypes = GeneralUtility::intExplode(',', (string)$pagesTSconfig['TCEMAIN.']['preview.']['disableButtonForDokType'], true);
1395  } else {
1396  // exclude sys-folders and spacers by default
1397  $excludeDokTypes = [
1400  ];
1401  }
1402  if (
1403  !in_array((int)($this->pageinfo['doktype'] ?? 0), $excludeDokTypes, true)
1404  || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
1405  ) {
1406  $previewPageId = $this->‪getPreviewPageId();
1407  $previewUrl = (string)‪PreviewUriBuilder::create($previewPageId)
1408  ->withSection($this->‪getPreviewUrlAnchorSection())
1409  ->withAdditionalQueryParameters($this->‪getPreviewUrlParameters($previewPageId))
1410  ->buildUri();
1411  if ($previewUrl !== '') {
1412  $viewButton = $buttonBar->makeLinkButton()
1413  ->setHref($previewUrl)
1414  ->setIcon($this->iconFactory->getIcon('actions-view', IconSize::SMALL))
1415  ->setShowLabelText(true)
1416  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.viewDoc'))
1417  ->setClasses('t3js-editform-view');
1418  if (!$this->isSavedRecord && $this->firstEl['table'] === 'pages') {
1419  $viewButton->setDataAttributes(['is-new' => '']);
1420  }
1421  $buttonBar->addButton($viewButton, $position, $group);
1422  }
1423  }
1424  }
1425  }
1426 
1430  protected function ‪registerNewButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, int $sysLanguageUid, int $l18nParent): void
1431  {
1432  if ($this->firstEl['table'] !== 'sys_file_metadata'
1433  && !empty($this->firstEl['table'])
1434  && (
1435  (
1436  (
1438  || $this->isPageInFreeTranslationMode
1439  )
1440  && $this->firstEl['table'] === 'tt_content'
1441  )
1442  || (
1443  $this->firstEl['table'] !== 'tt_content'
1444  && (
1445  $sysLanguageUid === 0
1446  || $l18nParent === 0
1447  )
1448  )
1449  )
1450  && $this->‪getTsConfigOption($this->firstEl['table'], 'saveDocNew')
1451  ) {
1452  $newButton = $buttonBar->makeLinkButton()
1453  ->setHref('#')
1454  ->setIcon($this->iconFactory->getIcon('actions-plus', IconSize::SMALL))
1455  ->setShowLabelText(true)
1456  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.newDoc'))
1457  ->setClasses('t3js-editform-new');
1458  if (!$this->isSavedRecord) {
1459  $newButton->setDataAttributes(['is-new' => '']);
1460  }
1461  $buttonBar->addButton($newButton, $position, $group);
1462  }
1463  }
1464 
1468  protected function ‪registerDuplicationButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, int $sysLanguageUid, int $l18nParent): void
1469  {
1470  if ($this->firstEl['table'] !== 'sys_file_metadata'
1471  && !empty($this->firstEl['table'])
1472  && (
1473  (
1474  (
1476  || $this->isPageInFreeTranslationMode
1477  )
1478  && $this->firstEl['table'] === 'tt_content'
1479  )
1480  || (
1481  $this->firstEl['table'] !== 'tt_content'
1482  && (
1483  $sysLanguageUid === 0
1484  || $l18nParent === 0
1485  )
1486  )
1487  )
1488  && $this->‪getTsConfigOption($this->firstEl['table'], 'showDuplicate')
1489  ) {
1490  $duplicateButton = $buttonBar->makeLinkButton()
1491  ->setHref('#')
1492  ->setShowLabelText(true)
1493  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.duplicateDoc'))
1494  ->setIcon($this->iconFactory->getIcon('actions-document-duplicates-select', IconSize::SMALL))
1495  ->setClasses('t3js-editform-duplicate');
1496  if (!$this->isSavedRecord) {
1497  $duplicateButton->setDataAttributes(['is-new' => '']);
1498  }
1499  $buttonBar->addButton($duplicateButton, $position, $group);
1500  }
1501  }
1502 
1506  protected function ‪registerDeleteButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, ServerRequestInterface $request): void
1507  {
1508  if ($this->firstEl['deleteAccess']
1509  && !$this->‪getDisableDelete()
1510  && !$this->‪isRecordCurrentBackendUser()
1511  && $this->isSavedRecord
1512  && $this->‪isSingleRecordView()
1513  ) {
1515  if ($this->firstEl['table'] === 'pages') {
1516  // The below is a hack to replace the return url with an url to the current module on id=0. Otherwise,
1517  // this might lead to empty views, since the current id is the page, which is about to be deleted.
1518  $parsedUrl = parse_url(‪$returnUrl);
1519  $routePath = str_replace($this->backendEntryPointResolver->getPathFromRequest($request), '', $parsedUrl['path'] ?? '');
1520  parse_str($parsedUrl['query'] ?? '', $queryParams);
1521  if ($routePath
1522  && isset($queryParams['id'])
1523  && (string)$this->firstEl['uid'] === (string)$queryParams['id']
1524  ) {
1525  try {
1526  // TODO: Use the page's pid instead of 0, this requires a clean API to manipulate the page
1527  // tree from the outside to be able to mark the pid as active
1528  ‪$returnUrl = (string)$this->uriBuilder->buildUriFromRoutePath($routePath, ['id' => 0]);
1529  } catch (ResourceNotFoundException $e) {
1530  // Resolved path can not be matched to a configured route
1531  }
1532  }
1533  }
1534 
1535  $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
1536  $numberOfReferences = $referenceIndex->getNumberOfReferencedRecords(
1537  $this->firstEl['table'],
1538  (int)$this->firstEl['uid']
1539  );
1540  $referenceCountMessage = BackendUtility::referenceCount(
1541  $this->firstEl['table'],
1542  (string)(int)$this->firstEl['uid'],
1543  $this->‪getLanguageService()->sL(
1544  'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.referencesToRecord'
1545  ),
1546  (string)$numberOfReferences
1547  );
1548  $translationCountMessage = BackendUtility::translationCount(
1549  $this->firstEl['table'],
1550  (string)(int)$this->firstEl['uid'],
1551  $this->‪getLanguageService()->sL(
1552  'LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.translationsOfRecord'
1553  )
1554  );
1555 
1556  $deleteUrl = (string)$this->uriBuilder->buildUriFromRoute('tce_db', [
1557  'cmd' => [
1558  $this->firstEl['table'] => [
1559  $this->firstEl['uid'] => [
1560  'delete' => '1',
1561  ],
1562  ],
1563  ],
1564  'redirect' => ‪$returnUrl,
1565  ]);
1566 
1567  $recordInfo = ‪$this->storeTitle;
1568  if ($this->‪getBackendUser()->shallDisplayDebugInformation()) {
1569  $recordInfo .= ' [' . $this->firstEl['table'] . ':' . $this->firstEl['uid'] . ']';
1570  }
1571 
1572  $deleteButton = $buttonBar->makeLinkButton()
1573  ->setClasses('t3js-editform-delete-record')
1574  ->setDataAttributes([
1575  'uid' => $this->firstEl['uid'],
1576  'table' => $this->firstEl['table'],
1577  'record-info' => trim($recordInfo),
1578  'reference-count-message' => $referenceCountMessage,
1579  'translation-count-message' => $translationCountMessage,
1580  ])
1581  ->setHref($deleteUrl)
1582  ->setIcon($this->iconFactory->getIcon('actions-edit-delete', IconSize::SMALL))
1583  ->setShowLabelText(true)
1584  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:deleteItem'));
1585  $buttonBar->addButton($deleteButton, $position, $group);
1586  }
1587  }
1588 
1592  protected function ‪registerHistoryButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group): void
1593  {
1594  if ($this->‪isSingleRecordView()
1595  && !empty($this->firstEl['table'])
1596  && $this->‪getTsConfigOption($this->firstEl['table'], 'showHistory')
1597  ) {
1598  $historyUrl = (string)$this->uriBuilder->buildUriFromRoute('record_history', [
1599  'element' => $this->firstEl['table'] . ':' . $this->firstEl['uid'],
1600  'returnUrl' => $this->R_URI,
1601  ]);
1602  $historyButton = $buttonBar->makeLinkButton()
1603  ->setHref($historyUrl)
1604  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:recordHistory'))
1605  ->setIcon($this->iconFactory->getIcon('actions-document-history-open', IconSize::SMALL));
1606  $buttonBar->addButton($historyButton, $position, $group);
1607  }
1608  }
1609 
1613  protected function ‪registerColumnsOnlyButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group): void
1614  {
1615  if ($this->columnsOnly
1616  && $this->‪isSingleRecordView()
1617  ) {
1618  $columnsOnlyButton = $buttonBar->makeLinkButton()
1619  ->setHref($this->R_URI . '&columnsOnly=')
1620  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_alt_doc.xlf:editWholeRecord'))
1621  ->setShowLabelText(true)
1622  ->setIcon($this->iconFactory->getIcon('actions-open', IconSize::SMALL));
1623 
1624  $buttonBar->addButton($columnsOnlyButton, $position, $group);
1625  }
1626  }
1627 
1631  protected function ‪registerOpenInNewWindowButtonToButtonBar(‪ButtonBar $buttonBar, string $position, int $group, ServerRequestInterface $request): void
1632  {
1633  $closeUrl = $this->‪getCloseUrl();
1634  if ($this->returnUrl !== $closeUrl) {
1635  // Generate a URL to the current edit form
1636  $arguments = $this->‪getUrlQueryParamsForCurrentRequest($request);
1637  $arguments['returnUrl'] = $closeUrl;
1638  $requestUri = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $arguments);
1639  $openInNewWindowButton = $buttonBar
1640  ->‪makeLinkButton()
1641  ->‪setHref('#')
1642  ->‪setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.openInNewWindow'))
1643  ->setIcon($this->iconFactory->getIcon('actions-window-open', IconSize::SMALL))
1644  ->setDataAttributes([
1645  'dispatch-action' => 'TYPO3.WindowManager.localOpen',
1646  'dispatch-args' => GeneralUtility::jsonEncodeForHtmlAttribute([
1647  $requestUri,
1648  true, // switchFocus
1649  md5($this->R_URI), // windowName,
1650  'width=670,height=500,status=0,menubar=0,scrollbars=1,resizable=1', // windowFeatures
1651  ]),
1652  ]);
1653  $buttonBar->‪addButton($openInNewWindowButton, $position, $group);
1654  }
1655  }
1656 
1660  protected function ‪registerShortcutButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, ServerRequestInterface $request): void
1661  {
1662  if ($this->returnUrl !== $this->‪getCloseUrl()) {
1663  $arguments = $this->‪getUrlQueryParamsForCurrentRequest($request);
1664  $shortCutButton = $buttonBar->makeShortcutButton()
1665  ->setRouteIdentifier('record_edit')
1666  ->setDisplayName($this->‪getShortcutTitle($request))
1667  ->setArguments($arguments);
1668  $buttonBar->addButton($shortCutButton, $position, $group);
1669  }
1670  }
1671 
1672  protected function ‪getUrlQueryParamsForCurrentRequest(ServerRequestInterface $request): array
1673  {
1674  $queryParams = $request->getQueryParams();
1675  $potentialArguments = [
1676  'edit',
1677  'defVals',
1678  'overrideVals',
1679  'columnsOnly',
1680  'returnNewPageId',
1681  'noView',
1682  ];
1683  $arguments = [];
1684  foreach ($potentialArguments as $argument) {
1685  if (!empty($queryParams[$argument])) {
1686  $arguments[$argument] = $queryParams[$argument];
1687  }
1688  }
1689  return $arguments;
1690  }
1691 
1695  protected function ‪getConnectedContentElementTranslationsCount(int $page, int $column, int $language): int
1696  {
1697  $queryBuilder = $this->‪getQueryBuilderForTranslationMode($page, $column, $language);
1698  return (int)$queryBuilder
1699  ->andWhere(
1700  $queryBuilder->expr()->gt(
1701  ‪$GLOBALS['TCA']['tt_content']['ctrl']['transOrigPointerField'],
1702  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1703  )
1704  )
1705  ->executeQuery()
1706  ->fetchOne();
1707  }
1708 
1712  protected function ‪getStandAloneContentElementTranslationsCount(int $page, int $column, int $language): int
1713  {
1714  $queryBuilder = $this->‪getQueryBuilderForTranslationMode($page, $column, $language);
1715  return (int)$queryBuilder
1716  ->andWhere(
1717  $queryBuilder->expr()->eq(
1718  ‪$GLOBALS['TCA']['tt_content']['ctrl']['transOrigPointerField'],
1719  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1720  )
1721  )
1722  ->executeQuery()
1723  ->fetchOne();
1724  }
1725 
1729  protected function ‪getQueryBuilderForTranslationMode(int $page, int $column, int $language): QueryBuilder
1730  {
1731  $languageField = ‪$GLOBALS['TCA']['tt_content']['ctrl']['languageField'];
1732  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
1733  $queryBuilder->getRestrictions()
1734  ->removeAll()
1735  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1736  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪getBackendUser()->workspace));
1737  return $queryBuilder
1738  ->count('uid')
1739  ->from('tt_content')
1740  ->where(
1741  $queryBuilder->expr()->eq(
1742  'pid',
1743  $queryBuilder->createNamedParameter($page, ‪Connection::PARAM_INT)
1744  ),
1745  $queryBuilder->expr()->eq(
1746  $languageField,
1747  $queryBuilder->createNamedParameter($language, ‪Connection::PARAM_INT)
1748  ),
1749  $queryBuilder->expr()->eq(
1750  'colPos',
1751  $queryBuilder->createNamedParameter($column, ‪Connection::PARAM_INT)
1752  )
1753  );
1754  }
1762  protected function ‪compileForm(string $editForm): string
1763  {
1764  $formContent = '
1765  <form
1766  action="' . htmlspecialchars($this->R_URI) . '"
1767  method="post"
1768  enctype="multipart/form-data"
1769  name="editform"
1770  id="EditDocumentController"
1771  >
1772  ' . $editForm . '
1773  <input type="hidden" name="returnUrl" value="' . htmlspecialchars($this->retUrl) . '" />
1774  <input type="hidden" name="popViewId" value="' . htmlspecialchars((string)$this->viewId) . '" />
1775  <input type="hidden" name="closeDoc" value="0" />
1776  <input type="hidden" name="doSave" value="0" />';
1777  if ($this->returnNewPageId) {
1778  $formContent .= '<input type="hidden" name="returnNewPageId" value="1" />';
1779  }
1780  return $formContent;
1781  }
1782 
1789  protected function ‪updateInlineView(?array $uc, ‪DataHandler $dataHandler): void
1790  {
1791  $backendUser = $this->‪getBackendUser();
1792  if (!isset($uc['inlineView']) || !is_array($uc['inlineView'])) {
1793  return;
1794  }
1795  $inlineView = (array)json_decode(is_string($backendUser->uc['inlineView'] ?? false) ? $backendUser->uc['inlineView'] : '', true);
1796  foreach ($uc['inlineView'] as $topTable => $topRecords) {
1797  foreach ($topRecords as $topUid => $childElements) {
1798  foreach ($childElements as $childTable => $childRecords) {
1799  $uids = array_keys($dataHandler->substNEWwithIDs_table, $childTable);
1800  if (!empty($uids)) {
1801  $newExpandedChildren = [];
1802  foreach ($childRecords as $childUid => $state) {
1803  if ($state && in_array($childUid, $uids)) {
1804  $newChildUid = $dataHandler->substNEWwithIDs[$childUid];
1805  $newExpandedChildren[] = $newChildUid;
1806  }
1807  }
1808  // Add new expanded child records to UC (if any):
1809  if (!empty($newExpandedChildren)) {
1810  $inlineViewCurrent = &$inlineView[$topTable][$topUid][$childTable];
1811  if (is_array($inlineViewCurrent)) {
1812  $inlineViewCurrent = array_unique(array_merge($inlineViewCurrent, $newExpandedChildren));
1813  } else {
1814  $inlineViewCurrent = $newExpandedChildren;
1815  }
1816  }
1817  }
1818  }
1819  }
1820  }
1821  $backendUser->uc['inlineView'] = json_encode($inlineView);
1822  $backendUser->writeUC();
1823  }
1829  protected function ‪getDisableDelete(): bool
1830  {
1831  $disableDelete = false;
1832  if ($this->firstEl['table'] === 'sys_file_metadata') {
1833  $row = BackendUtility::getRecord('sys_file_metadata', $this->firstEl['uid'], 'sys_language_uid');
1834  $languageUid = $row['sys_language_uid'];
1835  if ($languageUid === 0) {
1836  $disableDelete = true;
1837  }
1838  } else {
1839  $disableDelete = (bool)$this->‪getTsConfigOption($this->firstEl['table'] ?? '', 'disableDelete');
1840  }
1841  return $disableDelete;
1842  }
1843 
1847  protected function ‪isRecordCurrentBackendUser(): bool
1848  {
1849  $backendUser = $this->‪getBackendUser();
1850  return $this->firstEl['table'] === 'be_users'
1851  && (int)($this->firstEl['uid'] ?? 0) === (int)$backendUser->user[$backendUser->userid_column];
1852  }
1853 
1858  protected function ‪getCloseUrl(): string
1859  {
1860  return ‪PathUtility::getPublicResourceWebPath('EXT:backend/Resources/Public/Html/Close.html');
1861  }
1862 
1871  protected function ‪languageSwitch(ModuleTemplate $view, string $table, int ‪$uid, ?int $pid = null)
1872  {
1873  $backendUser = $this->‪getBackendUser();
1874  $languageField = ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'] ?? '';
1875  $transOrigPointerField = ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'] ?? '';
1876  // Table editable and activated for languages?
1877  if ($backendUser->check('tables_modify', $table)
1878  && $languageField
1879  && $transOrigPointerField
1880  ) {
1881  if ($pid === null) {
1882  $row = BackendUtility::getRecord($table, ‪$uid, 'pid');
1883  $pid = $row['pid'];
1884  }
1885  // Get all available languages for the page
1886  // If editing a page, the translations of the current UID need to be fetched
1887  if ($table === 'pages') {
1888  $row = BackendUtility::getRecord($table, ‪$uid, ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']);
1889  // Ensure the check is always done against the default language page
1890  $availableLanguages = $this->‪getLanguages(
1891  (int)$row[‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField']] ?: ‪$uid,
1892  $table
1893  );
1894  } else {
1895  $availableLanguages = $this->‪getLanguages((int)$pid, $table);
1896  }
1897  // Remove default language, if user does not have access. This is necessary, since
1898  // the default language is always added when fetching the system languages (#88504).
1899  if (isset($availableLanguages[0]) && !$this->‪getBackendUser()->checkLanguageAccess(0)) {
1900  unset($availableLanguages[0]);
1901  }
1902  // Page available in other languages than default language?
1903  if (count($availableLanguages) > 1) {
1904  $rowsByLang = [];
1905  $fetchFields = 'uid,' . $languageField . ',' . $transOrigPointerField;
1906  // Get record in current language
1907  $rowCurrent = BackendUtility::getLiveVersionOfRecord($table, ‪$uid, $fetchFields);
1908  if (!is_array($rowCurrent)) {
1909  $rowCurrent = BackendUtility::getRecord($table, ‪$uid, $fetchFields);
1910  }
1911  $currentLanguage = (int)$rowCurrent[$languageField];
1912  // Disabled for records with [all] language!
1913  if ($currentLanguage > -1) {
1914  // Get record in default language if needed
1915  if ($currentLanguage && $rowCurrent[$transOrigPointerField]) {
1916  $rowsByLang[0] = BackendUtility::getLiveVersionOfRecord(
1917  $table,
1918  $rowCurrent[$transOrigPointerField],
1919  $fetchFields
1920  );
1921  if (!is_array($rowsByLang[0])) {
1922  $rowsByLang[0] = BackendUtility::getRecord(
1923  $table,
1924  $rowCurrent[$transOrigPointerField],
1925  $fetchFields
1926  );
1927  }
1928  } else {
1929  $rowsByLang[$rowCurrent[$languageField]] = $rowCurrent;
1930  }
1931  // List of language id's that should not be added to the selector
1932  $noAddOption = [];
1933  if ($rowCurrent[$transOrigPointerField] || $currentLanguage === 0) {
1934  // Get record in other languages to see what's already available
1935  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
1936  $queryBuilder->getRestrictions()
1937  ->removeAll()
1938  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1939  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $backendUser->workspace));
1940  $result = $queryBuilder->select(...GeneralUtility::trimExplode(',', $fetchFields, true))
1941  ->from($table)
1942  ->where(
1943  $queryBuilder->expr()->eq(
1944  'pid',
1945  $queryBuilder->createNamedParameter($pid, ‪Connection::PARAM_INT)
1946  ),
1947  $queryBuilder->expr()->gt(
1948  $languageField,
1949  $queryBuilder->createNamedParameter(0, ‪Connection::PARAM_INT)
1950  ),
1951  $queryBuilder->expr()->eq(
1952  $transOrigPointerField,
1953  $queryBuilder->createNamedParameter($rowsByLang[0]['uid'], ‪Connection::PARAM_INT)
1954  )
1955  )
1956  ->executeQuery();
1957  while ($row = $result->fetchAssociative()) {
1958  if ($backendUser->workspace !== 0 && BackendUtility::isTableWorkspaceEnabled($table)) {
1959  $workspaceVersion = BackendUtility::getWorkspaceVersionOfRecord($backendUser->workspace, $table, $row['uid'], 'uid,t3ver_state');
1960  if (!empty($workspaceVersion)) {
1961  $versionState = VersionState::tryFrom($workspaceVersion['t3ver_state'] ?? 0);
1962  if ($versionState === VersionState::DELETE_PLACEHOLDER) {
1963  // If a workspace delete placeholder exists for this translation: Mark
1964  // this language as "don't add to selector" and continue with next row,
1965  // otherwise an edit link to a delete placeholder would be created, which
1966  // does not make sense.
1967  $noAddOption[] = (int)$row[$languageField];
1968  continue;
1969  }
1970  }
1971  }
1972  $rowsByLang[$row[$languageField]] = $row;
1973  }
1974  }
1975  $languageMenu = $view->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1976  $languageMenu->setIdentifier('_langSelector');
1977  foreach ($availableLanguages as $languageId => $language) {
1978  $selectorOptionLabel = $language['title'];
1979  // Create url for creating a localized record
1980  $addOption = true;
1981  $href = '';
1982  if (!isset($rowsByLang[$languageId])) {
1983  // Translation in this language does not exist
1984  if (!isset($rowsByLang[0]['uid'])) {
1985  // Don't add option since no default row to localize from exists
1986  // TODO: Actually tt_content is able to localize from another l10n_source then L=0.
1987  // This however is currently only possible via the translation wizard.
1988  $addOption = false;
1989  } else {
1990  // Build the link to add the localization
1991  $selectorOptionLabel .= ' [' . htmlspecialchars($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.new')) . ']';
1992  $href = (string)$this->uriBuilder->buildUriFromRoute(
1993  'tce_db',
1994  [
1995  'cmd' => [
1996  $table => [
1997  $rowsByLang[0]['uid'] => [
1998  'localize' => $languageId,
1999  ],
2000  ],
2001  ],
2002  'redirect' => (string)$this->uriBuilder->buildUriFromRoute(
2003  'record_edit',
2004  [
2005  'justLocalized' => $table . ':' . $rowsByLang[0]['uid'] . ':' . $languageId,
2006  'returnUrl' => $this->retUrl,
2007  ]
2008  ),
2009  ]
2010  );
2011  }
2012  } else {
2013  $params = [
2014  'edit[' . $table . '][' . $rowsByLang[$languageId]['uid'] . ']' => 'edit',
2015  'returnUrl' => ‪$this->retUrl,
2016  ];
2017  if ($table === 'pages') {
2018  // Disallow manual adjustment of the language field for pages
2019  $params['overrideVals'] = [
2020  'pages' => [
2021  'sys_language_uid' => $languageId,
2022  ],
2023  ];
2024  }
2025  $href = (string)$this->uriBuilder->buildUriFromRoute('record_edit', $params);
2026  }
2027  if ($addOption && !in_array($languageId, $noAddOption, true)) {
2028  $menuItem = $languageMenu->makeMenuItem()
2029  ->setTitle($selectorOptionLabel)
2030  ->setHref($href);
2031  if ($languageId === $currentLanguage) {
2032  $menuItem->setActive(true);
2033  }
2034  $languageMenu->addMenuItem($menuItem);
2035  }
2036  }
2037  $view->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
2038  }
2039  }
2040  }
2041  }
2042 
2046  protected function ‪localizationRedirect(ServerRequestInterface $request): ?ResponseInterface
2047  {
2048  $justLocalized = $request->getQueryParams()['justLocalized'] ?? null;
2049  if (empty($justLocalized)) {
2050  return null;
2051  }
2052 
2053  [$table, $origUid, $language] = explode(':', $justLocalized);
2054 
2055  if (‪$GLOBALS['TCA'][$table]
2056  && ‪$GLOBALS['TCA'][$table]['ctrl']['languageField']
2057  && ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField']
2058  ) {
2059  $parsedBody = $request->getParsedBody();
2060  $queryParams = $request->getQueryParams();
2061  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
2062  $queryBuilder->getRestrictions()
2063  ->removeAll()
2064  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2065  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪getBackendUser()->workspace));
2066  $localizedRecord = $queryBuilder->select('uid')
2067  ->from($table)
2068  ->where(
2069  $queryBuilder->expr()->eq(
2070  ‪$GLOBALS['TCA'][$table]['ctrl']['languageField'],
2071  $queryBuilder->createNamedParameter($language, ‪Connection::PARAM_INT)
2072  ),
2073  $queryBuilder->expr()->eq(
2074  ‪$GLOBALS['TCA'][$table]['ctrl']['transOrigPointerField'],
2075  $queryBuilder->createNamedParameter($origUid, ‪Connection::PARAM_INT)
2076  )
2077  )
2078  ->executeQuery()
2079  ->fetchAssociative();
2080  ‪$returnUrl = $parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? '';
2081  if (is_array($localizedRecord)) {
2082  // Create redirect response to self to edit just created record
2083  return new ‪RedirectResponse(
2084  (string)$this->uriBuilder->buildUriFromRoute(
2085  'record_edit',
2086  [
2087  'edit[' . $table . '][' . $localizedRecord['uid'] . ']' => 'edit',
2088  'returnUrl' => GeneralUtility::sanitizeLocalUrl(‪$returnUrl),
2089  ]
2090  ),
2091  303
2092  );
2093  }
2094  }
2095  return null;
2096  }
2097 
2106  protected function ‪getLanguages(int $id, string $table): array
2107  {
2108  // This usually happens when a non-pages record is added after another, so we are fetching the proper page ID
2109  if ($id < 0 && $table !== 'pages') {
2110  $pageId = $this->pageinfo['uid'] ?? null;
2111  if ($pageId !== null) {
2112  $pageId = (int)$pageId;
2113  } else {
2114  $fullRecord = BackendUtility::getRecord($table, abs($id));
2115  $pageId = (int)$fullRecord['pid'];
2116  }
2117  } else {
2118  if ($table === 'pages' && $id > 0) {
2119  $fullRecord = BackendUtility::getRecordWSOL('pages', $id);
2120  $id = (int)($fullRecord['t3ver_oid'] ?: $fullRecord['uid']);
2121  }
2122  $pageId = $id;
2123  }
2124  // Fetch the current translations of this page, to only show the ones where there is a page translation
2125  $allLanguages = array_filter(
2126  GeneralUtility::makeInstance(TranslationConfigurationProvider::class)->getSystemLanguages($pageId),
2127  static fn (array $language): bool => (int)$language['uid'] !== -1
2128  );
2129  if ($table !== 'pages' && $id > 0) {
2130  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
2131  $queryBuilder->getRestrictions()->removeAll()
2132  ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
2133  ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->‪getBackendUser()->workspace));
2134  $statement = $queryBuilder->select('uid', ‪$GLOBALS['TCA']['pages']['ctrl']['languageField'])
2135  ->from('pages')
2136  ->where(
2137  $queryBuilder->expr()->eq(
2138  ‪$GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
2139  $queryBuilder->createNamedParameter($pageId, ‪Connection::PARAM_INT)
2140  )
2141  )
2142  ->executeQuery();
2143  $availableLanguages = [];
2144  if ($allLanguages[0] ?? false) {
2145  $availableLanguages = [
2146  0 => $allLanguages[0],
2147  ];
2148  }
2149  while ($row = $statement->fetchAssociative()) {
2150  $languageId = (int)$row[‪$GLOBALS['TCA']['pages']['ctrl']['languageField']];
2151  if (isset($allLanguages[$languageId])) {
2152  $availableLanguages[$languageId] = $allLanguages[$languageId];
2153  }
2154  }
2155  return $availableLanguages;
2156  }
2157  return $allLanguages;
2158  }
2159 
2165  protected function ‪fixWSversioningInEditConf(?array $mapArray = null): void
2166  {
2167  if (!is_array($this->editconf)) {
2168  return;
2169  }
2170  foreach ($this->editconf as $table => $conf) {
2171  if (is_array($conf) && ‪$GLOBALS['TCA'][$table]) {
2172  // Traverse the keys/comments of each table (keys can be a comma list of uids)
2173  $newConf = [];
2174  foreach ($conf as $cKey => ‪$cmd) {
2175  if (‪$cmd === 'edit') {
2176  // Traverse the ids:
2177  $ids = GeneralUtility::trimExplode(',', (string)$cKey, true);
2178  foreach ($ids as $idKey => $theUid) {
2179  if (is_array($mapArray)) {
2180  if ($mapArray[$table][$theUid] ?? false) {
2181  $ids[$idKey] = $mapArray[$table][$theUid];
2182  }
2183  } else {
2184  // Default, look for versions in workspace for record:
2185  $calcPRec = $this->‪getRecordForEdit((string)$table, (int)$theUid);
2186  if (is_array($calcPRec)) {
2187  // Setting UID again if it had changed, eg. due to workspace versioning.
2188  $ids[$idKey] = $calcPRec['uid'];
2189  }
2190  }
2191  }
2192  // Add the possibly manipulated IDs to the new-build newConf array:
2193  $newConf[implode(',', $ids)] = ‪$cmd;
2194  } else {
2195  $newConf[$cKey] = ‪$cmd;
2196  }
2197  }
2198  // Store the new conf array:
2199  $this->editconf[$table] = $newConf;
2200  }
2201  }
2202  }
2203 
2211  protected function ‪getRecordForEdit(string $table, int $theUid): array|bool
2212  {
2213  $tableSupportsVersioning = BackendUtility::isTableWorkspaceEnabled($table);
2214  // Fetch requested record:
2215  $reqRecord = BackendUtility::getRecord($table, $theUid, 'uid,pid' . ($tableSupportsVersioning ? ',t3ver_oid' : ''));
2216  if (is_array($reqRecord)) {
2217  // If workspace is OFFLINE:
2218  if ($this->‪getBackendUser()->workspace !== 0) {
2219  // Check for versioning support of the table:
2220  if ($tableSupportsVersioning) {
2221  // If the record is already a version of "something" pass it by.
2222  if ($reqRecord['t3ver_oid'] > 0 || VersionState::tryFrom($reqRecord['t3ver_state'] ?? 0) === VersionState::NEW_PLACEHOLDER) {
2223  // (If it turns out not to be a version of the current workspace there will be trouble, but
2224  // that is handled inside DataHandler then and in the interface it would clearly be an error of
2225  // links if the user accesses such a scenario)
2226  return $reqRecord;
2227  }
2228  // The input record was online and an offline version must be found or made:
2229  // Look for version of this workspace:
2230  $versionRec = BackendUtility::getWorkspaceVersionOfRecord(
2231  $this->‪getBackendUser()->workspace,
2232  $table,
2233  $reqRecord['uid'],
2234  'uid,pid,t3ver_oid'
2235  );
2236  return is_array($versionRec) ? $versionRec : $reqRecord;
2237  }
2238  // This means that editing cannot occur on this record because it was not supporting versioning
2239  // which is required inside an offline workspace.
2240  return false;
2241  }
2242  // In ONLINE workspace, just return the originally requested record:
2243  return $reqRecord;
2244  }
2245  // Return FALSE because the table/uid was not found anyway.
2246  return false;
2247  }
2248 
2253  protected function ‪compileStoreData(ServerRequestInterface $request): void
2254  {
2255  $queryParams = $request->getQueryParams();
2256  $parsedBody = $request->getParsedBody();
2257 
2258  foreach (['edit', 'defVals', 'overrideVals' , 'columnsOnly' , 'noView'] as $key) {
2259  if (isset($this->R_URL_getvars[$key])) {
2260  $this->storeArray[$key] = $this->R_URL_getvars[$key];
2261  } else {
2262  $this->storeArray[$key] = $parsedBody[$key] ?? $queryParams[$key] ?? null;
2263  }
2264  }
2265 
2266  $this->storeUrl = ‪HttpUtility::buildQueryString($this->storeArray, '&');
2267  $this->storeUrlMd5 = md5($this->storeUrl);
2268  }
2269 
2273  protected function ‪getTsConfigOption(string $table, string $key): string
2274  {
2275  return trim((string)(
2276  $this->‪getBackendUser()->getTSConfig()['options.'][$key . '.'][$table]
2277  ?? $this->‪getBackendUser()->getTSConfig()['options.'][$key]
2278  ?? ''
2279  ));
2280  }
2281 
2294  protected function ‪closeDocument(int $mode, ServerRequestInterface $request): ?ResponseInterface
2295  {
2296  $setupArr = [];
2297  // If current document is found in docHandler,
2298  // then unset it, possibly unset it ALL and finally, write it to the session data
2299  if (isset($this->docHandler[$this->storeUrlMd5])) {
2300  // add the closing document to the recent documents
2301  $recentDocs = $this->‪getBackendUser()->getModuleData('opendocs::recent');
2302  if (!is_array($recentDocs)) {
2303  $recentDocs = [];
2304  }
2305  $closedDoc = $this->docHandler[‪$this->storeUrlMd5];
2306  $recentDocs = array_merge([$this->storeUrlMd5 => $closedDoc], $recentDocs);
2307  if (count($recentDocs) > 8) {
2308  $recentDocs = array_slice($recentDocs, 0, 8);
2309  }
2310  // remove it from the list of the open documents
2311  unset($this->docHandler[$this->storeUrlMd5]);
2312  if ($mode === self::DOCUMENT_CLOSE_MODE_CLEAR_ALL) {
2313  $recentDocs = array_merge($this->docHandler, $recentDocs);
2314  $this->docHandler = [];
2315  }
2316  $this->‪getBackendUser()->pushModuleData('opendocs::recent', $recentDocs);
2317  $this->‪getBackendUser()->pushModuleData('FormEngine', [$this->docHandler, $this->docDat[1]]);
2318  BackendUtility::setUpdateSignal('OpendocsController::updateNumber', count($this->docHandler));
2319  }
2320  if ($mode === self::DOCUMENT_CLOSE_MODE_NO_REDIRECT) {
2321  return null;
2322  }
2323  // If ->returnEditConf is set, then add the current content of editconf to the ->retUrl variable: used by
2324  // other scripts, like wizard_add, to know which records was created or so...
2325  if ($this->returnEditConf && $this->retUrl != (string)$this->uriBuilder->buildUriFromRoute('dummy')) {
2326  $this->retUrl .= '&returnEditConf=' . rawurlencode((string)json_encode($this->editconf));
2327  }
2328  // If mode is NOT set (means 0) OR set to 1, then make a header location redirect to $this->retUrl
2329  if ($mode === self::DOCUMENT_CLOSE_MODE_DEFAULT || $mode === self::DOCUMENT_CLOSE_MODE_REDIRECT) {
2330  return new RedirectResponse($this->retUrl, 303);
2331  }
2332  if ($this->retUrl === '') {
2333  return null;
2334  }
2335  ‪$retUrl = (string)$this->returnUrl;
2336  if (is_array($this->docHandler) && !empty($this->docHandler)) {
2337  if (!empty($setupArr[2])) {
2338  $sParts = parse_url($request->getAttribute('normalizedParams')->getRequestUri());
2339  ‪$retUrl = $sParts['path'] . '?' . $setupArr[2] . '&returnUrl=' . rawurlencode(‪$retUrl);
2340  }
2341  }
2342  return new RedirectResponse(‪$retUrl, 303);
2343  }
2344 
2348  protected function ‪getShortcutTitle(ServerRequestInterface $request): string
2349  {
2350  $queryParameters = $request->getQueryParams();
2351  $languageService = $this->‪getLanguageService();
2352  $defaultTitle = $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.edit');
2353 
2354  if (!is_array($queryParameters['edit'] ?? false)) {
2355  return $defaultTitle;
2356  }
2357 
2358  // @todo There may be a more efficient way in using FormEngine FormData.
2359  // @todo Therefore, the button initialization however has to take place at a later stage.
2360 
2361  $table = (string)key($queryParameters['edit']);
2362  $tableTitle = $languageService->sL(‪$GLOBALS['TCA'][$table]['ctrl']['title'] ?? '') ?: $table;
2363  ‪$identifier = (string)key($queryParameters['edit'][$table]);
2364  $action = (string)($queryParameters['edit'][$table][‪$identifier] ?? '');
2365 
2366  if ($action === 'new') {
2367  if ($table === 'pages') {
2368  return sprintf(
2369  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNewPage'),
2370  $tableTitle
2371  );
2372  }
2373 
2375  if (‪$identifier === 0) {
2376  return sprintf(
2377  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNewRecordRootLevel'),
2378  $tableTitle
2379  );
2380  }
2381 
2382  $pageRecord = null;
2383  if (‪$identifier < 0) {
2384  $parentRecord = BackendUtility::getRecord($table, abs(‪$identifier));
2385  if ($parentRecord['pid'] ?? false) {
2386  $pageRecord = BackendUtility::getRecord('pages', (int)($parentRecord['pid']), 'title');
2387  }
2388  } else {
2389  $pageRecord = BackendUtility::getRecord('pages', ‪$identifier, 'title');
2390  }
2391 
2392  if ($pageRecord !== null) {
2393  $pageTitle = BackendUtility::getRecordTitle('pages', $pageRecord);
2394  if ($pageTitle !== '') {
2395  return sprintf(
2396  $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNewRecord'),
2397  $tableTitle,
2398  $pageTitle
2399  );
2400  }
2401  }
2402 
2403  return $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.createNew') . ' ' . $tableTitle;
2404  }
2405 
2406  if ($action === 'edit') {
2407  if ($multiple = str_contains(‪$identifier, ',')) {
2408  // Multiple records are given, use the first one for further evaluation of e.g. the parent page
2409  $recordId = (int)(GeneralUtility::trimExplode(',', ‪$identifier, true)[0] ?? 0);
2410  } else {
2411  $recordId = (int)‪$identifier;
2412  }
2413  ‪$record = BackendUtility::getRecord($table, $recordId) ?? [];
2414  $recordTitle = BackendUtility::getRecordTitle($table, ‪$record);
2415  if ($table === 'pages') {
2416  return $multiple
2417  ? $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editMultiplePages')
2418  : sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editPage'), $tableTitle, $recordTitle);
2419  }
2420  if (!isset(‪$record['pid'])) {
2421  return $defaultTitle;
2422  }
2423  $pageId = (int)‪$record['pid'];
2424  if ($pageId === 0) {
2425  return $multiple
2426  ? sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editMultipleRecordsRootLevel'), $tableTitle)
2427  : sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editRecordRootLevel'), $tableTitle, $recordTitle);
2428  }
2429  $pageRow = BackendUtility::getRecord('pages', $pageId) ?? [];
2430  $pageTitle = BackendUtility::getRecordTitle('pages', $pageRow);
2431  if ($multiple) {
2432  return sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editMultipleRecords'), $tableTitle, $pageTitle);
2433  }
2434  if ($recordTitle !== '') {
2435  return sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editRecord'), $tableTitle, $recordTitle, $pageTitle);
2436  }
2437  return sprintf($languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.editRecordNoTitle'), $tableTitle, $pageTitle);
2438  }
2439 
2440  return $defaultTitle;
2441  }
2442 
2447  protected function ‪isSingleRecordView(): bool
2448  {
2449  return count($this->elementsData) === 1;
2450  }
2451 
2452  protected function ‪getBackendUser(): BackendUserAuthentication
2453  {
2454  return ‪$GLOBALS['BE_USER'];
2455  }
2456 
2457  protected function ‪getLanguageService(): LanguageService
2458  {
2459  return ‪$GLOBALS['LANG'];
2460  }
2461 }
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:100
‪TYPO3\CMS\Backend\Template\Components\AbstractControl\setClasses
‪$this setClasses($classes)
Definition: AbstractControl.php:79
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$columnsOnly
‪string null $columnsOnly
Definition: EditDocumentController.php:98
‪TYPO3\CMS\Core\Resource\Exception\InsufficientUserPermissionsException
Definition: InsufficientUserPermissionsException.php:24
‪TYPO3\CMS\Backend\Controller\EditDocumentController\isInconsistentLanguageHandlingAllowed
‪isInconsistentLanguageHandlingAllowed()
Definition: EditDocumentController.php:1273
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$storeUrlMd5
‪string $storeUrlMd5
Definition: EditDocumentController.php:244
‪TYPO3\CMS\Backend\Form\FormResultCompiler
Definition: FormResultCompiler.php:34
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:46
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\BUTTON_POSITION_LEFT
‪const BUTTON_POSITION_LEFT
Definition: ButtonBar.php:37
‪TYPO3\CMS\Backend\Template\Components\ButtonBar
Definition: ButtonBar.php:33
‪TYPO3\CMS\Core\Utility\GeneralUtility\fixed_lgd_cs
‪static string fixed_lgd_cs(string $string, int $chars, string $appendString='...')
Definition: GeneralUtility.php:93
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$perms_clause
‪string $perms_clause
Definition: EditDocumentController.php:189
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$returnUrl
‪string null $returnUrl
Definition: EditDocumentController.php:117
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getPreviewUrlAnchorSection
‪getPreviewUrlAnchorSection()
Definition: EditDocumentController.php:865
‪TYPO3\CMS\Backend\Template\Components\Buttons\AbstractButton\setIcon
‪$this setIcon(Icon $icon)
Definition: AbstractButton.php:94
‪TYPO3\CMS\Core\Resource\FileInterface
Definition: FileInterface.php:24
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$mirror
‪array $mirror
Definition: EditDocumentController.php:156
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getRecordForEdit
‪array false getRecordForEdit(string $table, int $theUid)
Definition: EditDocumentController.php:2177
‪TYPO3\CMS\Backend\Template\ModuleTemplateFactory
Definition: ModuleTemplateFactory.php:33
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$recTitle
‪string $recTitle
Definition: EditDocumentController.php:179
‪TYPO3\CMS\Core\Versioning\VersionState
‪VersionState
Definition: VersionState.php:22
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$previewCode
‪string null $previewCode
Definition: EditDocumentController.php:173
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getCloseUrl
‪getCloseUrl()
Definition: EditDocumentController.php:1824
‪TYPO3\CMS\Core\Database\ReferenceIndex
Definition: ReferenceIndex.php:45
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$firstEl
‪array $firstEl
Definition: EditDocumentController.php:271
‪TYPO3\CMS\Backend\Controller\EditDocumentController\init
‪init(ServerRequestInterface $request)
Definition: EditDocumentController.php:742
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$R_URL_parts
‪array $R_URL_parts
Definition: EditDocumentController.php:201
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerShortcutButtonToButtonBar
‪registerShortcutButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, ServerRequestInterface $request)
Definition: EditDocumentController.php:1626
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$isPageInFreeTranslationMode
‪bool $isPageInFreeTranslationMode
Definition: EditDocumentController.php:307
‪TYPO3\CMS\Backend\Controller\EditDocumentController\main
‪main(ModuleTemplate $view, ServerRequestInterface $request)
Definition: EditDocumentController.php:940
‪TYPO3\CMS\Backend\Controller\EditDocumentController\addSlugFieldsToColumnsOnly
‪addSlugFieldsToColumnsOnly(array $queryParams)
Definition: EditDocumentController.php:430
‪TYPO3\CMS\Backend\Template\ModuleTemplate\addFlashMessage
‪addFlashMessage(string $messageBody, string $messageTitle='', ContextualFeedbackSeverity $severity=ContextualFeedbackSeverity::OK, bool $storeInSession=true)
Definition: ModuleTemplate.php:229
‪TYPO3\CMS\Backend\Attribute\Controller
Definition: Controller.php:25
‪TYPO3\CMS\Backend\Controller\EditDocumentController\DOCUMENT_CLOSE_MODE_REDIRECT
‪const DOCUMENT_CLOSE_MODE_REDIRECT
Definition: EditDocumentController.php:83
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerOpenInNewWindowButtonToButtonBar
‪registerOpenInNewWindowButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, ServerRequestInterface $request)
Definition: EditDocumentController.php:1597
‪TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
Definition: FileDoesNotExistException.php:22
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getFreeTranslationMode
‪getFreeTranslationMode(int $page, int $column, int $language)
Definition: EditDocumentController.php:1307
‪TYPO3\CMS\Backend\Controller\EditDocumentController\DOCUMENT_CLOSE_MODE_DEFAULT
‪const DOCUMENT_CLOSE_MODE_DEFAULT
Definition: EditDocumentController.php:81
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$isSavedRecord
‪bool $isSavedRecord
Definition: EditDocumentController.php:305
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$previewData
‪array $previewData
Definition: EditDocumentController.php:300
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$docHandler
‪array $docHandler
Definition: EditDocumentController.php:259
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$dontStoreDocumentRef
‪int $dontStoreDocumentRef
Definition: EditDocumentController.php:294
‪TYPO3\CMS\Backend\Controller\EditDocumentController\DOCUMENT_CLOSE_MODE_CLEAR_ALL
‪const DOCUMENT_CLOSE_MODE_CLEAR_ALL
Definition: EditDocumentController.php:84
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getLanguages
‪array getLanguages(int $id, string $table)
Definition: EditDocumentController.php:2072
‪$fields
‪$fields
Definition: pages.php:5
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerSaveButtonToButtonBar
‪registerSaveButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
Definition: EditDocumentController.php:1335
‪TYPO3\CMS\Backend\Controller\EditDocumentController\compileForm
‪string compileForm(string $editForm)
Definition: EditDocumentController.php:1728
‪TYPO3\CMS\Backend\Template\ModuleTemplate
Definition: ModuleTemplate.php:46
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\makeInputButton
‪InputButton makeInputButton()
Definition: ButtonBar.php:102
‪TYPO3\CMS\Backend\Form\Exception\DatabaseRecordWorkspaceDeletePlaceholderException
Definition: DatabaseRecordWorkspaceDeletePlaceholderException.php:26
‪TYPO3\CMS\Backend\Controller\EditDocumentController\__construct
‪__construct(protected readonly EventDispatcherInterface $eventDispatcher, protected readonly IconFactory $iconFactory, protected readonly PageRenderer $pageRenderer, protected readonly UriBuilder $uriBuilder, protected readonly ModuleTemplateFactory $moduleTemplateFactory, protected readonly BackendEntryPointResolver $backendEntryPointResolver, private readonly FormDataCompiler $formDataCompiler,)
Definition: EditDocumentController.php:309
‪TYPO3\CMS\Core\Type\Bitmask\Permission
Definition: Permission.php:26
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\addButton
‪$this addButton(ButtonInterface $button, $buttonPosition=self::BUTTON_POSITION_LEFT, $buttonGroup=1)
Definition: ButtonBar.php:61
‪TYPO3\CMS\Backend\Controller\EditDocumentController\fixWSversioningInEditConf
‪fixWSversioningInEditConf(?array $mapArray=null)
Definition: EditDocumentController.php:2131
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerHistoryButtonToButtonBar
‪registerHistoryButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
Definition: EditDocumentController.php:1558
‪TYPO3\CMS\Backend\Controller\EditDocumentController\compileStoreData
‪compileStoreData(ServerRequestInterface $request)
Definition: EditDocumentController.php:2219
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getTsConfigOption
‪getTsConfigOption(string $table, string $key)
Definition: EditDocumentController.php:2239
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$returnNewPageId
‪bool $returnNewPageId
Definition: EditDocumentController.php:163
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getLanguageService
‪getLanguageService()
Definition: EditDocumentController.php:2423
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerViewButtonToButtonBar
‪registerViewButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
Definition: EditDocumentController.php:1350
‪TYPO3\CMS\Backend\Controller\Event\AfterFormEnginePageInitializedEvent
Definition: AfterFormEnginePageInitializedEvent.php:27
‪TYPO3\CMS\Core\Utility\MathUtility\convertToPositiveInteger
‪static convertToPositiveInteger(mixed $theInt)
Definition: MathUtility.php:55
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getShortcutTitle
‪getShortcutTitle(ServerRequestInterface $request)
Definition: EditDocumentController.php:2314
‪TYPO3\CMS\Core\Page\PageRenderer
Definition: PageRenderer.php:46
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$pageinfo
‪array $pageinfo
Definition: EditDocumentController.php:218
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerCloseButtonToButtonBar
‪registerCloseButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
Definition: EditDocumentController.php:1321
‪TYPO3\CMS\Backend\Controller\EditDocumentController\setIsPageInFreeTranslationMode
‪setIsPageInFreeTranslationMode(?array $record, int $sysLanguageUid)
Definition: EditDocumentController.php:1285
‪TYPO3\CMS\Backend\Controller\EditDocumentController
Definition: EditDocumentController.php:80
‪TYPO3\CMS\Core\Messaging\FlashMessageQueue\NOTIFICATION_QUEUE
‪const NOTIFICATION_QUEUE
Definition: FlashMessageQueue.php:32
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerDeleteButtonToButtonBar
‪registerDeleteButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, ServerRequestInterface $request)
Definition: EditDocumentController.php:1472
‪TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton\setRouteIdentifier
‪setRouteIdentifier(string $routeIdentifier)
Definition: ShortcutButton.php:81
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$noView
‪bool $noView
Definition: EditDocumentController.php:185
‪TYPO3\CMS\Core\Utility\PathUtility\getPublicResourceWebPath
‪static getPublicResourceWebPath(string $resourcePath, bool $prefixWithSitePath=true)
Definition: PathUtility.php:97
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$R_URL_getvars
‪array $R_URL_getvars
Definition: EditDocumentController.php:208
‪TYPO3\CMS\Backend\Controller\EditDocumentController\canViewDoktype
‪canViewDoktype(array $currentPage)
Definition: EditDocumentController.php:924
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getUrlQueryParamsForCurrentRequest
‪getUrlQueryParamsForCurrentRequest(ServerRequestInterface $request)
Definition: EditDocumentController.php:1638
‪TYPO3\CMS\Backend\Controller\EditDocumentController\setIsSavedRecord
‪setIsSavedRecord()
Definition: EditDocumentController.php:1262
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:44
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:41
‪TYPO3\CMS\Core\Utility\HttpUtility\buildQueryString
‪static string buildQueryString(array $parameters, string $prependCharacter='', bool $skipEmptyParameters=false)
Definition: HttpUtility.php:124
‪TYPO3\CMS\Backend\Routing\PreviewUriBuilder\create
‪static static create(int $pageId)
Definition: PreviewUriBuilder.php:65
‪TYPO3\CMS\Webhooks\Message\$record
‪identifier readonly int readonly array $record
Definition: PageModificationMessage.php:36
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SPACER
‪const DOKTYPE_SPACER
Definition: PageRepository.php:113
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$editconf
‪array< string, array > $editconf
Definition: EditDocumentController.php:91
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$closeDoc
‪int $closeDoc
Definition: EditDocumentController.php:130
‪TYPO3\CMS\Backend\Controller\Event\BeforeFormEnginePageInitializedEvent
Definition: BeforeFormEnginePageInitializedEvent.php:27
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:64
‪TYPO3\CMS\Backend\Template\ModuleTemplate\setUiBlock
‪setUiBlock(bool $uiBlock)
Definition: ModuleTemplate.php:253
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getBackendUser
‪getBackendUser()
Definition: EditDocumentController.php:2418
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_SHOW
‪const PAGE_SHOW
Definition: Permission.php:35
‪TYPO3\CMS\Backend\Controller\EditDocumentController\mainAction
‪mainAction(ServerRequestInterface $request)
Definition: EditDocumentController.php:323
‪TYPO3\CMS\Core\Domain\Repository\PageRepository\DOKTYPE_SYSFOLDER
‪const DOKTYPE_SYSFOLDER
Definition: PageRepository.php:114
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getButtons
‪getButtons(ModuleTemplate $view, ServerRequestInterface $request)
Definition: EditDocumentController.php:1199
‪TYPO3\CMS\Backend\Controller\EditDocumentController\isRecordCurrentBackendUser
‪isRecordCurrentBackendUser()
Definition: EditDocumentController.php:1813
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\makeLinkButton
‪LinkButton makeLinkButton()
Definition: ButtonBar.php:132
‪TYPO3\CMS\Backend\Template\Components\Buttons\InputButton\setForm
‪InputButton setForm($form)
Definition: InputButton.php:118
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getStandAloneContentElementTranslationsCount
‪getStandAloneContentElementTranslationsCount(int $page, int $column, int $language)
Definition: EditDocumentController.php:1678
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$cmd
‪array $cmd
Definition: EditDocumentController.php:150
‪TYPO3\CMS\Backend\Controller\EditDocumentController\closeDocument
‪ResponseInterface null closeDocument(int $mode, ServerRequestInterface $request)
Definition: EditDocumentController.php:2260
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$storeUrl
‪string $storeUrl
Definition: EditDocumentController.php:238
‪TYPO3\CMS\Core\Http\RedirectResponse
Definition: RedirectResponse.php:30
‪TYPO3\CMS\Backend\Form\NodeFactory
Definition: NodeFactory.php:40
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$defVals
‪array null $defVals
Definition: EditDocumentController.php:104
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getConnectedContentElementTranslationsCount
‪getConnectedContentElementTranslationsCount(int $page, int $column, int $language)
Definition: EditDocumentController.php:1661
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getPreviewPageId
‪getPreviewPageId()
Definition: EditDocumentController.php:876
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:35
‪TYPO3\CMS\Backend\Form\Exception\DatabaseRecordException
Definition: DatabaseRecordException.php:24
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$formResultCompiler
‪FormResultCompiler $formResultCompiler
Definition: EditDocumentController.php:288
‪TYPO3\CMS\Backend\Exception\AccessDeniedException
Definition: AccessDeniedException.php:26
‪TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider
Definition: TranslationConfigurationProvider.php:39
‪TYPO3\CMS\Backend\Template\ModuleTemplate\getDocHeaderComponent
‪getDocHeaderComponent()
Definition: ModuleTemplate.php:181
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getInfobox
‪getInfobox(string $message, ?string $title=null)
Definition: EditDocumentController.php:1179
‪TYPO3\CMS\Core\Messaging\FlashMessage
Definition: FlashMessage.php:27
‪TYPO3\CMS\Backend\Controller\EditDocumentController\resolvePreviewRecordId
‪resolvePreviewRecordId(string $table, array $recordArray, array $previewConfiguration)
Definition: EditDocumentController.php:845
‪TYPO3\CMS\Core\Type\Bitmask\Permission\CONTENT_EDIT
‪const CONTENT_EDIT
Definition: Permission.php:55
‪TYPO3\CMS\Webhooks\Message\$uid
‪identifier readonly int $uid
Definition: PageModificationMessage.php:35
‪TYPO3\CMS\Backend\Controller\EditDocumentController\languageSwitch
‪languageSwitch(ModuleTemplate $view, string $table, int $uid, ?int $pid=null)
Definition: EditDocumentController.php:1837
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$errorC
‪int $errorC
Definition: EditDocumentController.php:277
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerNewButtonToButtonBar
‪registerNewButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, int $sysLanguageUid, int $l18nParent)
Definition: EditDocumentController.php:1396
‪TYPO3\CMS\Backend\Controller\EditDocumentController\updateInlineView
‪updateInlineView(?array $uc, DataHandler $dataHandler)
Definition: EditDocumentController.php:1755
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getDisableDelete
‪getDisableDelete()
Definition: EditDocumentController.php:1795
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$storeTitle
‪string $storeTitle
Definition: EditDocumentController.php:225
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Backend\Form\Exception\AccessDeniedException
Definition: AccessDeniedException.php:26
‪TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction
Definition: DeletedRestriction.php:28
‪TYPO3\CMS\Backend\Routing\PreviewUriBuilder
Definition: PreviewUriBuilder.php:44
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getQueryBuilderForTranslationMode
‪getQueryBuilderForTranslationMode(int $page, int $column, int $language)
Definition: EditDocumentController.php:1695
‪TYPO3\CMS\Core\Type\Bitmask\Permission\PAGE_DELETE
‪const PAGE_DELETE
Definition: Permission.php:45
‪TYPO3\CMS\Backend\Controller\EditDocumentController\DOCUMENT_CLOSE_MODE_NO_REDIRECT
‪const DOCUMENT_CLOSE_MODE_NO_REDIRECT
Definition: EditDocumentController.php:85
‪TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException
Definition: ResourceNotFoundException.php:24
‪TYPO3\CMS\Backend\Controller\EditDocumentController\makeEditForm
‪string makeEditForm(ServerRequestInterface $request, ModuleTemplate $view)
Definition: EditDocumentController.php:1025
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$data
‪array $data
Definition: EditDocumentController.php:144
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$R_URI
‪string $R_URI
Definition: EditDocumentController.php:214
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:24
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Backend\Controller\EditDocumentController\generatePreviewCode
‪generatePreviewCode()
Definition: EditDocumentController.php:778
‪TYPO3\CMS\Backend\Controller\EditDocumentController\localizationRedirect
‪localizationRedirect(ServerRequestInterface $request)
Definition: EditDocumentController.php:2012
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$doSave
‪bool $doSave
Definition: EditDocumentController.php:138
‪TYPO3\CMS\Core\Domain\Repository\PageRepository
Definition: PageRepository.php:61
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$storeArray
‪array $storeArray
Definition: EditDocumentController.php:232
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:51
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerDuplicationButtonToButtonBar
‪registerDuplicationButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group, int $sysLanguageUid, int $l18nParent)
Definition: EditDocumentController.php:1434
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$overrideVals
‪array null $overrideVals
Definition: EditDocumentController.php:110
‪TYPO3\CMS\Backend\Controller\EditDocumentController\isSingleRecordView
‪isSingleRecordView()
Definition: EditDocumentController.php:2413
‪TYPO3\CMS\Backend\Routing\PreviewUriBuilder\OPTION_SWITCH_FOCUS
‪const OPTION_SWITCH_FOCUS
Definition: PreviewUriBuilder.php:45
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Backend\Template\Components\AbstractControl\setTitle
‪$this setTitle($title)
Definition: AbstractControl.php:92
‪TYPO3\CMS\Backend\Form\FormDataCompiler
Definition: FormDataCompiler.php:26
‪TYPO3\CMS\Backend\Controller\EditDocumentController\registerColumnsOnlyButtonToButtonBar
‪registerColumnsOnlyButtonToButtonBar(ButtonBar $buttonBar, string $position, int $group)
Definition: EditDocumentController.php:1579
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$elementsData
‪array $elementsData
Definition: EditDocumentController.php:265
‪TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord
Definition: TcaDatabaseRecord.php:25
‪TYPO3\CMS\Backend\Controller\EditDocumentController\resolveMetaInformation
‪resolveMetaInformation(ModuleTemplate $view)
Definition: EditDocumentController.php:1002
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\BUTTON_POSITION_RIGHT
‪const BUTTON_POSITION_RIGHT
Definition: ButtonBar.php:42
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$viewId
‪int $viewId
Definition: EditDocumentController.php:284
‪TYPO3\CMS\Core\Messaging\FlashMessageQueue
Definition: FlashMessageQueue.php:30
‪TYPO3\CMS\Backend\Controller
Definition: AboutController.php:18
‪TYPO3\CMS\Core\Routing\BackendEntryPointResolver
Definition: BackendEntryPointResolver.php:29
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$returnEditConf
‪bool $returnEditConf
Definition: EditDocumentController.php:195
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$docDat
‪array $docDat
Definition: EditDocumentController.php:250
‪TYPO3\CMS\Core\Messaging\FlashMessageService
Definition: FlashMessageService.php:27
‪TYPO3\CMS\Backend\Template\Components\ButtonBar\makeShortcutButton
‪ShortcutButton makeShortcutButton()
Definition: ButtonBar.php:152
‪TYPO3\CMS\Backend\Controller\EditDocumentController\preInit
‪preInit(ServerRequestInterface $request)
Definition: EditDocumentController.php:373
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Backend\Controller\EditDocumentController\getPreviewUrlParameters
‪getPreviewUrlParameters(int $previewPageId)
Definition: EditDocumentController.php:800
‪TYPO3\CMS\Backend\Controller\EditDocumentController\processData
‪processData(ModuleTemplate $view, ServerRequestInterface $request)
Definition: EditDocumentController.php:454
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$popViewId
‪int $popViewId
Definition: EditDocumentController.php:169
‪TYPO3\CMS\Backend\Template\Components\Buttons\AbstractButton\setShowLabelText
‪$this setShowLabelText($showLabelText)
Definition: AbstractButton.php:61
‪TYPO3\CMS\Backend\Controller\EditDocumentController\$retUrl
‪string $retUrl
Definition: EditDocumentController.php:125
‪TYPO3\CMS\Core\Database\Query\Restriction\WorkspaceRestriction
Definition: WorkspaceRestriction.php:39