├── Classes
├── Backend
│ ├── ContextMenu
│ │ └── RecordContextMenuItemProvider.php
│ ├── Grid
│ │ ├── ContainerGridColumn.php
│ │ └── ContainerGridColumnItem.php
│ ├── Preview
│ │ ├── ContainerPreviewRenderer.php
│ │ └── GridRenderer.php
│ └── Service
│ │ └── NewContentUrlBuilder.php
├── Command
│ ├── DeleteChildrenWithNonExistingParentCommand.php
│ ├── DeleteChildrenWithWrongPidCommand.php
│ ├── FixContainerParentForConnectedModeCommand.php
│ ├── FixLanguageModeCommand.php
│ ├── IntegrityCommand.php
│ ├── SortingCommand.php
│ └── SortingInPageCommand.php
├── ContentDefender
│ ├── ContainerColumnConfigurationService.php
│ ├── Hooks
│ │ └── ColumnConfigurationManipulationHook.php
│ └── Xclasses
│ │ ├── CommandMapHook.php
│ │ └── DatamapHook.php
├── DataProcessing
│ ├── ContainerDataProcessingFailedException.php
│ └── ContainerProcessor.php
├── Domain
│ ├── Factory
│ │ ├── ContainerFactory.php
│ │ ├── Database.php
│ │ ├── Exception.php
│ │ ├── FrontendContainerFactory.php
│ │ └── PageView
│ │ │ └── Backend
│ │ │ ├── ContainerFactory.php
│ │ │ └── ContentStorage.php
│ ├── Model
│ │ └── Container.php
│ └── Service
│ │ └── ContainerService.php
├── Events
│ ├── BeforeContainerConfigurationIsAppliedEvent.php
│ └── BeforeContainerPreviewIsRenderedEvent.php
├── Exception.php
├── Hooks
│ ├── Datahandler
│ │ ├── CommandMapAfterFinishHook.php
│ │ ├── CommandMapBeforeStartHook.php
│ │ ├── CommandMapPostProcessingHook.php
│ │ ├── Database.php
│ │ ├── DatahandlerProcess.php
│ │ ├── DatamapBeforeStartHook.php
│ │ ├── DatamapPreProcessFieldArrayHook.php
│ │ └── DeleteHook.php
│ ├── UsedRecords.php
│ └── WizardItems.php
├── Integrity
│ ├── Database.php
│ ├── Error
│ │ ├── ChildInTranslatedContainerError.php
│ │ ├── ErrorInterface.php
│ │ ├── NonExistingParentWarning.php
│ │ ├── UnusedColPosWarning.php
│ │ ├── WrongL18nParentError.php
│ │ ├── WrongLanguageWarning.php
│ │ ├── WrongParentError.php
│ │ └── WrongPidError.php
│ ├── Integrity.php
│ ├── IntegrityFix.php
│ ├── Sorting.php
│ └── SortingInPage.php
├── Listener
│ ├── BootCompleted.php
│ ├── ContentUsedOnPage.php
│ ├── LegacyPageTsConfig.php
│ ├── ModifyNewContentElementWizardItems.php
│ ├── PageContentPreviewRendering.php
│ ├── PageTsConfig.php
│ └── RecordSummaryForLocalization.php
├── Service
│ └── RecordLocalizeSummaryModifier.php
├── Tca
│ ├── ContainerConfiguration.php
│ ├── ItemProcFunc.php
│ └── Registry.php
├── Updates
│ ├── ContainerDeleteChildrenWithWrongPid.php
│ └── ContainerMigrateSorting.php
└── Xclasses
│ └── LocalizationController.php
├── Configuration
├── Icons.php
├── JavaScriptModules.php
├── Services.yaml
└── TCA
│ └── Overrides
│ └── tt_content.php
├── LICENSE
├── README.md
├── Resources
├── Private
│ ├── Language
│ │ └── locallang.xlf
│ ├── Partials
│ │ └── PageLayout
│ │ │ ├── Grid
│ │ │ ├── Column.html
│ │ │ └── ColumnHeader.html
│ │ │ └── Record.html
│ ├── Partials11
│ │ └── PageLayout
│ │ │ ├── Grid
│ │ │ └── Column.html
│ │ │ └── Record.html
│ ├── Partials12
│ │ └── PageLayout
│ │ │ ├── Grid
│ │ │ ├── Column.html
│ │ │ └── ColumnHeader.html
│ │ │ └── Record.html
│ └── Templates
│ │ ├── Container.html
│ │ └── Grid.html
└── Public
│ ├── Icons
│ ├── Extension.svg
│ ├── container-1col.svg
│ ├── container-2col-left.svg
│ ├── container-2col-right.svg
│ ├── container-2col.svg
│ ├── container-3col.svg
│ └── container-4col.svg
│ └── JavaScript
│ ├── Overrides
│ ├── drag-drop.js
│ └── paste.js
│ └── Overrides12
│ ├── drag-drop.js
│ └── paste.js
├── composer.json
├── ext_emconf.php
├── ext_localconf.php
└── ext_tables.sql
/Classes/Backend/ContextMenu/RecordContextMenuItemProvider.php:
--------------------------------------------------------------------------------
1 | table === 'tt_content'
24 | && isset($this->record['tx_container_parent']) && $this->record['tx_container_parent'] > 0) {
25 | $languageField = method_exists($this, 'getLanguageField') ? $this->getLanguageField() : $GLOBALS['TCA']['tt_content']['ctrl']['languageField'];
26 | $urlParameters = [
27 | 'id' => $this->record['pid'],
28 | 'sys_language_uid' => $this->record[$languageField] ?? null,
29 | 'colPos' => $this->record['colPos'],
30 | 'uid_pid' => -$this->record['uid'],
31 | 'tx_container_parent' => $this->record['tx_container_parent'],
32 | ];
33 | $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
34 | $url = (string)$uriBuilder->buildUriFromRoute('new_content_element_wizard', $urlParameters);
35 | if (isset($attributes['data-new-wizard-url'])) {
36 | $attributes['data-new-wizard-url'] = $url;
37 | }
38 | }
39 |
40 | return $attributes;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Classes/Backend/Grid/ContainerGridColumn.php:
--------------------------------------------------------------------------------
1 | container = $container;
37 | $this->newContentUrl = $newContentUrl;
38 | $this->skipNewContentElementWizard = $skipNewContentElementWizard;
39 | }
40 |
41 | public function getContainerUid(): int
42 | {
43 | return $this->container->getUidOfLiveWorkspace();
44 | }
45 |
46 | public function getNewContentElementWizardShouldBeSkipped(): bool
47 | {
48 | return $this->skipNewContentElementWizard;
49 | }
50 |
51 | public function getTitle(): string
52 | {
53 | return (string)$this->getLanguageService()->sL($this->getColumnName());
54 | }
55 |
56 | public function getAllowNewContent(): bool
57 | {
58 | if ($this->container->getLanguage() > 0 && $this->container->isConnectedMode()) {
59 | return false;
60 | }
61 | return $this->newContentUrl !== null;
62 | }
63 |
64 | public function isActive(): bool
65 | {
66 | // yes we are active
67 | return true;
68 | }
69 |
70 | public function getNewContentUrl(): string
71 | {
72 | if ($this->newContentUrl === null) {
73 | return '';
74 | }
75 | return $this->newContentUrl;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Classes/Backend/Grid/ContainerGridColumnItem.php:
--------------------------------------------------------------------------------
1 | container = $container;
28 | $this->newContentUrl = $newContentUrl;
29 | }
30 |
31 | public function getAllowNewContent(): bool
32 | {
33 | if ($this->container->getLanguage() > 0 && $this->container->isConnectedMode()) {
34 | return false;
35 | }
36 | return true;
37 | }
38 |
39 | public function getWrapperClassName(): string
40 | {
41 | $wrapperClassNames = [];
42 | if ($this->isDisabled()) {
43 | $wrapperClassNames[] = 't3-page-ce-hidden t3js-hidden-record';
44 | }
45 | // we do not need a "t3-page-ce-warning" class because we are build from Container
46 | return implode(' ', $wrapperClassNames);
47 | }
48 |
49 | public function getNewContentAfterUrl(): string
50 | {
51 | if ($this->newContentUrl === null) {
52 | return '';
53 | }
54 | return $this->newContentUrl;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Classes/Backend/Preview/ContainerPreviewRenderer.php:
--------------------------------------------------------------------------------
1 | gridRenderer = $gridRenderer;
29 | $this->runtimeCache = $runtimeCache;
30 | }
31 |
32 | public function renderPageModulePreviewHeader(GridColumnItem $item): string
33 | {
34 | $this->runtimeCache->set('tx_container_current_gridColumItem', $item);
35 | return parent::renderPageModulePreviewHeader($item);
36 | }
37 |
38 | public function renderPageModulePreviewContent(GridColumnItem $item): string
39 | {
40 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() > 11) {
41 | return parent::renderPageModulePreviewContent($item);
42 | }
43 | $record = $item->getRecord();
44 | $record['tx_container_grid'] = $this->gridRenderer->renderGrid($record, $item->getContext());
45 | $item->setRecord($record);
46 | return parent::renderPageModulePreviewContent($item);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Classes/Backend/Service/NewContentUrlBuilder.php:
--------------------------------------------------------------------------------
1 | tcaRegistry = $tcaRegistry;
37 | $this->containerColumnConfigurationService = $containerColumnConfigurationService;
38 | $this->containerService = $containerService;
39 | $this->uriBuilder = $uriBuilder;
40 | }
41 |
42 | public function getNewContentUrlAfterChild(PageLayoutContext $context, Container $container, int $columnNumber, int $recordUid, ?array $defVals): string
43 | {
44 | if ($defVals !== null) {
45 | return $this->getNewContentEditUrl($container, $columnNumber, -$recordUid, $defVals);
46 | }
47 | return $this->getNewContentWizardUrl($context, $container, $columnNumber, -$recordUid);
48 | }
49 |
50 | public function getNewContentUrlAtTopOfColumn(PageLayoutContext $context, Container $container, int $columnNumber, ?array $defVals): ?string
51 | {
52 | if ($this->containerColumnConfigurationService->isMaxitemsReached($container, $columnNumber)) {
53 | return null;
54 | }
55 | $newContentElementAtTopTarget = $this->containerService->getNewContentElementAtTopTargetInColumn($container, $columnNumber);
56 | if ($defVals !== null) {
57 | return $this->getNewContentEditUrl($container, $columnNumber, $newContentElementAtTopTarget, $defVals);
58 | }
59 | return $this->getNewContentWizardUrl($context, $container, $columnNumber, $newContentElementAtTopTarget);
60 | }
61 |
62 | protected function getNewContentEditUrl(Container $container, int $columnNumber, int $target, array $defVals): string
63 | {
64 | $ttContentDefVals = array_merge($defVals, [
65 | 'colPos' => $columnNumber,
66 | 'sys_language_uid' => $container->getLanguage(),
67 | 'tx_container_parent' => $container->getUidOfLiveWorkspace(),
68 | ]);
69 | $urlParameters = [
70 | 'edit' => [
71 | 'tt_content' => [
72 | $target => 'new',
73 | ],
74 | ],
75 | 'defVals' => [
76 | 'tt_content' => $ttContentDefVals,
77 | ],
78 | 'returnUrl' => $this->getReturnUrl(),
79 | ];
80 | return (string)$this->uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
81 | }
82 |
83 | protected function getNewContentWizardUrl(PageLayoutContext $context, Container $container, int $columnNumber, int $uidPid): string
84 | {
85 | $pageId = $context->getPageId();
86 | $urlParameters = [
87 | 'id' => $pageId,
88 | 'sys_language_uid' => $container->getLanguage(),
89 | 'colPos' => $columnNumber,
90 | 'tx_container_parent' => $container->getUidOfLiveWorkspace(),
91 | 'uid_pid' => $uidPid,
92 | 'returnUrl' => $this->getReturnUrl(),
93 | ];
94 | return (string)$this->uriBuilder->buildUriFromRoute('new_content_element_wizard', $urlParameters);
95 | }
96 |
97 | protected function getServerRequest(): ?ServerRequestInterface
98 | {
99 | return $GLOBALS['TYPO3_REQUEST'] ?? null;
100 | }
101 |
102 | protected function getReturnUrl(): string
103 | {
104 | $request = $this->getServerRequest();
105 | if ($request === null) {
106 | return '';
107 | }
108 | return (string)$request->getAttribute('normalizedParams')->getRequestUri();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/Classes/Command/DeleteChildrenWithNonExistingParentCommand.php:
--------------------------------------------------------------------------------
1 | integrity = $integrity;
40 | $this->integrityFix = $integrityFix;
41 | parent::__construct($name);
42 | }
43 |
44 | public function execute(InputInterface $input, OutputInterface $output): int
45 | {
46 | Bootstrap::initializeBackendAuthentication();
47 | $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences($GLOBALS['BE_USER']);
48 | $res = $this->integrity->run();
49 | foreach ($res['warnings'] as $warning) {
50 | if ($warning instanceof NonExistingParentWarning) {
51 | $this->integrityFix->deleteChildrenWithNonExistingParent($warning);
52 | }
53 | }
54 | return 0;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Classes/Command/DeleteChildrenWithWrongPidCommand.php:
--------------------------------------------------------------------------------
1 | integrity = $integrity;
40 | $this->integrityFix = $integrityFix;
41 | parent::__construct($name);
42 | }
43 |
44 | public function execute(InputInterface $input, OutputInterface $output): int
45 | {
46 | Bootstrap::initializeBackendAuthentication();
47 | $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences($GLOBALS['BE_USER']);
48 | $res = $this->integrity->run();
49 | foreach ($res['errors'] as $error) {
50 | if ($error instanceof WrongPidError) {
51 | $this->integrityFix->deleteChildrenWithWrongPid($error);
52 | }
53 | }
54 | return 0;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Classes/Command/FixContainerParentForConnectedModeCommand.php:
--------------------------------------------------------------------------------
1 | integrity = $integrity;
37 | $this->integrityFix = $integrityFix;
38 | parent::__construct($name);
39 | }
40 |
41 | public function execute(InputInterface $input, OutputInterface $output): int
42 | {
43 | $res = $this->integrity->run();
44 | foreach ($res['errors'] as $error) {
45 | if ($error instanceof ChildInTranslatedContainerError) {
46 | $this->integrityFix->changeContainerParentToDefaultLanguageContainer($error);
47 | }
48 | }
49 | return 0;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Classes/Command/FixLanguageModeCommand.php:
--------------------------------------------------------------------------------
1 | integrity = $integrity;
37 | $this->integrityFix = $integrityFix;
38 | parent::__construct($name);
39 | }
40 |
41 | public function execute(InputInterface $input, OutputInterface $output): int
42 | {
43 | $res = $this->integrity->run();
44 | $errors = [];
45 | foreach ($res['errors'] as $error) {
46 | if ($error instanceof WrongL18nParentError) {
47 | $errors[] = $error;
48 | }
49 | }
50 | $this->integrityFix->languageMode($errors);
51 | return 0;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Classes/Command/IntegrityCommand.php:
--------------------------------------------------------------------------------
1 | integrity = $integrity;
31 | parent::__construct($name);
32 | }
33 |
34 | public function execute(InputInterface $input, OutputInterface $output): int
35 | {
36 | $io = new SymfonyStyle($input, $output);
37 | $res = $this->integrity->run();
38 | if (count($res['errors']) > 0) {
39 | $errors = [];
40 | foreach ($res['errors'] as $error) {
41 | $errors[] = $error->getErrorMessage();
42 | }
43 | $io->error('ERRORS: ' . chr(10) . implode(chr(10), $errors));
44 | }
45 | if (count($res['warnings']) > 0) {
46 | $warnings = [];
47 | foreach ($res['warnings'] as $warning) {
48 | $warnings[] = $warning->getErrorMessage();
49 | }
50 | $io->error('WARNINGS ("unused elements")' . chr(10) . implode(chr(10), $warnings));
51 | }
52 | if (count($res['warnings']) === 0 && count($res['errors']) === 0) {
53 | $io->success('Good Job, no errors/warnings!');
54 | }
55 | return 0;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Classes/Command/SortingCommand.php:
--------------------------------------------------------------------------------
1 | addArgument('pid', InputArgument::OPTIONAL, 'limit to this pid', 0);
35 | $this->addOption('apply', null, InputOption::VALUE_NONE, 'apply migration');
36 | $this->addOption(
37 | 'enable-logging',
38 | null,
39 | InputOption::VALUE_NONE,
40 | 'enables datahandler logging, should only use for debug issues, not in production'
41 | );
42 | }
43 |
44 | public function __construct(Sorting $sorting, ?string $name = null)
45 | {
46 | parent::__construct($name);
47 | $this->sorting = $sorting;
48 | }
49 |
50 | public function execute(InputInterface $input, OutputInterface $output): int
51 | {
52 | $dryrun = $input->getOption('apply') !== true;
53 | $pid = (int)$input->getArgument('pid');
54 |
55 | Bootstrap::initializeBackendAuthentication();
56 | $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences($GLOBALS['BE_USER']);
57 | $errors = $this->sorting->run(
58 | $dryrun,
59 | $input->getOption('enable-logging'),
60 | $pid
61 | );
62 |
63 | if ($output->isVerbose()) {
64 | $output->writeln('Checking ' . ($pid > 0 ? 'page ' . $pid : 'entire pagetree') . ($dryrun ? ' [DRYRUN]' : ''));
65 | foreach ($errors as $error) {
66 | $output->writeln($error);
67 | }
68 | if (empty($errors)) {
69 | $output->writeln('- all good, nothing to do here');
70 | }
71 | }
72 |
73 | return self::SUCCESS;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Classes/Command/SortingInPageCommand.php:
--------------------------------------------------------------------------------
1 | addArgument('pid', InputArgument::OPTIONAL, 'limit to this pid', 0);
35 | $this->addOption('apply', null, InputOption::VALUE_NONE, 'apply migration');
36 | $this->addOption(
37 | 'enable-logging',
38 | null,
39 | InputOption::VALUE_NONE,
40 | 'enables datahandler logging, should only use for debug issues, not in production'
41 | );
42 | }
43 |
44 | public function __construct(SortingInPage $sorting, ?string $name = null)
45 | {
46 | parent::__construct($name);
47 | $this->sorting = $sorting;
48 | }
49 |
50 | public function execute(InputInterface $input, OutputInterface $output): int
51 | {
52 | $dryrun = $input->getOption('apply') !== true;
53 | $pid = (int)$input->getArgument('pid');
54 |
55 | Bootstrap::initializeBackendAuthentication();
56 | $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences($GLOBALS['BE_USER']);
57 | $errors = $this->sorting->run(
58 | $dryrun,
59 | $input->getOption('enable-logging'),
60 | $pid
61 | );
62 | foreach ($errors as $error) {
63 | $output->writeln($error);
64 | }
65 | if (empty($errors)) {
66 | $output->writeln('migration finished');
67 | }
68 | return 0;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Classes/ContentDefender/ContainerColumnConfigurationService.php:
--------------------------------------------------------------------------------
1 | contentDefenderContainerDataHandlerHookIsLocked = true;
42 | }
43 |
44 | public function endCmdMap(): void
45 | {
46 | $this->contentDefenderContainerDataHandlerHookIsLocked = false;
47 | }
48 |
49 | public function isContentDefenderContainerDataHandlerHookLooked(): bool
50 | {
51 | return $this->contentDefenderContainerDataHandlerHookIsLocked;
52 | }
53 |
54 | public function __construct(ContainerFactory $containerFactory, Registry $tcaRegistry)
55 | {
56 | $this->containerFactory = $containerFactory;
57 | $this->tcaRegistry = $tcaRegistry;
58 | }
59 |
60 | protected function getRecord(int $uid): ?array
61 | {
62 | return BackendUtility::getRecord('tt_content', $uid);
63 | }
64 |
65 | public function addCopyMapping(int $sourceContentId, int $containerId, int $targetColpos): void
66 | {
67 | $record = $this->getRecord($sourceContentId);
68 | $sourceColPos = (int)$record['colPos'];
69 | $sourceContainerId = (int)$record['tx_container_parent'];
70 | $this->copyMapping[$sourceContainerId . ContainerGridColumn::CONTAINER_COL_POS_DELIMITER . $sourceColPos] = [
71 | 'containerId' => $containerId,
72 | 'sourceColPos' => $sourceColPos,
73 | 'targetColPos' => $targetColpos,
74 | ];
75 | }
76 |
77 | public function override(array $columnConfiguration, int $containerId, int $colPos): array
78 | {
79 | try {
80 | $container = $this->containerFactory->buildContainer($containerId);
81 | $columnConfiguration = $this->getColumnConfigurationForContainer($container, $colPos);
82 | } catch (Exception $e) {
83 | // not a container
84 | }
85 | return $columnConfiguration;
86 | }
87 |
88 | protected function getColumnConfigurationForContainer(Container $container, int $colPos): array
89 | {
90 | $cType = $container->getCType();
91 | $columnConfiguration = $this->tcaRegistry->getContentDefenderConfiguration($cType, $colPos);
92 | return $columnConfiguration;
93 | }
94 |
95 | public function isMaxitemsReachedByContainenrId(int $containerId, int $colPos, ?int $childUid = null): bool
96 | {
97 | try {
98 | $container = $this->containerFactory->buildContainer($containerId);
99 | return $this->isMaxitemsReached($container, $colPos, $childUid);
100 | } catch (Exception $e) {
101 | // not a container
102 | }
103 | return false;
104 | }
105 |
106 | public function isMaxitemsReached(Container $container, int $colPos, ?int $childUid = null): bool
107 | {
108 | $columnConfiguration = $this->getColumnConfigurationForContainer($container, $colPos);
109 | if (!isset($columnConfiguration['maxitems']) || (int)$columnConfiguration['maxitems'] === 0) {
110 | return false;
111 | }
112 | $childrenOfColumn = $container->getChildrenByColPos($colPos);
113 | $count = count($childrenOfColumn);
114 | if ($childUid !== null && $container->hasChildInColPos($colPos, $childUid)) {
115 | $count--;
116 | }
117 | return $count >= $columnConfiguration['maxitems'];
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/Classes/ContentDefender/Hooks/ColumnConfigurationManipulationHook.php:
--------------------------------------------------------------------------------
1 | containerFactory = $containerFactory;
37 | $this->tcaRegistry = $tcaRegistry;
38 | }
39 |
40 | public function manipulateConfiguration(array $configuration, int $colPos, $recordUid): array
41 | {
42 | $parent = $this->getParentUid($recordUid);
43 | if ($parent === null) {
44 | return $configuration;
45 | }
46 | try {
47 | $container = $this->containerFactory->buildContainer($parent);
48 | } catch (Exception $e) {
49 | // not a container
50 | return $configuration;
51 | }
52 | $cType = $container->getCType();
53 | $configuration = $this->tcaRegistry->getContentDefenderConfiguration($cType, $colPos);
54 | // maxitems needs not to be considered in this case
55 | // (new content elemment wizard, TcaCTypeItems: new record, TcaCTypeItems: edit record)
56 | // consider maxitems here leeds to errors, because relation to container gets lost in EXT:content_defender
57 | // EXT:container has already a own solution to prevent new records inside a container if maxitems is reached
58 | // "New Content" Button is not rendered inside die colPos, this is possible because EXT:container has its own templates
59 | $configuration['maxitems'] = 0;
60 | return $configuration;
61 | }
62 |
63 | private function getParentUid($recordUid): ?int
64 | {
65 | $request = $this->getServerRequest();
66 | if ($request === null) {
67 | return null;
68 | }
69 | $queryParams = $request->getQueryParams();
70 | if (isset($queryParams['tx_container_parent']) && $queryParams['tx_container_parent'] > 0) {
71 | // new content elemment wizard
72 | return (int)$queryParams['tx_container_parent'];
73 | }
74 | if (
75 | isset($queryParams['defVals']['tt_content']['tx_container_parent']) &&
76 | $queryParams['defVals']['tt_content']['tx_container_parent'] > 0
77 | ) {
78 | // TcaCTypeItems: new record
79 | return (int)$queryParams['defVals']['tt_content']['tx_container_parent'];
80 | }
81 | if (isset($queryParams['edit']['tt_content'][$recordUid])) {
82 | // TcaCTypeItems: edit record
83 | $record = BackendUtility::getRecord('tt_content', $recordUid, 'tx_container_parent');
84 | if (isset($record['tx_container_parent'])) {
85 | return (int)$record['tx_container_parent'];
86 | }
87 | }
88 | return null;
89 | }
90 |
91 | protected function getServerRequest(): ?ServerRequest
92 | {
93 | return $GLOBALS['TYPO3_REQUEST'] ?? null;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Classes/ContentDefender/Xclasses/CommandMapHook.php:
--------------------------------------------------------------------------------
1 | containerColumnConfigurationService = $containerColumnConfigurationService ?? GeneralUtility::makeInstance(ContainerColumnConfigurationService::class);
38 | parent::__construct($contentRepository);
39 | }
40 |
41 | public function processCmdmap_beforeStart(DataHandler $dataHandler): void
42 | {
43 | if (isset($dataHandler->cmdmap['pages']))
44 | {
45 | $this->containerColumnConfigurationService->startCmdMap();
46 | }
47 | if (!empty($dataHandler->cmdmap['tt_content'])) {
48 | $this->containerColumnConfigurationService->startCmdMap();
49 | foreach ($dataHandler->cmdmap['tt_content'] as $id => $cmds) {
50 | foreach ($cmds as $cmd => $data) {
51 | if (
52 | ($cmd === 'copy' || $cmd === 'move') &&
53 | (!empty($data['update'])) &&
54 | isset($data['update']['colPos']) &&
55 | $data['update']['colPos'] > 0 &&
56 | isset($data['update']['tx_container_parent']) &&
57 | $data['update']['tx_container_parent'] > 0 &&
58 | MathUtility::canBeInterpretedAsInteger($id)
59 | ) {
60 | $this->mapping[(int)$id] = [
61 | 'containerId' => (int)$data['update']['tx_container_parent'],
62 | 'colPos' => (int)$data['update']['colPos'],
63 | ];
64 | $this->containerColumnConfigurationService->addCopyMapping(
65 | (int)$id,
66 | (int)$data['update']['tx_container_parent'],
67 | (int)$data['update']['colPos']
68 | );
69 | $useChildId = null;
70 | if ($cmd === 'move') {
71 | $useChildId = $id;
72 | }
73 |
74 | if ($this->containerColumnConfigurationService->isMaxitemsReachedByContainenrId((int)$data['update']['tx_container_parent'], (int)$data['update']['colPos'], $useChildId)) {
75 | unset($dataHandler->cmdmap['tt_content'][$id]);
76 | $recpid = null;
77 | $detailsNumber = null;
78 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 13) {
79 | $recpid = 0;
80 | $detailsNumber = 28;
81 | }
82 | $dataHandler->log(
83 | 'tt_content',
84 | $id,
85 | 1,
86 | $recpid,
87 | 1,
88 | 'The command couldn\'t be executed due to reached maxitems configuration',
89 | $detailsNumber
90 | );
91 | }
92 | }
93 | }
94 | }
95 | }
96 | parent::processCmdmap_beforeStart($dataHandler);
97 | }
98 |
99 | public function processCmdmap_postProcess(string $command, string $table, $id, $value, DataHandler $dataHandler, $pasteUpdate, $pasteDatamap): void
100 | {
101 | $this->containerColumnConfigurationService->endCmdMap();
102 | }
103 |
104 | protected function isRecordAllowedByRestriction(array $columnConfiguration, array $record): bool
105 | {
106 | if (isset($record['tx_container_parent']) &&
107 | $record['tx_container_parent'] > 0 &&
108 | (GeneralUtility::makeInstance(DatahandlerProcess::class))->isContainerInProcess((int)$record['tx_container_parent'])
109 | ) {
110 | return true;
111 | }
112 | $recordOrigUid = (int)($record['uid'] ?? 0);
113 | if (isset($this->mapping[$recordOrigUid])) {
114 | $columnConfiguration = $this->containerColumnConfigurationService->override(
115 | $columnConfiguration,
116 | $this->mapping[$recordOrigUid]['containerId'],
117 | $this->mapping[$recordOrigUid]['colPos']
118 | );
119 | }
120 | return parent::isRecordAllowedByRestriction($columnConfiguration, $record);
121 | }
122 |
123 | protected function isRecordAllowedByItemsCount(array $columnConfiguration, array $record): bool
124 | {
125 | if (isset($record['tx_container_parent']) &&
126 | $record['tx_container_parent'] > 0 &&
127 | (GeneralUtility::makeInstance(DatahandlerProcess::class))->isContainerInProcess((int)$record['tx_container_parent'])
128 | ) {
129 | return true;
130 | }
131 | if (isset($this->mapping[$record['uid']])) {
132 | return true;
133 | }
134 | return parent::isRecordAllowedByItemsCount($columnConfiguration, $record);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Classes/ContentDefender/Xclasses/DatamapHook.php:
--------------------------------------------------------------------------------
1 | containerColumnConfigurationService = $containerColumnConfigurationService ?? GeneralUtility::makeInstance(ContainerColumnConfigurationService::class);
38 | parent::__construct($contentRepository);
39 | }
40 |
41 | /**
42 | * @param DataHandler $dataHandler
43 | */
44 | public function processDatamap_beforeStart(DataHandler $dataHandler): void
45 | {
46 | if (is_array($dataHandler->datamap['tt_content'] ?? null) &&
47 | !$this->containerColumnConfigurationService->isContentDefenderContainerDataHandlerHookLooked()
48 | ) {
49 | foreach ($dataHandler->datamap['tt_content'] as $id => $values) {
50 | if (
51 | isset($values['tx_container_parent']) &&
52 | $values['tx_container_parent'] > 0 &&
53 | isset($values['colPos']) &&
54 | $values['colPos'] > 0
55 | ) {
56 | if (MathUtility::canBeInterpretedAsInteger($id)) {
57 | // edit
58 | continue;
59 | }
60 | $containerId = (int)$values['tx_container_parent'];
61 |
62 | if ($this->containerColumnConfigurationService->isMaxitemsReachedByContainenrId($containerId, (int)$values['colPos'])) {
63 | unset($dataHandler->datamap['tt_content'][$id]);
64 | $recpid = null;
65 | $detailsNumber = null;
66 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 13) {
67 | $recpid = 0;
68 | $detailsNumber = 28;
69 | }
70 | $dataHandler->log(
71 | 'tt_content',
72 | $id,
73 | 1,
74 | $recpid,
75 | 1,
76 | 'The command couldn\'t be executed due to reached maxitems configuration',
77 | $detailsNumber
78 | );
79 | }
80 | }
81 | }
82 | parent::processDatamap_beforeStart($dataHandler);
83 | }
84 | }
85 |
86 | protected function isRecordAllowedByRestriction(array $columnConfiguration, array $record): bool
87 | {
88 | if (
89 | isset($record['tx_container_parent']) &&
90 | $record['tx_container_parent'] > 0 &&
91 | (GeneralUtility::makeInstance(DatahandlerProcess::class))->isContainerInProcess((int)$record['tx_container_parent'])
92 | ) {
93 | return true;
94 | }
95 | return parent::isRecordAllowedByRestriction($columnConfiguration, $record);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Classes/DataProcessing/ContainerDataProcessingFailedException.php:
--------------------------------------------------------------------------------
1 | contentDataProcessor = $contentDataProcessor;
39 | $this->context = $context;
40 | $this->frontendContainerFactory = $frontendContainerFactory;
41 | }
42 |
43 | public function process(
44 | ContentObjectRenderer $cObj,
45 | array $contentObjectConfiguration,
46 | array $processorConfiguration,
47 | array $processedData
48 | ): array {
49 | if (isset($processorConfiguration['if.']) && !$cObj->checkIf($processorConfiguration['if.'])) {
50 | return $processedData;
51 | }
52 | $contentId = null;
53 | if ($processorConfiguration['contentId.'] ?? false) {
54 | $contentId = (int)$cObj->stdWrap($processorConfiguration['contentId'] ?? '', $processorConfiguration['contentId.']);
55 | } elseif ($processorConfiguration['contentId'] ?? false) {
56 | $contentId = (int)$processorConfiguration['contentId'];
57 | }
58 |
59 | try {
60 | $container = $this->frontendContainerFactory->buildContainer($cObj, $this->context, $contentId);
61 | } catch (Exception $e) {
62 | // do nothing
63 | return $processedData;
64 | }
65 |
66 | $colPos = (int)$cObj->stdWrapValue('colPos', $processorConfiguration);
67 | if (empty($colPos)) {
68 | $allColPos = $container->getChildrenColPos();
69 | foreach ($allColPos as $colPos) {
70 | $processedData = $this->processColPos(
71 | $cObj,
72 | $container,
73 | $colPos,
74 | 'children_' . $colPos,
75 | $processedData,
76 | $processorConfiguration
77 | );
78 | }
79 | } else {
80 | $as = $cObj->stdWrapValue('as', $processorConfiguration, 'children');
81 | $processedData = $this->processColPos(
82 | $cObj,
83 | $container,
84 | $colPos,
85 | $as,
86 | $processedData,
87 | $processorConfiguration
88 | );
89 | }
90 | return $processedData;
91 | }
92 |
93 | protected function processColPos(
94 | ContentObjectRenderer $cObj,
95 | Container $container,
96 | int $colPos,
97 | string $as,
98 | array $processedData,
99 | array $processorConfiguration
100 | ): array {
101 | $children = $container->getChildrenByColPos($colPos);
102 |
103 | $contentRecordRenderer = $cObj->getContentObject('RECORDS');
104 | if ($contentRecordRenderer === null) {
105 | throw new ContainerDataProcessingFailedException('RECORDS content object not available.', 1691483526);
106 | }
107 | $conf = [
108 | 'tables' => 'tt_content',
109 | ];
110 | foreach ($children as &$child) {
111 | if (!isset($processorConfiguration['skipRenderingChildContent']) || (int)$processorConfiguration['skipRenderingChildContent'] === 0) {
112 | $conf['source'] = $child['uid'];
113 | $child['renderedContent'] = $cObj->render($contentRecordRenderer, $conf);
114 | }
115 | /** @var ContentObjectRenderer $recordContentObjectRenderer */
116 | $recordContentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class);
117 | $recordContentObjectRenderer->start($child, 'tt_content');
118 | $child = $this->contentDataProcessor->process($recordContentObjectRenderer, $processorConfiguration, $child);
119 | }
120 | $processedData[$as] = $children;
121 | return $processedData;
122 | }
123 |
124 | protected function getRequest(): ServerRequestInterface
125 | {
126 | return $GLOBALS['TYPO3_REQUEST'];
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Classes/Domain/Factory/ContainerFactory.php:
--------------------------------------------------------------------------------
1 | database = $database;
44 | $this->tcaRegistry = $tcaRegistry;
45 | $this->workspaceId = (int)$context->getPropertyFromAspect('workspace', 'id');
46 | }
47 |
48 | protected function containerByUid(int $uid): ?array
49 | {
50 | return $this->database->fetchOneRecord($uid);
51 | }
52 |
53 | protected function defaultContainer(array $localizedContainer): ?array
54 | {
55 | return $this->database->fetchOneDefaultRecord($localizedContainer);
56 | }
57 |
58 | public function buildContainer(int $uid): Container
59 | {
60 | $record = $this->containerByUid($uid);
61 | if ($record === null) {
62 | throw new Exception('cannot fetch record with uid ' . $uid, 1576572850);
63 | }
64 | if (!$this->tcaRegistry->isContainerElement($record['CType'])) {
65 | throw new Exception('not a container element with uid ' . $uid, 1576572851);
66 | }
67 |
68 | $defaultRecord = null;
69 | $language = (int)$record['sys_language_uid'];
70 | if ($language > 0) {
71 | $defaultRecord = $this->defaultContainer($record);
72 | if ($defaultRecord === null) {
73 | // free mode
74 | $childRecords = $this->children($record, $language);
75 | } else {
76 | // connected mode
77 | $defaultRecords = $this->children($defaultRecord, 0);
78 | $childRecords = $this->localizedRecordsByDefaultRecords($defaultRecords, $language);
79 | }
80 | } else {
81 | $childRecords = $this->children($record, $language);
82 | }
83 | $childRecordByColPosKey = $this->recordsByColPosKey($childRecords);
84 | if ($defaultRecord === null) {
85 | $container = GeneralUtility::makeInstance(Container::class, $record, $childRecordByColPosKey, $language);
86 | } else {
87 | $container = GeneralUtility::makeInstance(Container::class, $defaultRecord, $childRecordByColPosKey, $language);
88 | }
89 | return $container;
90 | }
91 |
92 | protected function localizedRecordsByDefaultRecords(array $defaultRecords, int $language): array
93 | {
94 | $localizedRecords = $this->database->fetchOverlayRecords($defaultRecords, $language);
95 | $childRecords = $this->sortLocalizedRecordsByDefaultRecords($defaultRecords, $localizedRecords);
96 | return $childRecords;
97 | }
98 |
99 | protected function children(array $containerRecord, int $language): array
100 | {
101 | $records = $this->database->fetchRecordsByParentAndLanguage((int)$containerRecord['uid'], $language);
102 | $records = $this->workspaceOverlay($records);
103 | return $records;
104 | }
105 |
106 | protected function workspaceOverlay(array $records): array
107 | {
108 | $filtered = [];
109 | foreach ($records as $row) {
110 | BackendUtility::workspaceOL('tt_content', $row, $this->workspaceId, true);
111 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() > 12) {
112 | if ($row && VersionState::tryFrom($row['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER) {
113 | $filtered[] = $row;
114 | }
115 | } else {
116 | if ($row && !VersionState::cast($row['t3ver_state'] ?? 0)->equals(VersionState::DELETE_PLACEHOLDER)) {
117 | $filtered[] = $row;
118 | }
119 | }
120 | }
121 | return $filtered;
122 | }
123 |
124 | protected function sortLocalizedRecordsByDefaultRecords(array $defaultRecords, array $localizedRecords): array
125 | {
126 | $sorted = [];
127 | foreach ($defaultRecords as $defaultRecord) {
128 | foreach ($localizedRecords as $localizedRecord) {
129 | if ($localizedRecord['l18n_parent'] === $defaultRecord['uid'] ||
130 | $localizedRecord['l18n_parent'] === $defaultRecord['t3ver_oid']
131 | ) {
132 | $sorted[] = $localizedRecord;
133 | }
134 | }
135 | }
136 | return $sorted;
137 | }
138 |
139 | protected function recordsByColPosKey(array $records): array
140 | {
141 | $recordsByColPosKey = [];
142 | foreach ($records as $record) {
143 | if (empty($recordsByColPosKey[$record['colPos']])) {
144 | $recordsByColPosKey[$record['colPos']] = [];
145 | }
146 | $recordsByColPosKey[$record['colPos']][] = $record;
147 | }
148 | return $recordsByColPosKey;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/Classes/Domain/Factory/Database.php:
--------------------------------------------------------------------------------
1 | backendUserId = (int)$context->getPropertyFromAspect('backend.user', 'id', 0);
39 | $this->workspaceId = (int)$context->getPropertyFromAspect('workspace', 'id');
40 | }
41 |
42 | protected function getQueryBuilder(): QueryBuilder
43 | {
44 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
45 | $queryBuilder->getRestrictions()
46 | ->removeAll()
47 | ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
48 | ->add(GeneralUtility::makeInstance(WorkspaceRestriction::class, $this->workspaceId));
49 | return $queryBuilder;
50 | }
51 |
52 | public function fetchRecordsByPidAndLanguage(int $pid, int $language): array
53 | {
54 | $queryBuilder = $this->getQueryBuilder();
55 | $rows = $queryBuilder->select('*')
56 | ->from('tt_content')
57 | ->where(
58 | $queryBuilder->expr()->eq(
59 | 'sys_language_uid',
60 | $queryBuilder->createNamedParameter($language, Connection::PARAM_INT)
61 | ),
62 | $queryBuilder->expr()->eq(
63 | 'pid',
64 | $queryBuilder->createNamedParameter($pid, Connection::PARAM_INT)
65 | )
66 | )
67 | ->orderBy('sorting', 'ASC')
68 | ->executeQuery()
69 | ->fetchAllAssociative();
70 | return $rows;
71 | }
72 |
73 | public function fetchOneRecord(int $uid): ?array
74 | {
75 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
76 | $queryBuilder->getRestrictions()->removeAll();
77 | $record = $queryBuilder->select('*')
78 | ->from('tt_content')
79 | ->where(
80 | $queryBuilder->expr()->eq(
81 | 'uid',
82 | $queryBuilder->createNamedParameter($uid, Connection::PARAM_INT)
83 | )
84 | )
85 | ->executeQuery()
86 | ->fetchAssociative();
87 | if ($record === false) {
88 | return null;
89 | }
90 | return $record;
91 | }
92 |
93 | public function fetchOneDefaultRecord(array $record): ?array
94 | {
95 | $queryBuilder = $this->getQueryBuilder();
96 | $record = $queryBuilder->select('*')
97 | ->from('tt_content')
98 | ->where(
99 | $queryBuilder->expr()->eq(
100 | 'uid',
101 | $queryBuilder->createNamedParameter($record['l18n_parent'], Connection::PARAM_INT)
102 | ),
103 | $queryBuilder->expr()->eq(
104 | 'sys_language_uid',
105 | $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
106 | )
107 | )
108 | ->executeQuery()
109 | ->fetchAssociative();
110 | if ($record === false) {
111 | return null;
112 | }
113 | return $record;
114 | }
115 |
116 | public function fetchRecordsByParentAndLanguage(int $parent, int $language): array
117 | {
118 | $queryBuilder = $this->getQueryBuilder();
119 |
120 | $rows = $queryBuilder->select('*')
121 | ->from('tt_content')
122 | ->where(
123 | $queryBuilder->expr()->eq(
124 | 'tx_container_parent',
125 | $queryBuilder->createNamedParameter($parent, Connection::PARAM_INT)
126 | ),
127 | $queryBuilder->expr()->eq(
128 | 'sys_language_uid',
129 | $queryBuilder->createNamedParameter($language, Connection::PARAM_INT)
130 | )
131 | )
132 | ->orderBy('sorting', 'ASC')
133 | ->executeQuery()
134 | ->fetchAllAssociative();
135 | return $rows;
136 | }
137 |
138 | public function fetchOverlayRecords(array $records, int $language): array
139 | {
140 | $uids = [];
141 | foreach ($records as $record) {
142 | $uids[] = $record['uid'];
143 | if ($record['t3ver_oid'] > 0) {
144 | $uids[] = $record['t3ver_oid'];
145 | }
146 | }
147 | $queryBuilder = $this->getQueryBuilder();
148 | $rows = $queryBuilder->select('*')
149 | ->from('tt_content')
150 | ->where(
151 | $queryBuilder->expr()->in(
152 | 'l18n_parent',
153 | $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)
154 | ),
155 | $queryBuilder->expr()->eq(
156 | 'sys_language_uid',
157 | $queryBuilder->createNamedParameter($language, Connection::PARAM_INT)
158 | )
159 | )
160 | ->executeQuery()
161 | ->fetchAllAssociative();
162 | return $rows;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/Classes/Domain/Factory/Exception.php:
--------------------------------------------------------------------------------
1 | tcaRegistry = $tcaRegistry;
30 | }
31 |
32 | public function buildContainer(ContentObjectRenderer $cObj, Context $context, ?int $uid = null): Container
33 | {
34 | if ($uid === null) {
35 | $record = $cObj->data;
36 | } else {
37 | $records = $cObj->getRecords('tt_content', ['uidInList' => $uid, 'pidInList' => 0]);
38 | if (empty($records)) {
39 | throw new Exception('no record ' . $uid, 1734946029);
40 | }
41 | $record = $records[0];
42 | }
43 | if (!$this->tcaRegistry->isContainerElement($record['CType'] ?? '')) {
44 | throw new Exception('not a container element with uid ' . $uid, 1734946028);
45 | }
46 | $conf = ['where' => 'tx_container_parent=' . $record['uid'], 'orderBy' => 'sorting', 'pidInList' => $record['pid']];
47 | /** @var LanguageAspect $languageAspect */
48 | $languageAspect = $context->getAspect('language');
49 | if ($languageAspect->getOverlayType() === LanguageAspect::OVERLAYS_OFF && $record['l18n_parent'] > 0) {
50 | $conf['where'] .= ' OR tx_container_parent=' . $record['l18n_parent'];
51 | }
52 | $childRecords = $cObj->getRecords('tt_content', $conf);
53 | $childRecords = $this->recordsByColPosKey($childRecords);
54 | $container = new Container($record, $childRecords, (int)$record['sys_language_uid']);
55 | return $container;
56 | }
57 |
58 | protected function recordsByColPosKey(array $records): array
59 | {
60 | $recordsByColPosKey = [];
61 | foreach ($records as $record) {
62 | if (empty($recordsByColPosKey[$record['colPos']])) {
63 | $recordsByColPosKey[$record['colPos']] = [];
64 | }
65 | $recordsByColPosKey[$record['colPos']][] = $record;
66 | }
67 | return $recordsByColPosKey;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Classes/Domain/Factory/PageView/Backend/ContainerFactory.php:
--------------------------------------------------------------------------------
1 | contentStorage = $contentStorage;
34 | }
35 |
36 | protected function children(array $containerRecord, int $language): array
37 | {
38 | return $this->contentStorage->getContainerChildren($containerRecord, $language);
39 | }
40 |
41 | protected function localizedRecordsByDefaultRecords(array $defaultRecords, int $language): array
42 | {
43 | $childRecords = parent::localizedRecordsByDefaultRecords($defaultRecords, $language);
44 | return $this->contentStorage->workspaceOverlay($childRecords);
45 | }
46 |
47 | protected function containerByUid(int $uid): ?array
48 | {
49 | $record = $this->database->fetchOneRecord($uid);
50 | if ($record === null) {
51 | return null;
52 | }
53 | return $this->contentStorage->containerRecordWorkspaceOverlay($record);
54 | }
55 |
56 | protected function defaultContainer(array $localizedContainer): ?array
57 | {
58 | if (isset($localizedContainer['_ORIG_uid'])) {
59 | $localizedContainer = $this->database->fetchOneRecord((int)$localizedContainer['uid']);
60 | }
61 | $defaultRecord = $this->database->fetchOneDefaultRecord($localizedContainer);
62 | if ($defaultRecord === null) {
63 | return null;
64 | }
65 | return $this->contentStorage->containerRecordWorkspaceOverlay($defaultRecord);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Classes/Domain/Factory/PageView/Backend/ContentStorage.php:
--------------------------------------------------------------------------------
1 | database = $database;
42 | $this->workspaceId = (int)$context->getPropertyFromAspect('workspace', 'id');
43 | }
44 |
45 | protected function buildRecords(int $pid, int $language): array
46 | {
47 | $records = $this->database->fetchRecordsByPidAndLanguage($pid, $language);
48 | $records = $this->workspaceOverlay($records);
49 | $records = $this->recordsByContainer($records);
50 | return $records;
51 | }
52 |
53 | protected function recordsByContainer(array $records): array
54 | {
55 | $recordsByContainer = [];
56 | foreach ($records as $record) {
57 | if ($record['tx_container_parent'] > 0) {
58 | if (empty($recordsByContainer[$record['tx_container_parent']])) {
59 | $recordsByContainer[$record['tx_container_parent']] = [];
60 | }
61 | $recordsByContainer[$record['tx_container_parent']][] = $record;
62 | }
63 | }
64 | return $recordsByContainer;
65 | }
66 |
67 | public function getContainerChildren(array $containerRecord, int $language): array
68 | {
69 | $pid = (int)$containerRecord['pid'];
70 | if (isset($containerRecord['t3ver_oid']) && $containerRecord['t3ver_oid'] > 0) {
71 | $defaultContainerRecord = $this->database->fetchOneRecord((int)$containerRecord['t3ver_oid']);
72 | $uid = (int)$defaultContainerRecord['uid'];
73 | } else {
74 | $uid = (int)$containerRecord['uid'];
75 | }
76 | if (!isset($this->records[$pid][$language])) {
77 | $this->records[$pid][$language] = $this->buildRecords($pid, $language);
78 | }
79 | if (empty($this->records[$pid][$language][$uid])) {
80 | return [];
81 | }
82 | return $this->records[$pid][$language][$uid];
83 | }
84 |
85 | public function workspaceOverlay(array $records): array
86 | {
87 | $filtered = [];
88 | foreach ($records as $row) {
89 | BackendUtility::workspaceOL('tt_content', $row, $this->workspaceId, true);
90 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() > 12) {
91 | if ($row && VersionState::tryFrom($row['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER) {
92 | $filtered[] = $row;
93 | }
94 | } else {
95 | if ($row && !VersionState::cast($row['t3ver_state'] ?? 0)->equals(VersionState::DELETE_PLACEHOLDER)) {
96 | $filtered[] = $row;
97 | }
98 | }
99 | }
100 | return $filtered;
101 | }
102 |
103 | public function containerRecordWorkspaceOverlay(array $record): ?array
104 | {
105 | BackendUtility::workspaceOL('tt_content', $record, $this->workspaceId, false);
106 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() > 12) {
107 | if ($record && VersionState::tryFrom($record['t3ver_state'] ?? 0) !== VersionState::DELETE_PLACEHOLDER) {
108 | return $record;
109 | }
110 | } else {
111 | if ($record && !VersionState::cast($record['t3ver_state'] ?? 0)->equals(VersionState::DELETE_PLACEHOLDER)) {
112 | return $record;
113 | }
114 | }
115 | return null;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/Container.php:
--------------------------------------------------------------------------------
1 | containerRecord = $containerRecord;
40 | $this->childRecords = $childRecords;
41 | $this->language = $language;
42 | }
43 |
44 | /**
45 | * @return int
46 | */
47 | public function getUid(): int
48 | {
49 | return (int)$this->containerRecord['uid'];
50 | }
51 |
52 | public function getUidOfLiveWorkspace(): int
53 | {
54 | if (isset($this->containerRecord['t3ver_oid']) && $this->containerRecord['t3ver_oid'] > 0) {
55 | return (int)$this->containerRecord['t3ver_oid'];
56 | }
57 | return $this->getUid();
58 | }
59 |
60 | /**
61 | * @return int
62 | */
63 | public function getPid(): int
64 | {
65 | if (!empty($this->containerRecord['_ORIG_pid'])) {
66 | return (int)$this->containerRecord['_ORIG_pid'];
67 | }
68 | return (int)$this->containerRecord['pid'];
69 | }
70 |
71 | /**
72 | * @return bool
73 | */
74 | public function isConnectedMode(): bool
75 | {
76 | return (int)$this->containerRecord['sys_language_uid'] === 0;
77 | }
78 |
79 | /**
80 | * @return int
81 | */
82 | public function getLanguage(): int
83 | {
84 | return $this->language;
85 | }
86 |
87 | /**
88 | * @return string
89 | */
90 | public function getCType(): string
91 | {
92 | return $this->containerRecord['CType'];
93 | }
94 |
95 | /**
96 | * @return array
97 | */
98 | public function getContainerRecord(): array
99 | {
100 | return $this->containerRecord;
101 | }
102 |
103 | /**
104 | * @return array
105 | */
106 | public function getChildRecords(): array
107 | {
108 | $childRecords = [];
109 | foreach ($this->childRecords as $colPos => $records) {
110 | $childRecords = array_merge($childRecords, $records);
111 | }
112 | return $childRecords;
113 | }
114 |
115 | /**
116 | * @param int $colPos
117 | * @return array
118 | */
119 | public function getChildrenByColPos(int $colPos): array
120 | {
121 | if (empty($this->childRecords[$colPos])) {
122 | return [];
123 | }
124 | return $this->childRecords[$colPos];
125 | }
126 |
127 | public function hasChildInColPos(int $colPos, int $childUid): bool
128 | {
129 | if (!isset($this->childRecords[$colPos])) {
130 | return false;
131 | }
132 | foreach ($this->childRecords[$colPos] as $childRecord) {
133 | if ((int)$childRecord['uid'] === $childUid) {
134 | return true;
135 | }
136 | }
137 | return false;
138 | }
139 |
140 | /**
141 | * @return array
142 | */
143 | public function getChildrenColPos(): array
144 | {
145 | return array_keys($this->childRecords);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/Classes/Domain/Service/ContainerService.php:
--------------------------------------------------------------------------------
1 | tcaRegistry = $tcaRegistry;
35 | $this->containerFactory = $containerFactory;
36 | }
37 |
38 | public function getNewContentElementAtTopTargetInColumn(Container $container, int $targetColPos): int
39 | {
40 | $target = -$container->getUid();
41 | $previousRecord = null;
42 | $allColumns = $this->tcaRegistry->getAllAvailableColumnsColPos($container->getCType());
43 | foreach ($allColumns as $colPos) {
44 | if ($colPos === $targetColPos && $previousRecord !== null) {
45 | $target = -(int)$previousRecord['uid'];
46 | }
47 | $children = $container->getChildrenByColPos($colPos);
48 | if (!empty($children)) {
49 | $last = array_pop($children);
50 | $previousRecord = $last;
51 | }
52 | }
53 | return $target;
54 | }
55 |
56 | public function getAfterContainerRecord(Container $container): array
57 | {
58 | $childRecords = $container->getChildRecords();
59 | if (empty($childRecords)) {
60 | return $container->getContainerRecord();
61 | }
62 |
63 | $lastChild = array_pop($childRecords);
64 | if (!$this->tcaRegistry->isContainerElement($lastChild['CType'])) {
65 | return $lastChild;
66 | }
67 |
68 | $container = $this->containerFactory->buildContainer((int)$lastChild['uid']);
69 | return $this->getAfterContainerRecord($container);
70 | }
71 |
72 | public function getAfterContainerElementTarget(Container $container): int
73 | {
74 | $target = $this->getAfterContainerRecord($container);
75 |
76 | return -$target['uid'];
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/Classes/Events/BeforeContainerConfigurationIsAppliedEvent.php:
--------------------------------------------------------------------------------
1 | containerConfiguration = $containerConfiguration;
25 | }
26 |
27 | public function skip(): void
28 | {
29 | $this->skip = true;
30 | }
31 |
32 | public function shouldBeSkipped(): bool
33 | {
34 | return $this->skip;
35 | }
36 |
37 | public function getContainerConfiguration(): ContainerConfiguration
38 | {
39 | return $this->containerConfiguration;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Classes/Events/BeforeContainerPreviewIsRenderedEvent.php:
--------------------------------------------------------------------------------
1 | container = $container;
33 | $this->view = $view;
34 | $this->grid = $grid;
35 | $this->item = $item;
36 | }
37 |
38 | public function getContainer(): Container
39 | {
40 | return $this->container;
41 | }
42 |
43 | public function getView(): StandaloneView
44 | {
45 | return $this->view;
46 | }
47 |
48 | public function getGrid(): Grid
49 | {
50 | return $this->grid;
51 | }
52 |
53 | public function getItem(): GridColumnItem
54 | {
55 | trigger_error('gridColumItem property will be removed on next major release', E_USER_DEPRECATED);
56 | return $this->item;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Classes/Exception.php:
--------------------------------------------------------------------------------
1 | database = $database;
28 | }
29 |
30 | public function processCmdmap_afterFinish(DataHandler $dataHandler): void
31 | {
32 | $cmdmap = $dataHandler->cmdmap;
33 | $copyMappingArray_merged = $dataHandler->copyMappingArray_merged;
34 |
35 | foreach ($cmdmap as $table => $incomingCmdArrayPerId) {
36 | if ($table !== 'tt_content') {
37 | continue;
38 | }
39 | foreach ($incomingCmdArrayPerId as $id => $incomingCmdArray) {
40 | if (!is_array($incomingCmdArray)) {
41 | continue;
42 | }
43 | if (empty($incomingCmdArray['copyToLanguage'])) {
44 | continue;
45 | }
46 | if (empty($copyMappingArray_merged['tt_content'][$id])) {
47 | continue;
48 | }
49 | $copyToLanguage = $incomingCmdArray['copyToLanguage'];
50 | $newId = $copyMappingArray_merged['tt_content'][$id];
51 | $data = [
52 | 'tt_content' => [],
53 | ];
54 | // child in free mode is copied
55 | $child = $this->database->fetchOneRecord($newId);
56 | if ($child === null) {
57 | continue;
58 | }
59 | if ($child['tx_container_parent'] > 0) {
60 | $copiedFromChild = $this->database->fetchOneRecord($id);
61 | // copied from non default language (connectecd mode) children
62 | if ($copiedFromChild !== null && $copiedFromChild['sys_language_uid'] > 0 && $copiedFromChild['l18n_parent'] > 0) {
63 | // fetch orig container
64 | $origContainer = $this->database->fetchOneTranslatedRecordByLocalizationParent((int)$copiedFromChild['tx_container_parent'], $copiedFromChild['sys_language_uid']);
65 | // should never be null
66 | if ($origContainer !== null) {
67 | $freeModeContainer = $this->database->fetchContainerRecordLocalizedFreeMode((int)$origContainer['uid'], $copyToLanguage);
68 | if ($freeModeContainer !== null) {
69 | $data['tt_content'][$newId] = ['tx_container_parent' => (int)$freeModeContainer['uid']];
70 | }
71 | }
72 | } else {
73 | $freeModeContainer = $this->database->fetchContainerRecordLocalizedFreeMode((int)$child['tx_container_parent'], $copyToLanguage);
74 | if ($freeModeContainer !== null) {
75 | $data['tt_content'][$newId] = ['tx_container_parent' => (int)$freeModeContainer['uid']];
76 | }
77 | }
78 | }
79 | if (empty($data['tt_content'])) {
80 | continue;
81 | }
82 | $localDataHandler = GeneralUtility::makeInstance(DataHandler::class);
83 | $localDataHandler->enableLogging = $dataHandler->enableLogging;
84 | $localDataHandler->start($data, [], $dataHandler->BE_USER);
85 | $localDataHandler->process_datamap();
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Classes/Hooks/Datahandler/DatahandlerProcess.php:
--------------------------------------------------------------------------------
1 | containerInProcess, true);
24 | }
25 |
26 | public function startContainerProcess(int $containerId): void
27 | {
28 | if (!in_array($containerId, $this->containerInProcess, true)) {
29 | $this->containerInProcess[] = $containerId;
30 | }
31 | }
32 |
33 | public function endContainerProcess(int $containerId): void
34 | {
35 | if (in_array($containerId, $this->containerInProcess, true)) {
36 | $this->containerInProcess = array_diff($this->containerInProcess, [$containerId]);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Classes/Hooks/Datahandler/DatamapBeforeStartHook.php:
--------------------------------------------------------------------------------
1 | containerFactory = $containerFactory;
50 | $this->database = $database;
51 | $this->tcaRegistry = $tcaRegistry;
52 | $this->containerService = $containerService;
53 | }
54 |
55 | public function processDatamap_beforeStart(DataHandler $dataHandler): void
56 | {
57 | $dataHandler->datamap = $this->datamapForChildLocalizations($dataHandler->datamap);
58 | $dataHandler->datamap = $this->datamapForChildrenChangeContainerLanguage($dataHandler->datamap);
59 | }
60 |
61 | protected function datamapForChildLocalizations(array $datamap): array
62 | {
63 | $datamapForLocalizations = ['tt_content' => []];
64 | if (!empty($datamap['tt_content'])) {
65 | foreach ($datamap['tt_content'] as $id => $data) {
66 | if (isset($data['colPos'])) {
67 | $record = $this->database->fetchOneRecord((int)$id);
68 | if ($record !== null && $record['sys_language_uid'] === 0) {
69 | $translations = $this->database->fetchOverlayRecords($record);
70 | foreach ($translations as $translation) {
71 | $datamapForLocalizations['tt_content'][$translation['uid']] = [
72 | 'colPos' => $data['colPos'],
73 | ];
74 | if (isset($data['tx_container_parent'])) {
75 | $datamapForLocalizations['tt_content'][$translation['uid']]['tx_container_parent'] = $data['tx_container_parent'];
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 | if (count($datamapForLocalizations['tt_content']) > 0) {
83 | $datamap['tt_content'] = array_replace($datamap['tt_content'], $datamapForLocalizations['tt_content']);
84 | }
85 | return $datamap;
86 | }
87 |
88 | protected function datamapForChildrenChangeContainerLanguage(array $datamap): array
89 | {
90 | $datamapForLocalizations = ['tt_content' => []];
91 | if (!empty($datamap['tt_content'])) {
92 | foreach ($datamap['tt_content'] as $id => $data) {
93 | if (isset($data['sys_language_uid'])) {
94 | try {
95 | $container = $this->containerFactory->buildContainer((int)$id);
96 | $children = $container->getChildRecords();
97 | foreach ($children as $child) {
98 | if ((int)$child['sys_language_uid'] !== (int)$data['sys_language_uid']) {
99 | $datamapForLocalizations['tt_content'][$child['uid']] = [
100 | 'sys_language_uid' => $data['sys_language_uid'],
101 | ];
102 | }
103 | }
104 | } catch (Exception $e) {
105 | // nothing todo
106 | }
107 | }
108 | }
109 | }
110 | if (count($datamapForLocalizations['tt_content']) > 0) {
111 | $datamap['tt_content'] = array_replace($datamap['tt_content'], $datamapForLocalizations['tt_content']);
112 | }
113 | return $datamap;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Classes/Hooks/Datahandler/DatamapPreProcessFieldArrayHook.php:
--------------------------------------------------------------------------------
1 | containerFactory = $containerFactory;
52 | $this->database = $database;
53 | $this->tcaRegistry = $tcaRegistry;
54 | $this->containerService = $containerService;
55 | }
56 |
57 | protected function newElementAfterContainer(array $incomingFieldArray): array
58 | {
59 | if (isset($incomingFieldArray['sorting'])) {
60 | return $incomingFieldArray;
61 | }
62 | $record = $this->database->fetchOneRecord(-(int)$incomingFieldArray['pid']);
63 | if ($record === null) {
64 | // new elements in container have already correct target
65 | return $incomingFieldArray;
66 | }
67 | if ((int)$record['uid'] === (int)($incomingFieldArray['tx_container_parent'] ?? 0)) {
68 | return $incomingFieldArray;
69 | }
70 | if (!$this->tcaRegistry->isContainerElement($record['CType'])) {
71 | return $incomingFieldArray;
72 | }
73 | try {
74 | $container = $this->containerFactory->buildContainer((int)$record['uid']);
75 | if ($container->getLanguage() === 0 || !$container->isConnectedMode()) {
76 | $incomingFieldArray['pid'] = $this->containerService->getAfterContainerElementTarget($container);
77 | }
78 | } catch (Exception $e) {
79 | }
80 | return $incomingFieldArray;
81 | }
82 |
83 | protected function copyToLanguageElementInContainer(array $incomingFieldArray): array
84 | {
85 | if (!isset($incomingFieldArray['tx_container_parent']) || (int)$incomingFieldArray['tx_container_parent'] === 0) {
86 | return $incomingFieldArray;
87 | }
88 | if (!isset($incomingFieldArray['l10n_source']) || (int)$incomingFieldArray['l10n_source'] === 0) {
89 | return $incomingFieldArray;
90 | }
91 | if (!isset($incomingFieldArray['l18n_parent']) || (int)$incomingFieldArray['l18n_parent'] > 0) {
92 | return $incomingFieldArray;
93 | }
94 | if (!isset($incomingFieldArray['sys_language_uid']) || (int)$incomingFieldArray['sys_language_uid'] === 0) {
95 | return $incomingFieldArray;
96 | }
97 | $record = $this->database->fetchOneRecord(-$incomingFieldArray['pid']);
98 | $translatedContainerRecord = $this->database->fetchOneTranslatedRecordByl10nSource((int)$incomingFieldArray['tx_container_parent'], (int)$incomingFieldArray['sys_language_uid']);
99 | if ($record === null || $translatedContainerRecord === null) {
100 | return $incomingFieldArray;
101 | }
102 | try {
103 | $incomingFieldArray['tx_container_parent'] = $translatedContainerRecord['uid'];
104 | $container = $this->containerFactory->buildContainer((int)$translatedContainerRecord['uid']);
105 | if ((int)$record['sys_language_uid'] === 0 || empty($container->getChildrenByColPos((int)$incomingFieldArray['colPos']))) {
106 | $target = $this->containerService->getNewContentElementAtTopTargetInColumn($container, (int)$incomingFieldArray['colPos']);
107 | $incomingFieldArray['pid'] = $target;
108 | }
109 | } catch (Exception $e) {
110 | // not a container
111 | }
112 | return $incomingFieldArray;
113 | }
114 |
115 | public function processDatamap_preProcessFieldArray(array &$incomingFieldArray, string $table, $id, DataHandler $dataHandler): void
116 | {
117 | if ($table !== 'tt_content') {
118 | return;
119 | }
120 | if (MathUtility::canBeInterpretedAsInteger($id)) {
121 | return;
122 | }
123 | if (!isset($incomingFieldArray['pid']) || (int)$incomingFieldArray['pid'] >= 0) {
124 | return;
125 | }
126 | $incomingFieldArray = $this->newElementAfterContainer($incomingFieldArray);
127 | $incomingFieldArray = $this->copyToLanguageElementInContainer($incomingFieldArray);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Classes/Hooks/Datahandler/DeleteHook.php:
--------------------------------------------------------------------------------
1 | containerFactory = $containerFactory;
31 | }
32 |
33 | public function processCmdmap_deleteAction(string $table, int $id, array $recordToDelete, bool $recordWasDeleted, DataHandler $dataHandler): void
34 | {
35 | if ($table === 'tt_content') {
36 | $this->deleteChildren($id, $dataHandler->BE_USER, $dataHandler->enableLogging);
37 | }
38 | }
39 |
40 | public function processCmdmap_discardAction(string $table, int $id, array $recordToDelete, bool $recordWasDeleted): void
41 | {
42 | if ($table === 'tt_content') {
43 | $this->deleteChildren($id, null);
44 | }
45 | }
46 |
47 | protected function deleteChildren(int $id, ?BackendUserAuthentication $backendUser, ?bool $enableLogging = null): void
48 | {
49 | try {
50 | $container = $this->containerFactory->buildContainer($id);
51 | $children = $container->getChildRecords();
52 | $toDelete = [];
53 | foreach ($children as $colPos => $record) {
54 | $toDelete[$record['uid']] = ['delete' => 1];
55 | }
56 | if (!empty($toDelete)) {
57 | $cmd = ['tt_content' => $toDelete];
58 | $localDataHandler = GeneralUtility::makeInstance(DataHandler::class);
59 | if ($enableLogging !== null) {
60 | $localDataHandler->enableLogging = $enableLogging;
61 | }
62 | $localDataHandler->start([], $cmd, $backendUser);
63 | $localDataHandler->process_cmdmap();
64 | }
65 | } catch (Exception $e) {
66 | // nothing todo
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Classes/Hooks/UsedRecords.php:
--------------------------------------------------------------------------------
1 | containerFactory = $containerFactory;
35 | $this->tcaRegistry = $tcaRegistry;
36 | }
37 |
38 | public function addContainerChildren(array $params, PageLayoutView $pageLayoutView): bool
39 | {
40 | $record = $params['record'];
41 |
42 | if (isset($record['tx_container_parent']) && $record['tx_container_parent'] > 0) {
43 | try {
44 | $container = $this->containerFactory->buildContainer((int)$record['tx_container_parent']);
45 | $columns = $this->tcaRegistry->getAvailableColumns($container->getCType());
46 | foreach ($columns as $column) {
47 | if ($column['colPos'] === (int)$record['colPos']) {
48 | if ($record['sys_language_uid'] > 0 && $container->isConnectedMode()) {
49 | return $container->hasChildInColPos((int)$record['colPos'], (int)$record['l18n_parent']);
50 | }
51 | return $container->hasChildInColPos((int)$record['colPos'], (int)$record['uid']);
52 | }
53 | }
54 | return false;
55 | } catch (Exception $e) {
56 | }
57 | }
58 | return $params['used'];
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Classes/Hooks/WizardItems.php:
--------------------------------------------------------------------------------
1 | getParentIdFromRequest();
23 | if ($parent !== null) {
24 | foreach ($wizardItems as $key => $wizardItem) {
25 | $wizardItems[$key]['tt_content_defValues']['tx_container_parent'] = $parent;
26 | if (!isset($wizardItems[$key]['params'])) {
27 | $wizardItems[$key]['params'] = '?defVals[tt_content][tx_container_parent]=' . $parent;
28 | } else {
29 | $wizardItems[$key]['params'] .= '&defVals[tt_content][tx_container_parent]=' . $parent;
30 | }
31 | }
32 | }
33 | }
34 |
35 | protected function getParentIdFromRequest(): ?int
36 | {
37 | $request = $this->getServerRequest();
38 | if ($request === null) {
39 | return null;
40 | }
41 | $queryParams = $request->getQueryParams();
42 | if (isset($queryParams['tx_container_parent']) && (int)$queryParams['tx_container_parent'] > 0) {
43 | return (int)$queryParams['tx_container_parent'];
44 | }
45 | return null;
46 | }
47 |
48 | protected function getServerRequest(): ?ServerRequest
49 | {
50 | return $GLOBALS['TYPO3_REQUEST'] ?? null;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Classes/Integrity/Error/ChildInTranslatedContainerError.php:
--------------------------------------------------------------------------------
1 | childRecord = $childRecord;
41 | $this->containerRecord = $containerRecord;
42 | $this->errorMessage = self::IDENTIFIER . ': translated container child with uid ' . $childRecord['uid'] .
43 | ' (page: ' . $childRecord['pid'] . ' language: ' . $childRecord['sys_language_uid'] . ')' .
44 | ' has translated tx_container_parent ' . $containerRecord['uid']
45 | . ' but should point to default language container record ' . $containerRecord['l18n_parent'];
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function getErrorMessage(): string
52 | {
53 | return $this->errorMessage;
54 | }
55 |
56 | /**
57 | * @return int
58 | */
59 | public function getSeverity(): int
60 | {
61 | return ErrorInterface::ERROR;
62 | }
63 |
64 | /**
65 | * @return array
66 | */
67 | public function getChildRecord(): array
68 | {
69 | return $this->childRecord;
70 | }
71 |
72 | /**
73 | * @return array
74 | */
75 | public function getContainerRecord(): array
76 | {
77 | return $this->containerRecord;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Classes/Integrity/Error/ErrorInterface.php:
--------------------------------------------------------------------------------
1 | childRecord = $childRecord;
35 | $this->errorMessage = self::IDENTIFIER . ': container child with uid ' . $childRecord['uid'] .
36 | ' (page: ' . $childRecord['pid'] . ' language: ' . $childRecord['sys_language_uid'] . ')' .
37 | ' has non existing tx_container_parent ' . $childRecord['tx_container_parent'];
38 | }
39 |
40 | public function getChildRecord(): array
41 | {
42 | return $this->childRecord;
43 | }
44 |
45 | /**
46 | * @return string
47 | */
48 | public function getErrorMessage(): string
49 | {
50 | return $this->errorMessage;
51 | }
52 |
53 | /**
54 | * @return int
55 | */
56 | public function getSeverity(): int
57 | {
58 | return ErrorInterface::ERROR;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Classes/Integrity/Error/UnusedColPosWarning.php:
--------------------------------------------------------------------------------
1 | childRecord = $childRecord;
41 | $this->containerRecord = $containerRecord;
42 | $this->errorMessage = self::IDENTIFIER . ': container child with uid ' . $childRecord['uid'] .
43 | ' (page: ' . $childRecord['pid'] . ' language: ' . $childRecord['sys_language_uid'] . ')' .
44 | ' has invalid colPos ' . $childRecord['colPos']
45 | . ' in container ' . $childRecord['tx_container_parent']
46 | . ' with CType ' . $containerRecord['CType'];
47 | }
48 |
49 | /**
50 | * @return string
51 | */
52 | public function getErrorMessage(): string
53 | {
54 | return $this->errorMessage;
55 | }
56 |
57 | /**
58 | * @return int
59 | */
60 | public function getSeverity(): int
61 | {
62 | return ErrorInterface::WARNING;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Classes/Integrity/Error/WrongL18nParentError.php:
--------------------------------------------------------------------------------
1 | childRecord = $childRecord;
41 | $this->containerRecord = $containerRecord;
42 | $this->errorMessage = self::IDENTIFIER . ': container child with uid ' . $childRecord['uid'] .
43 | ' (page: ' . $childRecord['pid'] . ' language: ' . $childRecord['sys_language_uid'] . ')' .
44 | ' has l18n_parent ' . $childRecord['l18n_parent']
45 | . ' but tx_container_parent ' . $childRecord['tx_container_parent']
46 | . ' has l18n_parent ' . $containerRecord['l18n_parent'] .
47 | ' (page: ' . $containerRecord['pid'] . ' language: ' . $containerRecord['sys_language_uid'] . ')';
48 | }
49 |
50 | /**
51 | * @return string
52 | */
53 | public function getErrorMessage(): string
54 | {
55 | return $this->errorMessage;
56 | }
57 |
58 | /**
59 | * @return int
60 | */
61 | public function getSeverity(): int
62 | {
63 | return ErrorInterface::ERROR;
64 | }
65 |
66 | /**
67 | * @return array
68 | */
69 | public function getChildRecord(): array
70 | {
71 | return $this->childRecord;
72 | }
73 |
74 | /**
75 | * @return array
76 | */
77 | public function getContainerRecord(): array
78 | {
79 | return $this->containerRecord;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Classes/Integrity/Error/WrongLanguageWarning.php:
--------------------------------------------------------------------------------
1 | childRecord = $childRecord;
41 | $this->containerRecord = $containerRecord;
42 | $this->errorMessage = self::IDENTIFIER . ': container child with uid ' . $childRecord['uid'] .
43 | ' (page: ' . $childRecord['pid'] . ' language: ' . $childRecord['sys_language_uid'] . ')' .
44 | ' has sys_language_uid ' . $childRecord['sys_language_uid']
45 | . ' but tx_container_parent ' . $childRecord['tx_container_parent']
46 | . ' has sys_language_uid ' . $containerRecord['sys_language_uid'];
47 | }
48 |
49 | /**
50 | * @return string
51 | */
52 | public function getErrorMessage(): string
53 | {
54 | return $this->errorMessage;
55 | }
56 |
57 | /**
58 | * @return int
59 | */
60 | public function getSeverity(): int
61 | {
62 | return ErrorInterface::WARNING;
63 | }
64 |
65 | /**
66 | * @return array
67 | */
68 | public function getChildRecord(): array
69 | {
70 | return $this->childRecord;
71 | }
72 |
73 | /**
74 | * @return array
75 | */
76 | public function getContainerRecord(): array
77 | {
78 | return $this->containerRecord;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Classes/Integrity/Error/WrongParentError.php:
--------------------------------------------------------------------------------
1 | containerRecord = $containerRecord;
35 | $this->errorMessage = self::IDENTIFIER . ': container uid ' . $containerRecord['uid'] .
36 | ' (page: ' . $containerRecord['pid'] . ' language: ' . $containerRecord['sys_language_uid'] . ')' .
37 | ' has tx_container_parent ' . $containerRecord['tx_container_parent'];
38 | }
39 |
40 | /**
41 | * @return string
42 | */
43 | public function getErrorMessage(): string
44 | {
45 | return $this->errorMessage;
46 | }
47 |
48 | /**
49 | * @return int
50 | */
51 | public function getSeverity(): int
52 | {
53 | return ErrorInterface::ERROR;
54 | }
55 |
56 | /**
57 | * @return array
58 | */
59 | public function getContainerRecord(): array
60 | {
61 | return $this->containerRecord;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Classes/Integrity/Error/WrongPidError.php:
--------------------------------------------------------------------------------
1 | childRecord = $childRecord;
41 | $this->containerRecord = $containerRecord;
42 | $this->errorMessage = self::IDENTIFIER . ': container child with uid ' . $childRecord['uid'] .
43 | ' (page: ' . $childRecord['pid'] . ' language: ' . $childRecord['sys_language_uid'] . ')' .
44 | ' but tx_container_parent ' . $childRecord['tx_container_parent'] .
45 | ' has pid ' . $containerRecord['pid'] . ' language: ' . $containerRecord['sys_language_uid'] . ')';
46 | }
47 |
48 | /**
49 | * @return string
50 | */
51 | public function getErrorMessage(): string
52 | {
53 | return $this->errorMessage;
54 | }
55 |
56 | /**
57 | * @return int
58 | */
59 | public function getSeverity(): int
60 | {
61 | return ErrorInterface::ERROR;
62 | }
63 |
64 | /**
65 | * @return array
66 | */
67 | public function getChildRecord(): array
68 | {
69 | return $this->childRecord;
70 | }
71 |
72 | /**
73 | * @return array
74 | */
75 | public function getContainerRecord(): array
76 | {
77 | return $this->containerRecord;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Classes/Integrity/IntegrityFix.php:
--------------------------------------------------------------------------------
1 | database = $database;
40 | $this->tcaRegistry = $tcaRegistry;
41 | }
42 |
43 | public function deleteChildrenWithWrongPid(WrongPidError $wrongPidError): void
44 | {
45 | $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
46 | $dataHandler->enableLogging = false;
47 | $childRecord = $wrongPidError->getChildRecord();
48 | $cmd = ['tt_content' => [$childRecord['uid'] => ['delete' => 1]]];
49 | $dataHandler->start([], $cmd);
50 | $dataHandler->process_cmdmap();
51 | }
52 |
53 | public function deleteChildrenWithNonExistingParent(NonExistingParentWarning $nonExistingParentWarning): void
54 | {
55 | $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
56 | $dataHandler->enableLogging = false;
57 | $childRecord = $nonExistingParentWarning->getChildRecord();
58 | $cmd = ['tt_content' => [$childRecord['uid'] => ['delete' => 1]]];
59 | $dataHandler->start([], $cmd);
60 | $dataHandler->process_cmdmap();
61 | }
62 |
63 | public function changeContainerParentToDefaultLanguageContainer(ChildInTranslatedContainerError $e): void
64 | {
65 | $translatedContainer = $e->getContainerRecord();
66 | $child = $e->getChildRecord();
67 | $l18nParentOfContainer = $translatedContainer['l18n_parent'];
68 | $queryBuilder = $this->database->getQueryBuilder();
69 | $queryBuilder->update('tt_content')
70 | ->set('tx_container_parent', $l18nParentOfContainer)
71 | ->where(
72 | $queryBuilder->expr()->eq(
73 | 'uid',
74 | $queryBuilder->createNamedParameter($child['uid'], Connection::PARAM_INT)
75 | )
76 | )
77 | ->executeStatement();
78 | }
79 |
80 | /**
81 | * @param WrongL18nParentError[] $errors
82 | */
83 | public function languageMode(array $errors): void
84 | {
85 | $cTypes = $this->tcaRegistry->getRegisteredCTypes();
86 | $defaultContainerRecords = $this->database->getContainerRecords($cTypes);
87 | $containerRecords = [];
88 | // uniq container records
89 | foreach ($errors as $error) {
90 | $containerRecord = $error->getContainerRecord();
91 | $containerRecords[$containerRecord['uid']] = $containerRecord;
92 | }
93 | foreach ($containerRecords as $containerRecord) {
94 | if (!isset($defaultContainerRecords[$containerRecord['l18n_parent']])) {
95 | // should not happen
96 | continue;
97 | }
98 | $defaultContainerRecord = $defaultContainerRecords[$containerRecord['l18n_parent']];
99 | $columns = $this->tcaRegistry->getAvailableColumns($defaultContainerRecord['CType']);
100 | foreach ($columns as $column) {
101 | $childRecords = $this->database->getChildrenByContainerAndColPos($containerRecord['uid'], (int)$column['colPos'], $containerRecord['sys_language_uid']);
102 | // some children may have corrent container parent set
103 | //$childRecords = array_merge($childRecords, $this->database->getChildrenByContainer($defaultContainerRecord['uid'], $containerRecord['sys_language_uid']));
104 | $defaultChildRecords = $this->database->getChildrenByContainerAndColPos($defaultContainerRecord['uid'], (int)$column['colPos'], $defaultContainerRecord['sys_language_uid']);
105 | if (count($childRecords) <= count($defaultChildRecords)) {
106 | // connect children
107 | for ($i = 0; $i < count($childRecords); $i++) {
108 | $childRecord = $childRecords[$i];
109 | $defaultChildRecord = $defaultChildRecords[$i];
110 | $queryBuilder = $this->database->getQueryBuilder();
111 | $stm = $queryBuilder->update('tt_content')
112 | ->set('tx_container_parent', $defaultContainerRecord['uid'])
113 | ->set('l18n_parent', $defaultChildRecord['uid'])
114 | ->where(
115 | $queryBuilder->expr()->eq(
116 | 'uid',
117 | $queryBuilder->createNamedParameter($childRecord['uid'], Connection::PARAM_INT)
118 | )
119 | );
120 | if ((int)$childRecord['l10n_source'] === 0) {
121 | // i think this is always true
122 | $stm->set('l10n_source', $defaultChildRecord['uid']);
123 | }
124 | $stm->executeStatement();
125 | }
126 | }
127 | // disconnect container ?
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Classes/Integrity/SortingInPage.php:
--------------------------------------------------------------------------------
1 | database = $database;
50 | $this->tcaRegistry = $tcaRegistry;
51 | $this->containerFactory = $containerFactory;
52 | $this->containerService = $containerService;
53 | }
54 |
55 | public function run(bool $dryRun = true, bool $enableLogging = false, ?int $pid = null): array
56 | {
57 | $this->unsetContentDefenderConfiguration();
58 | $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
59 | $dataHandler->enableLogging = $enableLogging;
60 | $cTypes = $this->tcaRegistry->getRegisteredCTypes();
61 | $containerUsedColPosArray = [];
62 | foreach ($cTypes as $cType) {
63 | $columns = $this->tcaRegistry->getAvailableColumns($cType);
64 | foreach ($columns as $column) {
65 | $containerUsedColPosArray[] = $column['colPos'];
66 | }
67 | }
68 | $rows = $this->database->getNonContainerChildrenPerColPos($containerUsedColPosArray, $pid);
69 | foreach ($rows as $recordsPerPageAndColPos) {
70 | $prevSorting = 0;
71 | $prevContainer = null;
72 | $prevChild = null;
73 | foreach ($recordsPerPageAndColPos as $record) {
74 | if (in_array($record['CType'], $cTypes, true)) {
75 | $container = $this->containerFactory->buildContainer($record['uid']);
76 | $lastChild = $this->containerService->getAfterContainerRecord($container);
77 | $sorting = $lastChild['sorting'];
78 |
79 | if ($record['uid'] !== $lastChild['uid']) {
80 | if ($prevChild === null || $prevContainer === null) {
81 | $prevChild = $lastChild;
82 | $prevContainer = $container;
83 | $prevSorting = $sorting;
84 | continue;
85 | }
86 | $containerSorting = $container->getContainerRecord()['sorting'];
87 | if ($containerSorting < $prevSorting) {
88 | $this->errors[] = 'record ' . $record['uid'] . ' (' . $record['sorting'] . ')' .
89 | ' on page ' . $record['pid'] .
90 | ' should be sorted after last child ' . $prevChild['uid'] . ' (' . $prevChild['sorting'] . ')' .
91 | ' of container ' . $prevContainer->getUid() . ' (' . $containerSorting . ')';
92 | $this->moveRecordAfter((int)$record['uid'], $prevContainer->getUid(), $dryRun, $dataHandler);
93 | }
94 | $prevContainer = $container;
95 | $prevChild = $lastChild;
96 | }
97 | } else {
98 | $sorting = $record['sorting'];
99 | }
100 | $prevSorting = $sorting;
101 | }
102 | }
103 | return $this->errors;
104 | }
105 |
106 | protected function moveRecordAfter(int $recordUid, int $moveUid, bool $dryRun, DataHandler $dataHandler): void
107 | {
108 | if ($dryRun === false) {
109 | $cmdmap = [
110 | 'tt_content' => [
111 | $recordUid => [
112 | 'move' => -1 * $moveUid,
113 | ],
114 | ],
115 | ];
116 | $dataHandler->start([], $cmdmap);
117 | $dataHandler->process_datamap();
118 | $dataHandler->process_cmdmap();
119 | }
120 | }
121 |
122 | protected function unsetContentDefenderConfiguration(): void
123 | {
124 | // content_defender uses FormDataCompiler which expects a ServerRequest
125 | if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['content_defender'])) {
126 | unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['content_defender']);
127 | }
128 | if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['content_defender'])) {
129 | unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['content_defender']);
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Classes/Listener/BootCompleted.php:
--------------------------------------------------------------------------------
1 | tcaRegistry = $tcaRegistry;
28 | }
29 |
30 | public function __invoke(BootCompletedEvent $event): void
31 | {
32 | $this->tcaRegistry->registerIcons();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Classes/Listener/ContentUsedOnPage.php:
--------------------------------------------------------------------------------
1 | containerFactory = $containerFactory;
35 | $this->tcaRegistry = $tcaRegistry;
36 | }
37 |
38 | public function __invoke(IsContentUsedOnPageLayoutEvent $event): void
39 | {
40 | $record = $event->getRecord();
41 | if ($record['tx_container_parent'] > 0) {
42 | try {
43 | $container = $this->containerFactory->buildContainer((int)$record['tx_container_parent']);
44 | $columns = $this->tcaRegistry->getAvailableColumns($container->getCType());
45 | foreach ($columns as $column) {
46 | if ($column['colPos'] === (int)$record['colPos']) {
47 | if ($record['sys_language_uid'] > 0 && $container->isConnectedMode()) {
48 | $used = $container->hasChildInColPos((int)$record['colPos'], (int)$record['l18n_parent']);
49 | $event->setUsed($used);
50 | return;
51 | }
52 | $used = $container->hasChildInColPos((int)$record['colPos'], (int)$record['uid']);
53 | $event->setUsed($used);
54 | return;
55 | }
56 | }
57 | } catch (Exception $e) {
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Classes/Listener/LegacyPageTsConfig.php:
--------------------------------------------------------------------------------
1 | tcaRegistry = $tcaRegistry;
30 | }
31 |
32 | public function __invoke(ModifyLoadedPageTsConfigEvent $event): void
33 | {
34 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() !== 11) {
35 | return;
36 | }
37 | $tsConfig = $event->getTsConfig();
38 | $tsConfig['default'] = trim($this->tcaRegistry->getPageTsString() . "\n" . ($tsConfig['default'] ?? ''));
39 | $event->setTsConfig($tsConfig);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Classes/Listener/ModifyNewContentElementWizardItems.php:
--------------------------------------------------------------------------------
1 | getParentIdFromRequest();
25 | if ($parent !== null) {
26 | $typo3Version = (GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion();
27 | $wizardItems = $event->getWizardItems();
28 | foreach ($wizardItems as $key => $wizardItem) {
29 | if ($typo3Version < 13) {
30 | $wizardItems[$key]['tt_content_defValues']['tx_container_parent'] = $parent;
31 | if (!isset($wizardItems[$key]['params'])) {
32 | $wizardItems[$key]['params'] = '?defVals[tt_content][tx_container_parent]=' . $parent;
33 | } else {
34 | $wizardItems[$key]['params'] .= '&defVals[tt_content][tx_container_parent]=' . $parent;
35 | }
36 | } else {
37 | $wizardItems[$key]['defaultValues']['tx_container_parent'] = $parent;
38 | }
39 | }
40 | $event->setWizardItems($wizardItems);
41 | }
42 | }
43 |
44 | protected function getParentIdFromRequest(): ?int
45 | {
46 | $request = $this->getServerRequest();
47 | if ($request === null) {
48 | return null;
49 | }
50 | $queryParams = $request->getQueryParams();
51 | if (isset($queryParams['tx_container_parent']) && (int)$queryParams['tx_container_parent'] > 0) {
52 | return (int)$queryParams['tx_container_parent'];
53 | }
54 | return null;
55 | }
56 |
57 | protected function getServerRequest(): ?ServerRequest
58 | {
59 | return $GLOBALS['TYPO3_REQUEST'] ?? null;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Classes/Listener/PageContentPreviewRendering.php:
--------------------------------------------------------------------------------
1 | gridRenderer = $gridRenderer;
27 | $this->tcaRegistry = $tcaRegistry;
28 | }
29 |
30 | public function __invoke(PageContentPreviewRenderingEvent $event): void
31 | {
32 | if ($event->getTable() !== 'tt_content') {
33 | return;
34 | }
35 |
36 | $record = $event->getRecord();
37 | if (!$this->tcaRegistry->isContainerElement( (string) $record['CType'])) {
38 | return;
39 | }
40 | $record['tx_container_grid'] = $this->gridRenderer->renderGrid($record, $event->getPageLayoutContext());
41 | $event->setRecord($record);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Classes/Listener/PageTsConfig.php:
--------------------------------------------------------------------------------
1 | tcaRegistry = $tcaRegistry;
30 | }
31 |
32 | public function __invoke(ModifyLoadedPageTsConfigEvent $event): void
33 | {
34 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() < 12) {
35 | return;
36 | }
37 | $tsConfig = $event->getTsConfig();
38 | $tsConfig = array_merge(['pagesTsConfig-package-container' => $this->tcaRegistry->getPageTsString()], $tsConfig);
39 | $event->setTsConfig($tsConfig);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Classes/Listener/RecordSummaryForLocalization.php:
--------------------------------------------------------------------------------
1 | recordLocalizeSummaryModifier = $recordLocalizeSummaryModifier;
28 | }
29 |
30 | public function __invoke(AfterRecordSummaryForLocalizationEvent $event): void
31 | {
32 | $records = $event->getRecords();
33 | $columns = $event->getColumns();
34 | $records = $this->recordLocalizeSummaryModifier->filterRecords($records);
35 | $columns = $this->recordLocalizeSummaryModifier->rebuildColumns($columns);
36 | $event->setColumns($columns);
37 | $event->setRecords($records);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Classes/Service/RecordLocalizeSummaryModifier.php:
--------------------------------------------------------------------------------
1 | containerRegistry = $containerRegistry;
33 | }
34 |
35 | public function rebuildPayload(array $payload): array
36 | {
37 | return [
38 | 'records' => $this->filterRecords($payload['records']),
39 | 'columns' => $this->rebuildColumns($payload['columns']),
40 | ];
41 | }
42 |
43 | public function filterRecords(array $recordsPerColPos): array
44 | {
45 | // cannot be done by event in v10
46 | $uids = [];
47 | foreach ($recordsPerColPos as $colPos => $records) {
48 | foreach ($records as $record) {
49 | $uids[] = $record['uid'];
50 | }
51 | }
52 | if (empty($uids)) {
53 | return $recordsPerColPos;
54 | }
55 | $containerUids = $this->getContainerUids($uids);
56 | if (empty($containerUids)) {
57 | return $recordsPerColPos;
58 | }
59 | $containerChildren = $this->getContainerChildren($uids);
60 | if (empty($containerChildren)) {
61 | return $recordsPerColPos;
62 | }
63 | // we have both: container to translate and container children to translate
64 | // unset all records in container to translate
65 | $filtered = [];
66 | foreach ($recordsPerColPos as $colPos => $records) {
67 | $filteredRecords = [];
68 | foreach ($records as $record) {
69 | if (empty($containerChildren[$record['uid']])) {
70 | $filteredRecords[] = $record;
71 | } else {
72 | $fullRecord = $containerChildren[$record['uid']];
73 | if (!in_array($fullRecord['tx_container_parent'], $containerUids, true)) {
74 | $filteredRecords[] = $record;
75 | }
76 | }
77 | }
78 | if (!empty($filteredRecords)) {
79 | $filtered[$colPos] = $filteredRecords;
80 | }
81 | }
82 | return $filtered;
83 | }
84 |
85 | public function rebuildColumns(array $columns): array
86 | {
87 | // this can be done with AfterPageColumnsSelectedForLocalizationEvent event in v10
88 | $containerColumns = $this->containerRegistry->getAllAvailableColumns();
89 | foreach ($containerColumns as $containerColumn) {
90 | $columns = [
91 | 'columns' => array_replace([$containerColumn['colPos'] => 'Container Children (' . $containerColumn['colPos'] . ')'], $columns['columns']),
92 | 'columnList' => array_values(array_unique(array_merge([$containerColumn['colPos']], $columns['columnList']))),
93 | ];
94 | }
95 | return $columns;
96 | }
97 |
98 | // database helper
99 |
100 | protected function getContainerUids(array $uids): array
101 | {
102 | $containerCTypes = $this->containerRegistry->getRegisteredCTypes();
103 | if (empty($containerCTypes)) {
104 | return [];
105 | }
106 | $queryBuilder = $this->getQueryBuilder();
107 | $rows = $queryBuilder->select('uid', 'l18n_parent')
108 | ->from('tt_content')
109 | ->where(
110 | $queryBuilder->expr()->in(
111 | 'uid',
112 | $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)
113 | ),
114 | $queryBuilder->expr()->in(
115 | 'CType',
116 | $queryBuilder->createNamedParameter($containerCTypes, Connection::PARAM_STR_ARRAY)
117 | )
118 | )
119 | ->executeQuery()
120 | ->fetchAllAssociative();
121 | $containerUids = [];
122 | foreach ($rows as $row) {
123 | $containerUids[] = $row['uid'];
124 | if ($row['l18n_parent'] > 0) {
125 | $containerUids[] = $row['l18n_parent'];
126 | }
127 | }
128 | return $containerUids;
129 | }
130 |
131 | protected function getContainerChildren(array $uids): array
132 | {
133 | $containerChildren = [];
134 | $queryBuilder = $this->getQueryBuilder();
135 | $rows = $queryBuilder->select('*')
136 | ->from('tt_content')
137 | ->where(
138 | $queryBuilder->expr()->in(
139 | 'uid',
140 | $queryBuilder->createNamedParameter($uids, Connection::PARAM_INT_ARRAY)
141 | ),
142 | $queryBuilder->expr()->neq(
143 | 'tx_container_parent',
144 | $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)
145 | )
146 | )
147 | ->executeQuery()
148 | ->fetchAllAssociative();
149 | foreach ($rows as $row) {
150 | $containerChildren[$row['uid']] = $row;
151 | }
152 | return $containerChildren;
153 | }
154 |
155 | protected function getQueryBuilder(): QueryBuilder
156 | {
157 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
158 | $queryBuilder->getRestrictions()
159 | ->removeAll()
160 | ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
161 | return $queryBuilder;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/Classes/Tca/ItemProcFunc.php:
--------------------------------------------------------------------------------
1 | containerFactory = $containerFactory;
39 | $this->tcaRegistry = $tcaRegistry;
40 | $this->backendLayoutView = $backendLayoutView;
41 | }
42 |
43 | /**
44 | * Gets colPos items to be shown in the forms engine.
45 | * This method is called as "itemsProcFunc" with the accordant context
46 | * for tt_content.colPos.
47 | */
48 | public function colPos(array &$parameters): void
49 | {
50 | $row = $parameters['row'];
51 | if (($row['tx_container_parent'] ?? 0) > 0) {
52 | try {
53 | $container = $this->containerFactory->buildContainer((int)$row['tx_container_parent']);
54 | $cType = $container->getCType();
55 | $grid = $this->tcaRegistry->getGrid($cType);
56 | if (is_array($grid)) {
57 | $items = [];
58 | foreach ($grid as $rows) {
59 | foreach ($rows as $column) {
60 | // only one item is show, so it is not changeable
61 | if ((int)$column['colPos'] === (int)$row['colPos']) {
62 | $items[] = [
63 | $column['name'],
64 | $column['colPos'],
65 | ];
66 | }
67 | }
68 | }
69 | $parameters['items'] = $items;
70 | return;
71 | }
72 | } catch (Exception $e) {
73 | }
74 | }
75 |
76 | $this->backendLayoutView->colPosListItemProcFunc($parameters);
77 | }
78 |
79 | public function txContainerParent(array &$parameters): void
80 | {
81 | $row = $parameters['row'];
82 | $items = [];
83 | if (($row['tx_container_parent'] ?? 0) > 0) {
84 | try {
85 | $container = $this->containerFactory->buildContainer((int)$row['tx_container_parent']);
86 | $cType = $container->getCType();
87 | $items[] = [
88 | $cType,
89 | $row['tx_container_parent'],
90 | ];
91 | } catch (Exception $e) {
92 | $items[] = [
93 | '-',
94 | 0,
95 | ];
96 | }
97 | } else {
98 | $items[] = [
99 | '-',
100 | 0,
101 | ];
102 | }
103 | $parameters['items'] = $items;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Classes/Updates/ContainerDeleteChildrenWithWrongPid.php:
--------------------------------------------------------------------------------
1 | integrity = $integrity;
49 | $this->integrityFix = $integrityFix;
50 | }
51 |
52 | public function getIdentifier(): string
53 | {
54 | return self::IDENTIFIER;
55 | }
56 |
57 | /**
58 | * @return string Title of this updater
59 | */
60 | public function getTitle(): string
61 | {
62 | return 'EXT:container: Delete "container" children with wrong pid';
63 | }
64 |
65 | /**
66 | * @return string Longer description of this updater
67 | */
68 | public function getDescription(): string
69 | {
70 | return 'if you update from Version < 1.3 you may have children with wrong pid and they was never shown in BE/FE';
71 | }
72 |
73 | public function updateNecessary(): bool
74 | {
75 | $res = $this->integrity->run();
76 | foreach ($res['errors'] as $error) {
77 | if ($error instanceof WrongPidError) {
78 | return true;
79 | }
80 | }
81 | return false;
82 | }
83 |
84 | public function executeUpdate(): bool
85 | {
86 | if (Environment::isCli() === false) {
87 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() > 11) {
88 | $requestFactory = GeneralUtility::makeInstance(ServerRequestFactory::class);
89 | $request = $requestFactory::fromGlobals();
90 | $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request));
91 | Bootstrap::initializeBackendUser(BackendUserAuthentication::class, $request);
92 | } else {
93 | Bootstrap::initializeBackendUser();
94 | }
95 | Bootstrap::initializeBackendAuthentication();
96 | $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences($GLOBALS['BE_USER']);
97 | }
98 | $res = $this->integrity->run();
99 | foreach ($res['errors'] as $error) {
100 | if ($error instanceof WrongPidError) {
101 | $this->integrityFix->deleteChildrenWithWrongPid($error);
102 | }
103 | }
104 | return true;
105 | }
106 |
107 | public function getPrerequisites(): array
108 | {
109 | return [
110 | DatabaseUpdatedPrerequisite::class,
111 | ];
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Classes/Updates/ContainerMigrateSorting.php:
--------------------------------------------------------------------------------
1 | sorting = $sorting;
42 | }
43 |
44 | public function getIdentifier(): string
45 | {
46 | return self::IDENTIFIER;
47 | }
48 |
49 | /**
50 | * @return string Title of this updater
51 | */
52 | public function getTitle(): string
53 | {
54 | return 'EXT:container: Migrate "container" sorting';
55 | }
56 |
57 | /**
58 | * @return string Longer description of this updater
59 | */
60 | public function getDescription(): string
61 | {
62 | return 'change sorting of container children (must be run multiple times for nested containers)';
63 | }
64 |
65 | public function updateNecessary(): bool
66 | {
67 | $errors = $this->sorting->run(true);
68 | return !empty($errors);
69 | }
70 |
71 | public function executeUpdate(): bool
72 | {
73 | if (Environment::isCli() === false) {
74 | if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() > 11) {
75 | $requestFactory = GeneralUtility::makeInstance(ServerRequestFactory::class);
76 | $request = $requestFactory::fromGlobals();
77 | $request = $request->withAttribute('normalizedParams', NormalizedParams::createFromRequest($request));
78 | Bootstrap::initializeBackendUser(BackendUserAuthentication::class, $request);
79 | } else {
80 | Bootstrap::initializeBackendUser();
81 | }
82 | Bootstrap::initializeBackendAuthentication();
83 | $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageServiceFactory::class)->createFromUserPreferences($GLOBALS['BE_USER']);
84 | }
85 | $this->sorting->run(false);
86 | return true;
87 | }
88 |
89 | public function getPrerequisites(): array
90 | {
91 | return [
92 | DatabaseUpdatedPrerequisite::class,
93 | ];
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Classes/Xclasses/LocalizationController.php:
--------------------------------------------------------------------------------
1 | recordLocalizeSummaryModifier = $recordLocalizeSummaryModifier ?? GeneralUtility::makeInstance(RecordLocalizeSummaryModifier::class);
32 | }
33 |
34 | public function getRecordLocalizeSummary(ServerRequestInterface $request): ResponseInterface
35 | {
36 | $response = parent::getRecordLocalizeSummary($request);
37 | $payload = json_decode($response->getBody()->getContents(), true);
38 | $payload = $this->recordLocalizeSummaryModifier->rebuildPayload($payload);
39 | return new JsonResponse($payload);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Configuration/Icons.php:
--------------------------------------------------------------------------------
1 | [
5 | 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
6 | 'source' => 'EXT:container/Resources/Public/Icons/container-1col.svg',
7 | ],
8 | 'container-2col' => [
9 | 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
10 | 'source' => 'EXT:container/Resources/Public/Icons/container-2col.svg',
11 | ],
12 | 'container-2col-left' => [
13 | 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
14 | 'source' => 'EXT:container/Resources/Public/Icons/container-2col-left.svg',
15 | ],
16 | 'container-2col-right' => [
17 | 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
18 | 'source' => 'EXT:container/Resources/Public/Icons/container-2col-right.svg',
19 | ],
20 | 'container-3col' => [
21 | 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
22 | 'source' => 'EXT:container/Resources/Public/Icons/container-3col.svg',
23 | ],
24 | 'container-4col' => [
25 | 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
26 | 'source' => 'EXT:container/Resources/Public/Icons/container-4col.svg',
27 | ],
28 | ];
29 |
--------------------------------------------------------------------------------
/Configuration/JavaScriptModules.php:
--------------------------------------------------------------------------------
1 | getMajorVersion() < 13) {
4 | return [
5 | 'imports' => [
6 | '@typo3/backend/layout-module/drag-drop.js' => 'EXT:container/Resources/Public/JavaScript/Overrides12/drag-drop.js',
7 | '@typo3/backend/layout-module/paste.js' => 'EXT:container/Resources/Public/JavaScript/Overrides12/paste.js',
8 | ],
9 | ];
10 | }
11 | return [
12 | 'imports' => [
13 | '@typo3/backend/layout-module/drag-drop.js' => 'EXT:container/Resources/Public/JavaScript/Overrides/drag-drop.js',
14 | '@typo3/backend/layout-module/paste.js' => 'EXT:container/Resources/Public/JavaScript/Overrides/paste.js',
15 | ],
16 | ];
17 |
--------------------------------------------------------------------------------
/Configuration/Services.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | _defaults:
3 | autowire: true
4 | autoconfigure: true
5 | public: false
6 |
7 | B13\Container\:
8 | resource: '../Classes/*'
9 | exclude:
10 | - '../Classes/Domain/Model/*'
11 | - '../Classes/Backend/Grid/*'
12 | - '../Classes/Integrity/Error/*'
13 | - '../Classes/Tca/ContainerConfiguration.php'
14 | - '../Classes/**/Exception.php'
15 |
16 | B13\Container\Tca\Registry:
17 | public: true
18 | B13\Container\Backend\Preview\GridRenderer:
19 | arguments:
20 | $runtimeCache: '@cache.runtime'
21 | B13\Container\Backend\Preview\ContainerPreviewRenderer:
22 | public: true
23 | arguments:
24 | $runtimeCache: '@cache.runtime'
25 | B13\Container\Hooks\UsedRecords:
26 | public: true
27 | B13\Container\Hooks\Datahandler\CommandMapAfterFinishHook:
28 | public: true
29 | B13\Container\Hooks\Datahandler\CommandMapBeforeStartHook:
30 | public: true
31 | B13\Container\Hooks\Datahandler\CommandMapPostProcessingHook:
32 | public: true
33 | B13\Container\Hooks\Datahandler\DatamapBeforeStartHook:
34 | public: true
35 | B13\Container\Hooks\Datahandler\DatamapPreProcessFieldArrayHook:
36 | public: true
37 | B13\Container\Hooks\Datahandler\DeleteHook:
38 | public: true
39 | B13\Container\ContentDefender\Hooks\ColumnConfigurationManipulationHook:
40 | public: true
41 | B13\Container\DataProcessing\ContainerProcessor:
42 | public: true
43 | tags:
44 | - { name: 'data.processor', identifier: 'container' }
45 | B13\Container\Updates\ContainerMigrateSorting:
46 | public: true
47 | B13\Container\Updates\ContainerDeleteChildrenWithWrongPid:
48 | public: true
49 | B13\Container\Tca\ItemProcFunc:
50 | public: true
51 |
52 | B13\Container\Listener\RecordSummaryForLocalization:
53 | tags:
54 | - name: event.listener
55 | identifier: 'tx-container-record-summary-for-localization'
56 | B13\Container\Listener\ContentUsedOnPage:
57 | tags:
58 | - name: event.listener
59 | identifier: 'tx-container-content-used-on-page'
60 | B13\Container\Listener\ModifyNewContentElementWizardItems:
61 | tags:
62 | - name: event.listener
63 | identifier: 'tx-container-new-content-element-wizard'
64 | B13\Container\Listener\LegacyPageTsConfig:
65 | tags:
66 | - name: event.listener
67 | identifier: 'tx-container-legacy-page-ts-config'
68 | B13\Container\Listener\PageTsConfig:
69 | tags:
70 | - name: event.listener
71 | identifier: 'tx-container-page-ts-config'
72 | B13\Container\Listener\BootCompleted:
73 | tags:
74 | - name: event.listener
75 | identifier: 'tx-container-boot-completed'
76 | B13\Container\Listener\PageContentPreviewRendering:
77 | tags:
78 | - name: event.listener
79 | identifier: 'tx-container-page-content-preview-rendering'
80 | before: 'typo3-backend/fluid-preview/content'
81 | B13\Container\Command\FixLanguageModeCommand:
82 | tags:
83 | - name: 'console.command'
84 | command: 'container:fixLanguageMode'
85 | schedulable: false
86 | description: connect children of connected container if possible, else disconnect container
87 | B13\Container\Command\FixContainerParentForConnectedModeCommand:
88 | tags:
89 | - name: 'console.command'
90 | command: 'container:fixContainerParentForConnectedMode'
91 | schedulable: false
92 | description: tx_container_parent of children in connected mode should point to default language container
93 | B13\Container\Command\DeleteChildrenWithWrongPidCommand:
94 | tags:
95 | - name: 'console.command'
96 | command: 'container:deleteChildrenWithWrongPid'
97 | schedulable: false
98 | description: delete all child records with pid neq containers pid
99 | B13\Container\Command\DeleteChildrenWithNonExistingParentCommand:
100 | tags:
101 | - name: 'console.command'
102 | command: 'container:deleteChildrenWithNonExistingParent'
103 | schedulable: false
104 | description: delete all child records with a non existing parent record (they are displayed as unsued)
105 | B13\Container\Command\IntegrityCommand:
106 | tags:
107 | - name: 'console.command'
108 | command: 'container:integrity'
109 | schedulable: true
110 | description: Checks integrity of containers
111 | B13\Container\Command\SortingCommand:
112 | tags:
113 | - name: 'console.command'
114 | command: 'container:sorting'
115 | schedulable: false
116 | description: Resort Content Elements
117 | B13\Container\Command\SortingInPageCommand:
118 | tags:
119 | - name: 'console.command'
120 | command: 'container:sorting-in-page'
121 | schedulable: false
122 | description: Resort Content Elements
123 |
--------------------------------------------------------------------------------
/Configuration/TCA/Overrides/tt_content.php:
--------------------------------------------------------------------------------
1 | [
8 | 'label' => 'LLL:EXT:container/Resources/Private/Language/locallang.xlf:container',
9 | 'config' => [
10 | 'default' => 0,
11 | 'type' => 'select',
12 | 'foreign_table' => 'tt_content',
13 | // if not set, all content elements are already loaded before itemsProcFunc is called
14 | 'foreign_table_where' => ' AND 1=2',
15 | 'itemsProcFunc' => \B13\Container\Tca\ItemProcFunc::class . '->txContainerParent',
16 | 'renderType' => 'selectSingle',
17 | ],
18 | ],
19 | ];
20 |
21 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns(
22 | 'tt_content',
23 | $additionalColumns
24 | );
25 |
26 | \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addFieldsToPalette(
27 | 'tt_content',
28 | 'general',
29 | 'tx_container_parent'
30 | );
31 |
32 | $GLOBALS['TCA']['tt_content']['columns']['CType']['config']['itemGroups']['container'] = 'LLL:EXT:container/Resources/Private/Language/locallang.xlf:container';
33 |
34 | $GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['itemsProcFunc'] = \B13\Container\Tca\ItemProcFunc::class . '->colPos';
35 |
36 | // copyAfterDuplFields colPos,sys_language_uid
37 | // useColumnsForDefaultValues colPos,sys_language_uid,CType
38 | // new element
39 | $GLOBALS['TCA']['tt_content']['ctrl']['useColumnsForDefaultValues'] .= ',tx_container_parent';
40 | });
41 |
--------------------------------------------------------------------------------
/Resources/Private/Language/locallang.xlf:
--------------------------------------------------------------------------------
1 |
2 |