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