diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php index 802b198bf..dca57dd18 100644 --- a/.phpstorm.meta.php +++ b/.phpstorm.meta.php @@ -42,9 +42,9 @@ expectedArguments( \In2code\In2publishCore\Component\RemoteProcedureCall\Envelope::__construct(), 0, - \In2code\In2publishCore\Component\RemoteProcedureCall\EnvelopeDispatcher::CMD_FOLDER_EXISTS, + \In2code\In2publishCore\Component\RemoteProcedureCall\EnvelopeDispatcher::CMD_GET_FOLDER_INFO, + \In2code\In2publishCore\Component\RemoteProcedureCall\EnvelopeDispatcher::CMD_GET_FILE_INFO, \In2code\In2publishCore\Component\RemoteProcedureCall\EnvelopeDispatcher::CMD_FILE_EXISTS, - \In2code\In2publishCore\Component\RemoteProcedureCall\EnvelopeDispatcher::CMD_LIST_FOLDER_CONTENTS, ); expectedArguments( \In2code\In2publishCore\Component\ConfigContainer\Builder::addNode(), diff --git a/.project/qa/phpmd.xml b/.project/qa/phpmd.xml index 738586b89..f430a662f 100644 --- a/.project/qa/phpmd.xml +++ b/.project/qa/phpmd.xml @@ -58,7 +58,6 @@ \In2code\In2publishCore\Utility\LogUtility, \In2code\In2publishCore\Utility\StorageDriverExtractor, \In2code\In2publishCore\Utility\UriUtility, - \Spyc diff --git a/.project/qa/psalm.xml b/.project/qa/psalm.xml index 0099abe59..ee5956fec 100644 --- a/.project/qa/psalm.xml +++ b/.project/qa/psalm.xml @@ -26,7 +26,6 @@ - diff --git a/CHANGELOG.md b/CHANGELOG.md index 590e46483..42dab0d2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,122 @@ # In2publish Core Change Log +12.3.0: + +- [DOCS] Update Changelog.md +- [META] Set the EM conf version number to 12.3.0 +- [CLEANUP] Remove redundant PHP version constraints +- [DOCS] Add known issue about missing file links +- [DOCS] Remove section about Typoscript paths for templates in UPGRADING.md +- [DOCS] Add upgrading information for version 12.3.0 +- [BUGFIX] Revert making file publisher reversible, they aren't! +- [TEST] Fix FileSystemPublisherTests +- [DOCS] Add instruction to quote asterisks in YAML for admins +- [BUGFIX] Show reasons why files are not publishable in the filelist +- [FEATURE] Adds another test return to get more specific testresult +- [BUGFIX] Increase max depth when dumping compatible TCA +- [FEATURE] Add LinkProcessor for TYPO3 v12 +- [DOCS] Add admin changelog for YAML +- [BUGFIX] Add PreProcessor for TCA type file +- [BUGFIX] Corrects Loading of Middleware +- [DOCS] Adjust Feature List +- [BUGFIX] Restore the confirmation modal for file and folder publishing +- [BUGFIX] Ignore _file.publicUrl because it will always be different and is a computed property +- [BUGFIX] Fix ignoring ctrl.versioningWS +- [BUGFIX] Try to build absolute URLs to files for the preview buttons in the filelist module +- [BUGFIX] Redirect to the previous page after publishing, not the published page +- [BUGFIX] Restore context menu publishing for TYPO3 v12 +- [BUGFIX] Validate the resolver cache +- [BUGFIX] Query sys_file_reference.table_local only in TYPO3 v11 as it was removed +- [BUGFIX] Use middleware to inject the loading overlay, split JS into specific modules +- [BUGFIX] Allow passing nodes directly to JS overlay and modal functions +- [BUGFIX] Use Connection::PARAM_STR_ARRAY instead of ArrayParameterType +- [REFACTOR] Extract $GLOBALS access to method +- [BUGFIX] Overwrite toString for PageTreeRootRecord to return the "sitename" +- [BUGFIX] Make PublisherService::publishRecordTree internal to force everyone to use the PublishingContext +- [BUGFIX] Correctly exclude folders and files for the recursiveState of Folder Records +- [BUGFIX] Resolve storage for StorageRootFolderRecords +- [BUGFIX] Access correct property to set the name attribute of folders +- [REFACTOR] Rename ResolverService::getResolversForTable to getResolversForClassification +- [BUGFIX] Introduce a special class for folders which are file storage roots and display the correct icon +- [FEATURE] Add some Injection Traits +- [BUGFIX] Close modals in TYPO3 v12 via new API +- [CLEANUP] Remove unused imports from FileController +- [BUGFIX] Fix multiple errors that occur when calling publishRecordTree more than once +- [FEATURE] Change Order of Modules in Publish Tools +- [CODESTYLE] Add Annotations for Exceptions +- [BUGFIX] Fix Database Compare Table View +- [CLEANUP] Remove Logs Integration +- [BUGFIX] Correct Caption of english Labels +- [FEATURE] Moved all Labels from Publish Tools Module to locallang_mod4.xlf +- [WIP] Changed all Templates of Publish Tools Module +- Revert "[DOC] Add documentation for changed ext_typoscript_template suffix" +- [BUGFIX] Fetch folder records with demand/resolver structure +- [DOC] Add documentation for changed ext_typoscript_template suffix +- [BUGFIX] Corrects styling of redirects module +- [BUGFIX] Add Padding at the Top of the module +- [BUGFIX] Jumpmenu Label is now rendered inline +- [BUGFIX] Change order of flashmessage container +- [REFACTOR] Remove outdated overlay div +- [REFACTOR] Replace version_compare calls with TYPO3_V11 constant +- [BUGFIX] Define (namespaced) constants for TYPO3 version for easier up/down-compatibility +- [BUGFIX] Tighten Colors between v11 & v12 +- [BUGFIX] Show colors of badges in file module again +- [REFACTOR] Removes deprecated QueryBuilder methods +- [BUGFIX] Removes deprecated getSchemamanager call +- [BUGFIX] Removes check for directory typo3conf in TYPO3v12 +- [REFACTOR] Use be.infobox viewhelper instead of own markup +- [REFACTOR] Simplifies the generation of an controller alias +- [FEATURE] Changes AdminButton to show primary styling correct +- [BUGFIX] Remove deprecations from RegistryController +- [BUGFIX] Remove deprecations from LetterBox +- [REFACTOR] Remove deprecations fomr LogsExporter +- [REFACTOR] Remove deprecated execute from GarbageCollectorTest +- [BUGFIX] Return ResponseInterface in publishFile and publishFolder action +- [REFACTOR] Removes unnecessary Event & Middleware +- [BUGFIX] Add correct hrefs to Buttons on Publish Tools +- [FEATURE] Register Publish Tools Menu in Modules.php +- [REFACTOR] Module Registration for Publish Tools Module refactored +- [REFACTOR] Changes backend module registration for m1, m3, m5 +- [BUGFIX] Make the PageTsProvider a Singleton to unlock it globally +- [BUGFIX] Return the RedirectResponse in TYPO3 v12 +- [BUGFIX] Rename ext_typoscript_setup suffix to typoscript +- [CLEANUP] Remove superfluous empty lines from ext_tables.php +- [BUGFIX] Enable autowiring of the dynamic PageTypeService +- [BUGFIX] Allow deserialization of TYPO3 v12 Site objects +- [BUGFIX] Create a TYPO3 version aware service to replace TcaService::getTablesAllowedOnPage +- [BUGFIX] Overwrite callActionMethod instead of initializeView to prevent version issues +- [BUGFIX] Implement TYPO3 version specific code to translate the label of the Publish Overview Module shortcut button +- [BUGFIX] Use withRequest to alter immutable request objects +- [BUGFIX] Use the objects view property instead of initializeView arguments +- [REFACTOR] Changes Icon from Tools Module to IconFactory +- [REFACTOR] Change compare of version +- [BUGFIX] Removes trailing slash in Module Configuration +- [REFACTOR] Changes backend module registration for m1, m3, m5 +- [BUGFIX] Do not register the BackendRouteInitialization XCLASS in TYPO3 v12 +- [BUGFIX] Replace deprecated EventManager::getListeners with getAllListeners +- [TASK] Remove deprecated TYPO3 constants +- [BUGFIX] Set correct narrowed return type hint for ConnectionFactory +- [BUGFIX] Handle constructor differences in PublishItemProvider between t3v11 and t3v12 +- [FEATURE] Cache the TcaPreProcessing result +- [CLEANUP] Remove unused import/empty line +- [BUGFIX] Reduce resolver meta info to required keys class and args +- [BUGFIX] Replace Spyc with symfony/yaml +- [TASK] Update composer requirements +- [TASK] Add new branch aliases for develop branch +- [TASK] Allow PHP8.1 as requirement and remove outdated branch-aliases +- [BUGFIX] Inherit the base Exception from in2publish_core, not in2publish +- [BUGFIX] Show debugged queries in separate tab for each request +- [BUGFIX] Show the sum of query duration when debugging queries +- [BUGFIX] Sort grouped queries by amount of calls +- [REFACTOR] Use the CachedRuntimeCache instead of the custom implementation in ForeignSiteFinder +- [BUGFIX] Use runtime cache to prevent multiple cache hits +- [BUGFIX] Increment logged SQL queries statically +- [TESTS] Update unit tests for PublishFileInstructions +- [BUGFIX] Disable the function bar if publishing is not available +- [BUGFIX] Ignore table tx_in2publishcore_filepublisher_instruction by default +- [BUGFIX] Require table tx_in2publishcore_filepublisher_instruction instead of _task +- [DOCS] Update changelog + 12.2.0: - [META] Set the EM conf version number to 12.2.0 diff --git a/Classes/Backend/Button/ModuleShortcutButton.php b/Classes/Backend/Button/ModuleShortcutButton.php index ee28ee610..d4a8d01ca 100644 --- a/Classes/Backend/Button/ModuleShortcutButton.php +++ b/Classes/Backend/Button/ModuleShortcutButton.php @@ -30,6 +30,7 @@ */ use Psr\Http\Message\ServerRequestInterface; +use TYPO3\CMS\Backend\Module\ExtbaseModule; use TYPO3\CMS\Backend\Template\Components\Buttons\Action\ShortcutButton; use TYPO3\CMS\Core\Routing\Route; use TYPO3\CMS\Extbase\Mvc\ExtbaseRequestParameters; @@ -37,6 +38,8 @@ use function ucfirst; +use const In2code\In2publishCore\TYPO3_V11; + class ModuleShortcutButton extends ShortcutButton { public function setRequest(ServerRequestInterface $request): void @@ -44,9 +47,19 @@ public function setRequest(ServerRequestInterface $request): void /** @var Route $route */ $route = $request->getAttribute('route'); $arguments = $request->getQueryParams(); - $modConf = $route->getOption('moduleConfiguration'); $pageId = $request->getParsedBody()['id'] ?? $request->getQueryParams()['id'] ?? null; - $displayName = LocalizationUtility::translate($modConf['labels'] . ':mlang_tabs_tab'); + + if (TYPO3_V11) { + $modConf = $route->getOption('moduleConfiguration'); + $displayName = LocalizationUtility::translate($modConf['labels'] . ':mlang_tabs_tab'); + } else { + /** + * @noinspection PhpUndefinedClassInspection + * @var ExtbaseModule $module + */ + $module = $route->getOption('module'); + $displayName = LocalizationUtility::translate($module->getTitle()); + } if (null !== $pageId) { if (0 === $pageId) { diff --git a/Classes/Cache/CachedRuntimeCache.php b/Classes/Cache/CachedRuntimeCache.php new file mode 100644 index 000000000..0421350b4 --- /dev/null +++ b/Classes/Cache/CachedRuntimeCache.php @@ -0,0 +1,66 @@ + + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use Closure; +use In2code\In2publishCore\Cache\Exception\CacheableValueCanNotBeGeneratedException; +use In2code\In2publishCore\CommonInjection\CacheInjection; +use TYPO3\CMS\Core\SingletonInterface; + +use function array_key_exists; + +class CachedRuntimeCache implements SingletonInterface +{ + use CacheInjection; + + protected array $rtc = []; + + /** + * @return mixed + */ + public function get(string $key, Closure $valueFactory, int $ttl = 86400) + { + if (!array_key_exists($key, $this->rtc)) { + if (!$this->cache->has($key)) { + try { + $value = $valueFactory(); + $this->cache->set($key, $value, [], $ttl); + } catch (CacheableValueCanNotBeGeneratedException $exception) { + $value = $exception->getValue(); + } + } else { + $value = $this->cache->get($key); + } + $this->rtc[$key] = $value; + } + + return $this->rtc[$key]; + } +} diff --git a/Classes/Cache/CachedRuntimeCacheInjection.php b/Classes/Cache/CachedRuntimeCacheInjection.php new file mode 100644 index 000000000..561c518b4 --- /dev/null +++ b/Classes/Cache/CachedRuntimeCacheInjection.php @@ -0,0 +1,21 @@ +cachedRuntimeCache = $cachedRuntimeCache; + } +} diff --git a/Classes/Cache/Exception/CacheableValueCanNotBeGeneratedException.php b/Classes/Cache/Exception/CacheableValueCanNotBeGeneratedException.php new file mode 100644 index 000000000..5c8f3ca8d --- /dev/null +++ b/Classes/Cache/Exception/CacheableValueCanNotBeGeneratedException.php @@ -0,0 +1,31 @@ +value = $value; + parent::__construct('', 1698857094, $previous); + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/Classes/Command/Foreign/Status/DbConfigTestCommand.php b/Classes/Command/Foreign/Status/DbConfigTestCommand.php index 3711d3d36..ea644481d 100644 --- a/Classes/Command/Foreign/Status/DbConfigTestCommand.php +++ b/Classes/Command/Foreign/Status/DbConfigTestCommand.php @@ -58,7 +58,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $queryBuilder->createNamedParameter(ForeignDatabaseConfigTest::DB_CONFIG_TEST_TYPE), ); $queryBuilder->select('*')->from('tx_in2code_in2publish_task')->where($predicates); - $result = $queryBuilder->execute()->fetchAllAssociative(); + $result = $queryBuilder->executeQuery()->fetchAllAssociative(); $value = base64_encode(json_encode(array_column($result, 'configuration'), JSON_THROW_ON_ERROR)); $output->writeln('DB Config: ' . $value); return Command::SUCCESS; diff --git a/Classes/CommonInjection/BackendUserAuthenticationInjection.php b/Classes/CommonInjection/BackendUserAuthenticationInjection.php new file mode 100644 index 000000000..6eab967cd --- /dev/null +++ b/Classes/CommonInjection/BackendUserAuthenticationInjection.php @@ -0,0 +1,45 @@ + + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; + +trait BackendUserAuthenticationInjection +{ + protected BackendUserAuthentication $backendUserAuthentication; + + /** + * @noinspection PhpUnused + */ + public function injectBackendUserAuthentication(BackendUserAuthentication $backendUserAuthentication): void + { + $this->backendUserAuthentication = $backendUserAuthentication; + } +} diff --git a/Classes/CommonInjection/PageDoktypeRegistryInjection.php b/Classes/CommonInjection/PageDoktypeRegistryInjection.php new file mode 100644 index 000000000..bf6d208b8 --- /dev/null +++ b/Classes/CommonInjection/PageDoktypeRegistryInjection.php @@ -0,0 +1,28 @@ +pageDoktypeRegistry = $pageDoktypeRegistry; + } +} diff --git a/Classes/CommonInjection/TranslationConfigurationProviderInjection.php b/Classes/CommonInjection/TranslationConfigurationProviderInjection.php new file mode 100644 index 000000000..5baeb70c0 --- /dev/null +++ b/Classes/CommonInjection/TranslationConfigurationProviderInjection.php @@ -0,0 +1,45 @@ + + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider; + +trait TranslationConfigurationProviderInjection +{ + protected TranslationConfigurationProvider $translationConfigurationProvider; + + /** + * @noinspection PhpUnused + */ + public function injectTranslationConfigurationProvider(TranslationConfigurationProvider $translationConfigurationProvider): void + { + $this->translationConfigurationProvider = $translationConfigurationProvider; + } +} diff --git a/Classes/Component/ConfigContainer/Definer/In2publishCoreDefiner.php b/Classes/Component/ConfigContainer/Definer/In2publishCoreDefiner.php index 581b86b27..5118194ce 100644 --- a/Classes/Component/ConfigContainer/Definer/In2publishCoreDefiner.php +++ b/Classes/Component/ConfigContainer/Definer/In2publishCoreDefiner.php @@ -52,9 +52,9 @@ class In2publishCoreDefiner implements DefinerServiceInterface 'tx_in2code_in2publish_task', 'tx_in2code_rpc_data', 'tx_in2code_rpc_request', - 'tx_in2publishcore_filepublisher_task', 'tx_in2publishcore_log', 'tx_in2publishcore_running_request', + 'tx_in2publishcore_filepublisher_instruction', ]; public function getLocalDefinition(): NodeCollection @@ -117,6 +117,11 @@ public function getLocalDefinition(): NodeCollection 'transOrigDiffSourceField', ], ], + '_file' => [ + 'fields' => [ + 'publicUrl', + ], + ], 'pages' => [ 'fields' => [ 'perms_userid', @@ -148,12 +153,6 @@ public function getLocalDefinition(): NodeCollection ->addBoolean('includeSysFileReference', false) ->addBoolean('treatRemovedAndDeletedAsDifference', false), ) - ->addArray( - 'filePreviewDomainName', - Builder::start() - ->addString('local', 'stage.example.com') - ->addString('foreign', 'www.example.com'), - ) ->addArray( 'view', Builder::start() diff --git a/Classes/Component/ConfigContainer/Provider/FileProvider.php b/Classes/Component/ConfigContainer/Provider/FileProvider.php index 8a74feb39..195016235 100644 --- a/Classes/Component/ConfigContainer/Provider/FileProvider.php +++ b/Classes/Component/ConfigContainer/Provider/FileProvider.php @@ -34,15 +34,16 @@ use Psr\Log\LoggerAwareInterface; use Psr\Log\LoggerAwareTrait; use Spyc; +use Symfony\Component\Yaml\Parser; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Core\Environment; -use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; -use function class_exists; use function file_exists; +use function file_get_contents; use function hash_file; use function rtrim; +use function str_replace; use function strpos; use function substr; use function trigger_error; @@ -83,8 +84,10 @@ public function getConfig(): array $cacheKey = 'config_file_provider_' . hash_file('sha1', $file); if (!$this->earlyCache->has($cacheKey)) { - $this->loadSpycIfRequired(); - $config = Spyc::YAMLLoad($file); + $yamlContents = file_get_contents($file); + $yamlContents = str_replace(['groups: *', '---'], ['groups: "*"', ''], $yamlContents); + $yaml = new Parser(); + $config = $yaml->parse($yamlContents); $code = 'return ' . var_export($config, true) . ';'; $this->earlyCache->flushByTag('config_file_provider'); $this->earlyCache->set($cacheKey, $code, ['config_file_provider']); @@ -115,15 +118,4 @@ protected function getResolvedFilePath(): string } return rtrim($path, '/') . '/'; } - - protected function loadSpycIfRequired(): void - { - if (class_exists(Spyc::class)) { - return; - } - $file = ExtensionManagementUtility::extPath('in2publish_core', 'Resources/Private/Libraries/Spyc/Spyc.php'); - if (file_exists($file)) { - require_once($file); - } - } } diff --git a/Classes/Component/ConfigContainer/Provider/PageTsProvider.php b/Classes/Component/ConfigContainer/Provider/PageTsProvider.php index 305146cf9..f90726aae 100644 --- a/Classes/Component/ConfigContainer/Provider/PageTsProvider.php +++ b/Classes/Component/ConfigContainer/Provider/PageTsProvider.php @@ -34,9 +34,10 @@ use In2code\In2publishCore\Utility\DatabaseUtility; use TYPO3\CMS\Backend\Utility\BackendUtility as CoreBackendUtility; use TYPO3\CMS\Core\Database\Connection; +use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -class PageTsProvider implements ProviderServiceInterface, ContextualProvider +class PageTsProvider implements ProviderServiceInterface, ContextualProvider, SingletonInterface { protected bool $locked = true; diff --git a/Classes/Component/ConfigContainer/Provider/VersionedFileProvider.php b/Classes/Component/ConfigContainer/Provider/VersionedFileProvider.php index de5a0f8a2..cb493cdd4 100644 --- a/Classes/Component/ConfigContainer/Provider/VersionedFileProvider.php +++ b/Classes/Component/ConfigContainer/Provider/VersionedFileProvider.php @@ -32,11 +32,14 @@ use In2code\In2publishCore\Component\ConfigContainer\Cache\EarlyCacheInjection; use In2code\In2publishCore\Service\Extension\ExtensionServiceInjection; use Spyc; +use Symfony\Component\Yaml\Parser; use function explode; use function file_exists; +use function file_get_contents; use function hash_file; use function implode; +use function str_replace; use function var_export; class VersionedFileProvider extends FileProvider @@ -61,8 +64,10 @@ public function getConfig(): array if (file_exists($file)) { $cacheKey = 'config_versioned_file_provider_' . hash_file('sha1', $file); if (!$this->earlyCache->has($cacheKey)) { - $this->loadSpycIfRequired(); - $config = Spyc::YAMLLoad($file); + $yamlContents = file_get_contents($file); + $yamlContents = str_replace(['groups: *', '---'], ['groups: "*"', ''], $yamlContents); + $yaml = new Parser(); + $config = $yaml->parse($yamlContents); $code = 'return ' . var_export($config, true) . ';'; $this->earlyCache->flushByTag('config_versioned_file_provider'); $this->earlyCache->set($cacheKey, $code, ['config_versioned_file_provider']); diff --git a/Classes/Component/Core/Demand/DemandBuilder.php b/Classes/Component/Core/Demand/DemandBuilder.php index 9a4107d96..0821864e6 100644 --- a/Classes/Component/Core/Demand/DemandBuilder.php +++ b/Classes/Component/Core/Demand/DemandBuilder.php @@ -16,7 +16,7 @@ public function buildDemandForRecords(RecordCollection $records): Demands $demand = $this->demandsFactory->createDemand(); foreach ($records->getRecordsFlat() as $record) { $classification = $record->getClassification(); - $resolvers = $this->resolverService->getResolversForTable($classification); + $resolvers = $this->resolverService->getResolversForClassification($classification); foreach ($resolvers as $resolver) { $resolver->resolve($demand, $record); } diff --git a/Classes/Component/Core/Demand/Type/FilesInFolderDemand.php b/Classes/Component/Core/Demand/Type/FilesInFolderDemand.php new file mode 100644 index 000000000..c94875903 --- /dev/null +++ b/Classes/Component/Core/Demand/Type/FilesInFolderDemand.php @@ -0,0 +1,34 @@ +storage = $storage; + $this->parentFolderIdentifier = $parentFolderIdentifier; + $this->record = $record; + } + + public function addToDemandsArray(array &$demands): void + { + $uniqueRecordKey = $this->createUniqueRecordKey($this->record); + $demands[$this->storage][$this->parentFolderIdentifier][$uniqueRecordKey] = $this->record; + } + + public function addToMetaArray(array &$meta, array $frame): void + { + $meta[$this->storage][$this->parentFolderIdentifier][] = $frame; + } +} diff --git a/Classes/Component/Core/Demand/Type/FolderDemand.php b/Classes/Component/Core/Demand/Type/FolderDemand.php new file mode 100644 index 000000000..38c9a77bf --- /dev/null +++ b/Classes/Component/Core/Demand/Type/FolderDemand.php @@ -0,0 +1,34 @@ +storage = $storage; + $this->identifier = $identifier; + $this->record = $record; + } + + public function addToDemandsArray(array &$demands): void + { + $uniqueRecordKey = $this->createUniqueRecordKey($this->record); + $demands[$this->storage][$this->identifier][$uniqueRecordKey] = $this->record; + } + + public function addToMetaArray(array &$meta, array $frame): void + { + $meta[$this->storage][$this->identifier][] = $frame; + } +} diff --git a/Classes/Component/Core/Demand/Type/FoldersInFolderDemand.php b/Classes/Component/Core/Demand/Type/FoldersInFolderDemand.php new file mode 100644 index 000000000..e0359ad30 --- /dev/null +++ b/Classes/Component/Core/Demand/Type/FoldersInFolderDemand.php @@ -0,0 +1,34 @@ +storage = $storage; + $this->parentFolderIdentifier = $parentFolderIdentifier; + $this->record = $record; + } + + public function addToDemandsArray(array &$demands): void + { + $uniqueRecordKey = $this->createUniqueRecordKey($this->record); + $demands[$this->storage][$this->parentFolderIdentifier][$uniqueRecordKey] = $this->record; + } + + public function addToMetaArray(array &$meta, array $frame): void + { + $meta[$this->storage][$this->parentFolderIdentifier][] = $frame; + } +} diff --git a/Classes/Component/Core/FileHandling/FileDemandResolver.php b/Classes/Component/Core/DemandResolver/Filesystem/FileDemandResolver.php similarity index 60% rename from Classes/Component/Core/FileHandling/FileDemandResolver.php rename to Classes/Component/Core/DemandResolver/Filesystem/FileDemandResolver.php index 20e840cee..67fd2d336 100644 --- a/Classes/Component/Core/FileHandling/FileDemandResolver.php +++ b/Classes/Component/Core/DemandResolver/Filesystem/FileDemandResolver.php @@ -2,24 +2,24 @@ declare(strict_types=1); -namespace In2code\In2publishCore\Component\Core\FileHandling; +namespace In2code\In2publishCore\Component\Core\DemandResolver\Filesystem; use In2code\In2publishCore\Component\Core\Demand\Demands; use In2code\In2publishCore\Component\Core\Demand\Type\FileDemand; use In2code\In2publishCore\Component\Core\DemandResolver\DemandResolver; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FileSystemInfoServiceInjection; -use In2code\In2publishCore\Component\Core\FileHandling\Service\ForeignFileSystemInfoServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FilesystemInformationCollection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\ForeignFileInfoServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\LocalFileInfoServiceInjection; use In2code\In2publishCore\Component\Core\Record\Factory\RecordFactoryInjection; use In2code\In2publishCore\Component\Core\RecordCollection; use function array_keys; -use function hash; class FileDemandResolver implements DemandResolver { use RecordFactoryInjection; - use ForeignFileSystemInfoServiceInjection; - use FileSystemInfoServiceInjection; + use LocalFileInfoServiceInjection; + use ForeignFileInfoServiceInjection; public function resolveDemand(Demands $demands, RecordCollection $recordCollection): void { @@ -35,9 +35,9 @@ public function resolveDemand(Demands $demands, RecordCollection $recordCollecti } } - $localDriverInfo = $this->fileSystemInfoService->getFileInfo($filesArray); + $localDriverInfo = $this->localFileInfoService->getFileInfo($filesArray); $localFileInfo = $this->addFileInfoToDriverInfo($files, $localDriverInfo); - $foreignDriverInfo = $this->foreignFileSystemInfoService->getFileInfo($filesArray); + $foreignDriverInfo = $this->foreignFileInfoService->getFileInfo($filesArray); $foreignFileInfo = $this->addFileInfoToDriverInfo($files, $foreignDriverInfo); foreach ($files as $storage => $identifiers) { @@ -57,24 +57,13 @@ public function resolveDemand(Demands $demands, RecordCollection $recordCollecti } } - protected function addFileInfoToDriverInfo(array $files, array $driverInfo): array + protected function addFileInfoToDriverInfo(array $files, FilesystemInformationCollection $driverInfo): array { $result = []; foreach ($files as $storage => $identifiers) { foreach ($identifiers as $identifier => $parentRecords) { - if (isset($driverInfo[$storage][$identifier])) { - $fileInfo = $driverInfo[$storage][$identifier]; - $result[$storage][$identifier] = [ - 'storage' => $storage, - 'identifier' => $identifier, - 'identifier_hash' => hash('sha1', $identifier), - 'size' => $fileInfo['size'], - 'mimetype' => $fileInfo['mimetype'], - 'name' => $fileInfo['name'], - 'extension' => $fileInfo['extension'], - 'folder_hash' => $fileInfo['folder_hash'], - ]; - } + $fileInfo = $driverInfo->getInfo($storage, $identifier); + $result[$storage][$identifier] = $fileInfo->toArray(); } } return $result; diff --git a/Classes/Component/Core/DemandResolver/Filesystem/FilesInFolderDemandResolver.php b/Classes/Component/Core/DemandResolver/Filesystem/FilesInFolderDemandResolver.php new file mode 100644 index 000000000..53869acfa --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/FilesInFolderDemandResolver.php @@ -0,0 +1,221 @@ +>> $filesInFolderDemands */ + $filesInFolderDemands = $demands->getDemandsByType(FilesInFolderDemand::class); + if (empty($filesInFolderDemands)) { + return; + } + + $fileDemands = $this->demandsFactory->createDemand(); + + $request = []; + foreach ($filesInFolderDemands as $storage => $parentFolderIdentifier) { + $request[$storage] = array_keys($parentFolderIdentifier); + } + + $localResponseCollection = $this->localFolderInfoService->getFolderInfo($request); + $foreignResponseCollection = $this->foreignFolderInfoService->getFolderInformation($request); + + foreach ($filesInFolderDemands as $storage => $parentIdentifiers) { + foreach ($parentIdentifiers as $parentIdentifier => $valueMap) { + $localInfo = $localResponseCollection->getInfo($storage, $parentIdentifier); + $foreignInfo = $foreignResponseCollection->getInfo($storage, $parentIdentifier); + + /** @var array $mergedFiles */ + $mergedFiles = []; + if ($localInfo instanceof FolderInfo) { + $localFiles = $localInfo->getFiles(); + foreach ($localFiles as $localFile) { + $identifier = $localFile->getIdentifier(); + $storage = $localFile->getStorage(); + $mergedFiles[$identifier]['local'] = $localFile; + $mergedFiles[$identifier]['foreign'] = new MissingFileInfo($storage, $identifier); + } + } + if ($foreignInfo instanceof FolderInfo) { + $foreignFiles = $foreignInfo->getFiles(); + foreach ($foreignFiles as $foreignFile) { + $identifier = $foreignFile->getIdentifier(); + $storage = $foreignFile->getStorage(); + $mergedFiles[$identifier]['local'] ??= new MissingFileInfo($storage, $identifier); + $mergedFiles[$identifier]['foreign'] = $foreignFile; + } + } + foreach ($mergedFiles as $mergedFile) { + $fileRecord = $this->recordFactory->createFileRecord( + $mergedFile['local']->toArray(), + $mergedFile['foreign']->toArray(), + ); + if (null !== $fileRecord) { + $recordCollection->addRecord($fileRecord); + foreach ($valueMap as $record) { + $record->addChild($fileRecord); + } + $fileDemands->addDemand( + new SelectDemand( + 'sys_file', + 'storage = ' . $fileRecord->getProp('storage'), + 'identifier_hash', + $fileRecord->getProp('identifier_hash'), + $fileRecord, + ), + ); + } + } + } + } + + $fileRecordCollection = new RecordCollection(); + $this->demandResolver->resolveDemand($fileDemands, $fileRecordCollection); + $this->recordTreeBuilder->findRecordsByTca($fileRecordCollection); + + foreach ($request as $storage => $parentFolderIdentifiers) { + foreach ($parentFolderIdentifiers as $parentFolderIdentifier) { + /** @var FolderRecord $folderRecord */ + $folderRecord = $this->recordIndex->getRecord( + FolderRecord::CLASSIFICATION, + $storage . ':' . $parentFolderIdentifier, + ); + $this->identifyMovedRecords($folderRecord); + } + } + } + + protected function identifyMovedRecords(FolderRecord $folderRecord): void + { + /** @var array $fileRecords */ + $fileRecords = []; + $sysFileRecordCollection = new RecordCollection(); + foreach ($folderRecord->getChildren()[FileRecord::CLASSIFICATION] ?? [] as $filesInFolder) { + $fileRecords[$filesInFolder->getProp('identifier')] = $filesInFolder; + $sysFileRecordCollection->addRecords($filesInFolder->getChildren()['sys_file'] ?? []); + } + + $filesMovedOutFromFolder = []; + $filesMovedIntoFolder = []; + + $sysFileRecords = $sysFileRecordCollection->getRecords('sys_file'); + foreach ($sysFileRecords as $sysFileRecord) { + if ($sysFileRecord->getState() === Record::S_CHANGED) { + $localProps = $sysFileRecord->getLocalProps(); + $foreignProps = $sysFileRecord->getForeignProps(); + $localIdentifier = $localProps['identifier']; + $foreignIdentifier = $foreignProps['identifier']; + if ($localIdentifier !== $foreignIdentifier) { + $fileRecord = $fileRecords[$localIdentifier] ?? null; + if (null === $fileRecord || empty($fileRecord->getLocalProps())) { + $filesMovedOutFromFolder[$localProps['storage']][] = $localIdentifier; + } + $fileRecord = $fileRecords[$foreignIdentifier] ?? null; + if (null === $fileRecord || empty($fileRecord->getForeignProps())) { + $filesMovedIntoFolder[$foreignProps['storage']][] = $foreignIdentifier; + } + } + } + } + + if (!empty($filesMovedOutFromFolder)) { + $foundMovedOutFiles = $this->localFileInfoService->getFileInfo($filesMovedOutFromFolder); + foreach ($foundMovedOutFiles as $localInfo) { + $identifier = $localInfo->getIdentifier(); + $fileRecords[$identifier] = $this->recordFactory->createFileRecord($localInfo->toArray(), []); + } + } + if (!empty($filesMovedIntoFolder)) { + $foundMovedIntoFiles = $this->foreignFileInfoService->getFileInfo($filesMovedIntoFolder); + foreach ($foundMovedIntoFiles as $foreignInfo) { + $identifier = $foreignInfo->getIdentifier(); + $fileRecords[$identifier] = $this->recordFactory->createFileRecord([], $foreignInfo->toArray()); + } + } + + foreach ($sysFileRecords as $sysFileRecord) { + if ($sysFileRecord->getState() === Record::S_CHANGED) { + $localIdentifier = $sysFileRecord->getLocalProps()['identifier']; + $foreignIdentifier = $sysFileRecord->getForeignProps()['identifier']; + if ($localIdentifier === $foreignIdentifier) { + continue; + } + + $localFileRecord = $fileRecords[$localIdentifier]; + $foreignFileRecord = $fileRecords[$foreignIdentifier]; + + $folderRecord->removeChild($localFileRecord); + $folderRecord->removeChild($foreignFileRecord); + $localFileRecord->removeChild($sysFileRecord); + $foreignFileRecord->removeChild($sysFileRecord); + + // Special case: A file was renamed and another file with the same name as the old one was uploaded + if (!empty($foreignFileRecord->getLocalProps())) { + $newFileOnForeign = $this->recordFactory->createFileRecord( + $foreignFileRecord->getLocalProps(), + [], + ); + if (null !== $newFileOnForeign) { + $newOnForeignSysFileRecords = $sysFileRecordCollection->getRecordsByProperties('sys_file', [ + 'storage' => $newFileOnForeign->getProp('storage'), + 'identifier' => $newFileOnForeign->getProp('identifier'), + ]); + foreach ($newOnForeignSysFileRecords as $newOnForeignSysFileRecord) { + if (empty($newOnForeignSysFileRecord->getForeignProps())) { + $localFileRecord->removeChild($newOnForeignSysFileRecord); + $foreignFileRecord->removeChild($newOnForeignSysFileRecord); + $newFileOnForeign->addChild($newOnForeignSysFileRecord); + } + } + $folderRecord->addChild($newFileOnForeign); + } + } + $recordToAdd = $this->recordFactory->createFileRecord( + $localFileRecord->getLocalProps(), + $foreignFileRecord->getForeignProps(), + ); + if (null !== $recordToAdd) { + $recordToAdd->addChild($sysFileRecord); + $folderRecord->addChild($recordToAdd); + } + } + } + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/FolderDemandResolver.php b/Classes/Component/Core/DemandResolver/Filesystem/FolderDemandResolver.php new file mode 100644 index 000000000..a23e7a45c --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/FolderDemandResolver.php @@ -0,0 +1,72 @@ +>> $folderDemands */ + $folderDemands = $demands->getDemandsByType(FolderDemand::class); + if (empty($folderDemands)) { + return; + } + + $request = []; + foreach ($folderDemands as $storage => $folderIdentifiers) { + $request[$storage] = array_keys($folderIdentifiers); + } + + $localResponseCollection = $this->localFolderInfoService->getFolderInfo($request); + $foreignResponseCollection = $this->foreignFolderInfoService->getFolderInformation($request); + + foreach ($folderDemands as $storage => $identifiers) { + foreach ($identifiers as $identifier => $valueMap) { + $localInfo = $localResponseCollection->getInfo($storage, $identifier); + $foreignInfo = $foreignResponseCollection->getInfo($storage, $identifier); + + $combinedIdentifier = $storage . ':' . $identifier; + + $folderRecord = $this->recordIndex->getRecord(FolderRecord::CLASSIFICATION, $combinedIdentifier); + if (null === $folderRecord) { + if ($localInfo instanceof MissingFolderInfo && $foreignInfo instanceof MissingFolderInfo) { + continue; + } + + $folderRecord = $this->recordFactory->createFolderRecord( + $localInfo->toArray(), + $foreignInfo->toArray(), + ); + if (null === $folderRecord) { + continue; + } + $recordCollection->addRecord($folderRecord); + } + foreach ($valueMap as $record) { + $record->addChild($folderRecord); + } + } + } + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/FoldersInFolderDemandResolver.php b/Classes/Component/Core/DemandResolver/Filesystem/FoldersInFolderDemandResolver.php new file mode 100644 index 000000000..a7776ee19 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/FoldersInFolderDemandResolver.php @@ -0,0 +1,84 @@ +>> $foldersInFolderDemand */ + $foldersInFolderDemand = $demands->getDemandsByType(FoldersInFolderDemand::class); + if (empty($foldersInFolderDemand)) { + return; + } + + $request = []; + foreach ($foldersInFolderDemand as $storage => $parentFolderIdentifier) { + $request[$storage] = array_keys($parentFolderIdentifier); + } + + $localResponseCollection = $this->localFolderInfoService->getFolderInfo($request); + $foreignResponseCollection = $this->foreignFolderInfoService->getFolderInformation($request); + + foreach ($foldersInFolderDemand as $storage => $parentIdentifiers) { + foreach ($parentIdentifiers as $parentIdentifier => $valueMap) { + $localInfo = $localResponseCollection->getInfo($storage, $parentIdentifier); + $foreignInfo = $foreignResponseCollection->getInfo($storage, $parentIdentifier); + + /** @var array $mergedFolders */ + $mergedFolders = []; + if ($localInfo instanceof FolderInfo) { + $localFolders = $localInfo->getFolders(); + foreach ($localFolders as $localFolder) { + $identifier = $localFolder->getIdentifier(); + $storage = $localFolder->getStorage(); + $mergedFolders[$identifier]['local'] = $localFolder; + $mergedFolders[$identifier]['foreign'] = new MissingFolderInfo($storage, $identifier); + } + } + if ($foreignInfo instanceof FolderInfo) { + $foreignFolders = $foreignInfo->getFolders(); + foreach ($foreignFolders as $foreignFolder) { + $identifier = $foreignFolder->getIdentifier(); + $storage = $foreignFolder->getStorage(); + $mergedFolders[$identifier]['local'] ??= new MissingFolderInfo($storage, $identifier); + $mergedFolders[$identifier]['foreign'] = $foreignFolder; + } + } + foreach ($mergedFolders as $mergedFolder) { + $folderRecord = $this->recordFactory->createFolderRecord( + $mergedFolder['local']->toArray(), + $mergedFolder['foreign']->toArray(), + ); + if (null !== $folderRecord) { + foreach ($valueMap as $record) { + $record->addChild($folderRecord); + } + } + } + } + } + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Model/FileInfo.php b/Classes/Component/Core/DemandResolver/Filesystem/Model/FileInfo.php new file mode 100644 index 000000000..1fd0d69e6 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Model/FileInfo.php @@ -0,0 +1,109 @@ +storage = $storage; + $this->identifier = $identifier; + $this->name = $name; + $this->sha1 = $sha1; + $this->publicUrl = $publicUrl; + $this->size = $size; + $this->mimetype = $mimetype; + $this->extension = $extension; + $this->folderHash = $folderHash; + $this->identifierHash = $identifierHash; + } + + public function getStorage(): int + { + return $this->storage; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + public function getName(): string + { + return $this->name; + } + + public function getSha1(): string + { + return $this->sha1; + } + + public function getPublicUrl(): string + { + return $this->publicUrl; + } + + public function getSize(): int + { + return $this->size; + } + + public function getMimetype(): string + { + return $this->mimetype; + } + + public function getExtension(): string + { + return $this->extension; + } + + public function getFolderHash(): string + { + return $this->folderHash; + } + + public function getIdentifierHash(): string + { + return $this->identifierHash; + } + + public function toArray(): array + { + return [ + 'storage' => $this->storage, + 'identifier' => $this->identifier, + 'name' => $this->name, + 'sha1' => $this->sha1, + 'publicUrl' => $this->publicUrl, + 'size' => $this->size, + 'mimetype' => $this->mimetype, + 'extension' => $this->extension, + 'folder_hash' => $this->folderHash, + 'identifier_hash' => $this->identifierHash, + ]; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Model/FilesystemInfo.php b/Classes/Component/Core/DemandResolver/Filesystem/Model/FilesystemInfo.php new file mode 100644 index 000000000..b48ab6470 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Model/FilesystemInfo.php @@ -0,0 +1,14 @@ +getStorage(); + $identifier = $filesystemInformation->getIdentifier(); + $this->information[$storage][$identifier] = $filesystemInformation; + } + + public function getInfo(int $storage, string $identifier): FilesystemInfo + { + return $this->information[$storage][$identifier]; + } + + /** + * @return Generator + */ + public function getIterator(): Generator + { + foreach ($this->information as $identifiers) { + foreach ($identifiers as $information) { + yield $information; + } + } + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Model/FolderInfo.php b/Classes/Component/Core/DemandResolver/Filesystem/Model/FolderInfo.php new file mode 100644 index 000000000..54c7f730e --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Model/FolderInfo.php @@ -0,0 +1,67 @@ + */ + protected array $files = []; + /** @var array */ + protected array $folders = []; + + public function __construct(int $storage, string $identifier, string $name) + { + $this->storage = $storage; + $this->identifier = $identifier; + $this->name = $name; + } + + public function getStorage(): int + { + return $this->storage; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + public function getName(): string + { + return $this->name; + } + + public function addFolder(FilesystemInfo $folderInformation): void + { + $this->folders[] = $folderInformation; + } + + public function getFolders(): array + { + return $this->folders; + } + + public function addFile(FilesystemInfo $fileInformation): void + { + $this->files[] = $fileInformation; + } + + public function getFiles(): array + { + return $this->files; + } + + public function toArray(): array + { + return [ + 'storage' => $this->storage, + 'identifier' => $this->identifier, + 'name' => $this->name, + ]; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Model/MissingFileInfo.php b/Classes/Component/Core/DemandResolver/Filesystem/Model/MissingFileInfo.php new file mode 100644 index 000000000..f4b2747bf --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Model/MissingFileInfo.php @@ -0,0 +1,32 @@ +storage = $storage; + $this->identifier = $identifier; + } + + public function getStorage(): int + { + return $this->storage; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + public function toArray(): array + { + return []; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Model/MissingFolderInfo.php b/Classes/Component/Core/DemandResolver/Filesystem/Model/MissingFolderInfo.php new file mode 100644 index 000000000..c08a6c640 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Model/MissingFolderInfo.php @@ -0,0 +1,32 @@ +storage = $storage; + $this->identifier = $identifier; + } + + public function getStorage(): int + { + return $this->storage; + } + + public function getIdentifier(): string + { + return $this->identifier; + } + + public function toArray(): array + { + return []; + } +} diff --git a/Classes/Component/Core/FileHandling/Service/FalDriverService.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/FalDriverService.php similarity index 74% rename from Classes/Component/Core/FileHandling/Service/FalDriverService.php rename to Classes/Component/Core/DemandResolver/Filesystem/Service/FalDriverService.php index 30b19953b..b97cc91ee 100644 --- a/Classes/Component/Core/FileHandling/Service/FalDriverService.php +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/FalDriverService.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace In2code\In2publishCore\Component\Core\FileHandling\Service; +namespace In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service; use In2code\In2publishCore\CommonInjection\FlexFormServiceInjection; use In2code\In2publishCore\CommonInjection\LocalDatabaseInjection; @@ -12,8 +12,6 @@ use TYPO3\CMS\Core\Resource\ResourceStorageInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use function in_array; - class FalDriverService { use LocalDatabaseInjection; @@ -42,7 +40,7 @@ public function getDriver(int $storage): DriverInterface $query->select('*') ->from('sys_file_storage') ->where($query->expr()->eq('uid', $query->createNamedParameter($storage))); - $result = $query->execute(); + $result = $query->executeQuery(); $storageRow = $result->fetchAssociative(); $driver = $this->createFalDriver($storageRow); } @@ -51,38 +49,6 @@ public function getDriver(int $storage): DriverInterface return $this->rtc[$storage]; } - /** - * @return array - */ - public function getDrivers(array $storagesUids): array - { - $query = $this->localDatabase->createQueryBuilder(); - $query->getRestrictions()->removeAll(); - $query->select('*') - ->from('sys_file_storage') - ->where($query->expr()->in('uid', $storagesUids)); - $result = $query->execute(); - $storages = $result->fetchAllAssociative(); - - $drivers = []; - foreach ($storages as $storage) { - $storageUid = $storage['uid']; - if (!isset($this->rtc[$storageUid])) { - $this->rtc[$storageUid] = $this->createFalDriver($storage); - } - $drivers[$storageUid] = $this->rtc[$storageUid]; - } - - if (in_array(0, $storagesUids)) { - if (!isset($this->rtc[0])) { - $this->rtc[0] = $this->createFallbackDriver(); - } - $drivers[0] = $this->rtc[0]; - } - - return $drivers; - } - protected function createFalDriver(array $storage): DriverInterface { $storageConfiguration = $this->convertFlexFormDataToConfigurationArray($storage['configuration'] ?? []); diff --git a/Classes/Component/Core/FileHandling/Service/FalDriverServiceInjection.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/FalDriverServiceInjection.php similarity index 80% rename from Classes/Component/Core/FileHandling/Service/FalDriverServiceInjection.php rename to Classes/Component/Core/DemandResolver/Filesystem/Service/FalDriverServiceInjection.php index 62b51344c..56d2639de 100644 --- a/Classes/Component/Core/FileHandling/Service/FalDriverServiceInjection.php +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/FalDriverServiceInjection.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace In2code\In2publishCore\Component\Core\FileHandling\Service; +namespace In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service; /** * @codeCoverageIgnore diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/FileInfoService.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/FileInfoService.php new file mode 100644 index 000000000..d7ec535c3 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/FileInfoService.php @@ -0,0 +1,53 @@ +fileExists($fileIdentifier)) { + return new MissingFileInfo($storage, $fileIdentifier); + } + + $fileInfo = $driver->getFileInfoByIdentifier($fileIdentifier, self::PROPERTIES); + $fileInfo['folderHash'] = $fileInfo['folder_hash']; + $fileInfo['identifierHash'] = $fileInfo['identifier_hash']; + unset($fileInfo['folder_hash'], $fileInfo['identifier_hash']); + $fileInfo['sha1'] = $driver->hash($fileIdentifier, 'sha1'); + $fileInfo['publicUrl'] = null; + $publicUrl = $driver->getPublicUrl($fileInfo['identifier']); + if ($publicUrl) { + if (!PathUtility::hasProtocolAndScheme($publicUrl)) { + $firstSite = array_values($this->siteFinder->getAllSites())[0]; + $publicUrl = $firstSite->getRouter()->generateUri($firstSite->getRootPageId()) . $publicUrl; + } + $fileInfo['publicUrl'] = $publicUrl; + } + return new FileInfo(...$fileInfo); + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/FileInfoServiceInjection.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/FileInfoServiceInjection.php new file mode 100644 index 000000000..2a4b5a594 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/FileInfoServiceInjection.php @@ -0,0 +1,21 @@ +fileInfoService = $fileInfoService; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFileInfoService.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFileInfoService.php new file mode 100644 index 000000000..6bd2d8646 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFileInfoService.php @@ -0,0 +1,22 @@ +executeCommandDispatcher->executeEnvelope($envelope); + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFileInfoServiceInjection.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFileInfoServiceInjection.php new file mode 100644 index 000000000..077661b89 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFileInfoServiceInjection.php @@ -0,0 +1,22 @@ +foreignFileInfoService = $foreignFileInfoService; + } + +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFolderInfoService.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFolderInfoService.php new file mode 100644 index 000000000..699376348 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFolderInfoService.php @@ -0,0 +1,33 @@ +sharedFilesystemInfoCache->getForeign($request); + if (null !== $collection) { + return $collection; + } + + /** @see EnvelopeDispatcher::getFolderInfo */ + $envelope = new Envelope(EnvelopeDispatcher::CMD_GET_FOLDER_INFO, $request); + + $response = $this->executeCommandDispatcher->executeEnvelope($envelope); + + $this->sharedFilesystemInfoCache->setForeign($response, $request); + + return $response; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFolderInfoServiceInjection.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFolderInfoServiceInjection.php new file mode 100644 index 000000000..f7be7f9bc --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/ForeignFolderInfoServiceInjection.php @@ -0,0 +1,21 @@ +foreignFolderInfoService = $foreignFolderInfoService; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFileInfoService.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFileInfoService.php new file mode 100644 index 000000000..45e737d37 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFileInfoService.php @@ -0,0 +1,27 @@ + $fileIdentifiers) { + $driver = $this->falDriverService->getDriver($storage); + foreach ($fileIdentifiers as $identifier) { + $collection->addFilesystemInfo($this->fileInfoService->getFileInfo($driver, $storage, $identifier)); + } + } + + return $collection; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFileInfoServiceInjection.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFileInfoServiceInjection.php new file mode 100644 index 000000000..911faf35f --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFileInfoServiceInjection.php @@ -0,0 +1,21 @@ +localFileInfoService = $localFileInfoService; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFolderInfoService.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFolderInfoService.php new file mode 100644 index 000000000..571f1ffe6 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFolderInfoService.php @@ -0,0 +1,68 @@ +sharedFilesystemInfoCache->getLocal($request); + if (null !== $collection) { + return $collection; + } + $collection = new FilesystemInformationCollection(); + + foreach ($request as $storage => $identifiers) { + $driver = $this->falDriverService->getDriver($storage); + foreach ($identifiers as $identifier) { + if (!$driver->folderExists($identifier)) { + $collection->addFilesystemInfo(new MissingFolderInfo($storage, $identifier)); + continue; + } + + $name = $this->getFolderName($identifier, $storage); + $folderInformation = new FolderInfo($storage, $identifier, $name); + $collection->addFilesystemInfo($folderInformation); + + $folderIdentifiers = $driver->getFoldersInFolder($identifier); + foreach ($folderIdentifiers as $folderIdentifier) { + $name = $this->getFolderName($folderIdentifier, $storage); + $childFolderInformation = new FolderInfo($storage, $folderIdentifier, $name); + $folderInformation->addFolder($childFolderInformation); + } + + $fileIdentifiers = $driver->getFilesInFolder($identifier); + foreach ($fileIdentifiers as $fileIdentifier) { + $fileInfo = $this->fileInfoService->getFileInfo($driver, $storage, $fileIdentifier); + $folderInformation->addFile($fileInfo); + } + } + } + + $this->sharedFilesystemInfoCache->setLocal($collection, $request); + + return $collection; + } + + public function getFolderName(string $identifier, int $storage): string + { + $name = PathUtility::basename($identifier); + if (empty($name)) { + $name = $this->resourceFactory->getStorageObject($storage)->getName(); + } + return $name; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFolderInfoServiceInjection.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFolderInfoServiceInjection.php new file mode 100644 index 000000000..274478624 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/LocalFolderInfoServiceInjection.php @@ -0,0 +1,21 @@ +localFolderInfoService = $localFolderInfoService; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/SharedFilesystemInfoCache.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/SharedFilesystemInfoCache.php new file mode 100644 index 000000000..c4936a56a --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/SharedFilesystemInfoCache.php @@ -0,0 +1,44 @@ + */ + protected array $local; + /** @var array */ + protected array $foreign; + + public function setLocal(FilesystemInformationCollection $response, array $request): void + { + ksort($request); + $this->local[sha1(json_encode($request, JSON_THROW_ON_ERROR))] = $response; + } + + public function setForeign(FilesystemInformationCollection $response, array $request): void + { + ksort($request); + $this->foreign[sha1(json_encode($request, JSON_THROW_ON_ERROR))] = $response; + } + + public function getLocal(array $request): ?FilesystemInformationCollection + { + ksort($request); + return $this->local[sha1(json_encode($request, JSON_THROW_ON_ERROR))] ?? null; + } + + public function getForeign(array $request): ?FilesystemInformationCollection + { + ksort($request); + return $this->foreign[sha1(json_encode($request, JSON_THROW_ON_ERROR))] ?? null; + } +} diff --git a/Classes/Component/Core/DemandResolver/Filesystem/Service/SharedFilesystemInfoCacheInjection.php b/Classes/Component/Core/DemandResolver/Filesystem/Service/SharedFilesystemInfoCacheInjection.php new file mode 100644 index 000000000..e212b4ac0 --- /dev/null +++ b/Classes/Component/Core/DemandResolver/Filesystem/Service/SharedFilesystemInfoCacheInjection.php @@ -0,0 +1,21 @@ +sharedFilesystemInfoCache = $sharedFilesystemInfoCache; + } +} diff --git a/Classes/Component/Core/DependencyInjection/StaticResolverPass.php b/Classes/Component/Core/DependencyInjection/StaticResolverPass.php new file mode 100644 index 000000000..a446fd575 --- /dev/null +++ b/Classes/Component/Core/DependencyInjection/StaticResolverPass.php @@ -0,0 +1,31 @@ +tagName = $tagName; + } + + public function process(ContainerBuilder $container) + { + $resolverServiceDefinition = $container->findDefinition(ResolverService::class); + $staticResolvers = $container->findTaggedServiceIds($this->tagName); + foreach (array_keys($staticResolvers) as $staticResolver) { + $resolverServiceDefinition->addMethodCall('addStaticResolver', [new Reference($staticResolver)]); + } + } +} diff --git a/Classes/Component/Core/FileHandling/DefaultFalFinder.php b/Classes/Component/Core/FileHandling/DefaultFalFinder.php index a145b4a15..d485b07a8 100644 --- a/Classes/Component/Core/FileHandling/DefaultFalFinder.php +++ b/Classes/Component/Core/FileHandling/DefaultFalFinder.php @@ -31,41 +31,42 @@ use In2code\In2publishCore\CommonInjection\EventDispatcherInjection; use In2code\In2publishCore\CommonInjection\ResourceFactoryInjection; +use In2code\In2publishCore\Component\Core\Demand\DemandBuilderInjection; use In2code\In2publishCore\Component\Core\Demand\DemandsFactoryInjection; +use In2code\In2publishCore\Component\Core\Demand\Type\FilesInFolderDemand; +use In2code\In2publishCore\Component\Core\Demand\Type\FolderDemand; +use In2code\In2publishCore\Component\Core\Demand\Type\FoldersInFolderDemand; use In2code\In2publishCore\Component\Core\Demand\Type\SelectDemand; use In2code\In2publishCore\Component\Core\DemandResolver\DemandResolverInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FileInfo; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\ForeignFileInfoServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\LocalFileInfoServiceInjection; use In2code\In2publishCore\Component\Core\FileHandling\Exception\FolderDoesNotExistOnBothSidesException; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverServiceInjection; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FileSystemInfoServiceInjection; -use In2code\In2publishCore\Component\Core\FileHandling\Service\ForeignFileSystemInfoServiceInjection; use In2code\In2publishCore\Component\Core\Record\Factory\RecordFactoryInjection; -use In2code\In2publishCore\Component\Core\Record\Model\FileRecord; +use In2code\In2publishCore\Component\Core\Record\Model\FolderRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; use In2code\In2publishCore\Component\Core\RecordCollection; -use In2code\In2publishCore\Component\Core\RecordIndexInjection; use In2code\In2publishCore\Component\Core\RecordTree\RecordTree; use In2code\In2publishCore\Component\Core\RecordTree\RecordTreeBuilderInjection; use In2code\In2publishCore\Event\RecordRelationsWereResolved; use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Core\Utility\PathUtility; use function explode; -use function ltrim; -use function sha1; +use function str_contains; +use function trim; class DefaultFalFinder { use RecordFactoryInjection; - use RecordIndexInjection; use DemandsFactoryInjection; use DemandResolverInjection; use ResourceFactoryInjection; - use ForeignFileSystemInfoServiceInjection; - use FalDriverServiceInjection; - use FileSystemInfoServiceInjection; use RecordTreeBuilderInjection; use EventDispatcherInjection; + use LocalFileInfoServiceInjection; + use ForeignFileInfoServiceInjection; + use DemandBuilderInjection; /** * Creates a Record instance representing the current chosen folder in the @@ -97,221 +98,51 @@ public function findFolderRecord(?string $combinedIdentifier, bool $onlyRoot = f $folder = $this->resourceFactory->getDefaultStorage()->getRootLevelFolder(); // Update the combinedIdentifier to the actual folder we work with. $combinedIdentifier = $folder->getCombinedIdentifier(); - $storageUid = $folder->getStorage()->getUid(); + $storage = $folder->getStorage()->getUid(); $identifier = $folder->getIdentifier(); } else { + $combinedIdentifier = $this->normalizeCombinedIdentifier($combinedIdentifier); try { // This is the normal case. The local folder exists. $folder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier($combinedIdentifier); - $storageUid = $folder->getStorage()->getUid(); + $storage = $folder->getStorage()->getUid(); } /** @noinspection PhpRedundantCatchClauseInspection */ catch (FolderDoesNotExistException $exception) { - $localFolderExists = false; - [$storageUid] = GeneralUtility::trimExplode(':', $combinedIdentifier); - $storageUid = (int)$storageUid; - $localStorage = $this->resourceFactory->getStorageObject($storageUid); + [$storage] = GeneralUtility::trimExplode(':', $combinedIdentifier); + $storage = (int)$storage; + $localStorage = $this->resourceFactory->getStorageObject($storage); $folder = $localStorage->getRootLevelFolder(); } $identifier = GeneralUtility::trimExplode(':', $combinedIdentifier)[1]; } - $storageName = $folder->getStorage()->getName(); - $foreignFolderExists = $this->foreignFileSystemInfoService->folderExists($storageUid, $identifier); - if (!$localFolderExists && !$foreignFolderExists) { - throw new FolderDoesNotExistOnBothSidesException($combinedIdentifier, $folder->getCombinedIdentifier()); - } - unset($folder); + $recordTree = new RecordTree(); - $folderName = PathUtility::basename($combinedIdentifier); - $folderIdentifier = explode(':', $combinedIdentifier)[1]; + $demands = $this->demandsFactory->createDemand(); + $demands->addDemand(new FolderDemand($storage, $identifier, $recordTree)); - $localProps = []; - if ($localFolderExists) { - $localProps = [ - 'combinedIdentifier' => $combinedIdentifier, - 'identifier' => $folderIdentifier, - 'name' => $folderName ?: $storageName, - 'storage' => $storageUid, - ]; - } + $recordCollection = new RecordCollection(); + $this->demandResolver->resolveDemand($demands, $recordCollection); - $foreignProps = []; - if ($foreignFolderExists) { - $foreignProps = [ - 'combinedIdentifier' => $combinedIdentifier, - 'identifier' => $folderIdentifier, - 'name' => $folderName ?: $storageName, - 'storage' => $storageUid, - ]; - } - $folderRecord = $this->recordFactory->createFolderRecord($combinedIdentifier, $localProps, $foreignProps); + $demands = $this->demandBuilder->buildDemandForRecords($recordCollection); + $recordCollection = new RecordCollection(); + $this->demandResolver->resolveDemand($demands, $recordCollection); + + $folderRecord = $recordTree->getChild(FolderRecord::CLASSIFICATION, $combinedIdentifier); if (null === $folderRecord) { - return new RecordTree(); + throw new FolderDoesNotExistOnBothSidesException($combinedIdentifier, $folder->getCombinedIdentifier()); } if ($onlyRoot) { - return new RecordTree([$folderRecord]); - } - - $localFolderContents = $this->fileSystemInfoService->listFolderContents( - $storageUid, - $folderIdentifier, - ); - $foreignFolderContents = $this->foreignFileSystemInfoService->listFolderContents( - $storageUid, - $folderIdentifier, - ); - - $folders = []; - foreach ($localFolderContents['folders'] ?? [] as $folder) { - $combinedIdentifier = $storageUid . ':/' . ltrim($folder, '/'); - $folders[$combinedIdentifier]['local'] = [ - 'combinedIdentifier' => $combinedIdentifier, - 'identifier' => $folder, - 'name' => PathUtility::basename($folder), - 'storage' => $storageUid, - ]; - } - foreach ($foreignFolderContents['folders'] ?? [] as $folder) { - $combinedIdentifier = $storageUid . ':/' . ltrim($folder, '/'); - $folders[$combinedIdentifier]['foreign'] = [ - 'combinedIdentifier' => $combinedIdentifier, - 'identifier' => $folder, - 'name' => PathUtility::basename($folder), - 'storage' => $storageUid, - ]; - } - foreach ($folders as $subFolderIdentifier => $sides) { - $subFolderRecord = $this->recordFactory->createFolderRecord( - $subFolderIdentifier, - $sides['local'] ?? [], - $sides['foreign'] ?? [], - ); - if (null === $subFolderRecord) { - continue; - } - $folderRecord->addChild($subFolderRecord); + $this->eventDispatcher->dispatch(new RecordRelationsWereResolved($recordTree)); + return $recordTree; } - $files = []; - foreach ($localFolderContents['files'] ?? [] as $localFiles) { - foreach ($localFiles as $file) { - $files[$file['identifier']]['local'] = $file; - } - } - foreach ($foreignFolderContents['files'] ?? [] as $foreignFiles) { - foreach ($foreignFiles as $file) { - $files[$file['identifier']]['foreign'] = $file; - } - } $demands = $this->demandsFactory->createDemand(); - foreach ($files as $sides) { - $fileRecord = $this->recordFactory->createFileRecord( - $sides['local'] ?? [], - $sides['foreign'] ?? [], - ); - if (null === $fileRecord) { - continue; - } - $demands->addDemand( - new SelectDemand( - 'sys_file', - 'storage = ' . $fileRecord->getProp('storage'), - 'identifier_hash', - sha1($fileRecord->getProp('identifier')), - $fileRecord, - ), - ); - $folderRecord->addChild($fileRecord); - } - $recordCollection = new RecordCollection(); + $demands->addDemand(new FoldersInFolderDemand($storage, $identifier, $folderRecord)); + $demands->addDemand(new FilesInFolderDemand($storage, $identifier, $folderRecord)); + $recordCollection = new RecordCollection(); $this->demandResolver->resolveDemand($demands, $recordCollection); - $this->recordTreeBuilder->findRecordsByTca($recordCollection); - - $filesMovedOutFromFolder = []; - $filesMovedIntoFolder = []; - - $sysFileRecords = $recordCollection->getRecords('sys_file'); - foreach ($sysFileRecords as $sysFileRecord) { - if ($sysFileRecord->getState() === Record::S_CHANGED) { - $localProps = $sysFileRecord->getLocalProps(); - $foreignProps = $sysFileRecord->getForeignProps(); - $localIdentifier = $localProps['identifier']; - $foreignIdentifier = $foreignProps['identifier']; - if ($localIdentifier !== $foreignIdentifier) { - if (!isset($files[$localIdentifier]['local'])) { - $filesMovedOutFromFolder[$localProps['storage']][] = $localIdentifier; - } - if (!isset($files[$foreignIdentifier]['foreign'])) { - $filesMovedIntoFolder[$foreignProps['storage']][] = $foreignIdentifier; - } - } - } - } - - if (!empty($filesMovedOutFromFolder)) { - $foundMovedOutFiles = $this->fileSystemInfoService->getFileInfo($filesMovedOutFromFolder); - foreach ($foundMovedOutFiles as $identifiers) { - foreach ($identifiers as $identifier => $fileInfo) { - $files[$identifier]['local'] = $fileInfo; - } - } - } - if (!empty($filesMovedIntoFolder)) { - $foundMovedIntoFiles = $this->foreignFileSystemInfoService->getFileInfo($filesMovedIntoFolder); - foreach ($foundMovedIntoFiles as $identifiers) { - foreach ($identifiers as $identifier => $fileInfo) { - $files[$identifier]['foreign'] = $fileInfo; - } - } - } - - foreach ($sysFileRecords as $sysFileRecord) { - if ($sysFileRecord->getState() === Record::S_CHANGED) { - $localProps = $sysFileRecord->getLocalProps(); - $foreignProps = $sysFileRecord->getForeignProps(); - $localIdentifier = $localProps['identifier']; - $foreignIdentifier = $foreignProps['identifier']; - if ($localIdentifier !== $foreignIdentifier) { - $localCombinedIdentifier = $localProps['storage'] . ':' . $localIdentifier; - $foreignCombinedIdentifier = $foreignProps['storage'] . ':' . $foreignIdentifier; - if (isset($files[$foreignIdentifier])) { - $files[$localIdentifier]['foreign'] = $files[$foreignIdentifier]['foreign']; - unset($files[$foreignIdentifier]); - $foreignRecordToRemove = $this->recordIndex->getRecord( - FileRecord::CLASSIFICATION, - $foreignCombinedIdentifier, - ); - if (null !== $foreignRecordToRemove) { - $folderRecord->removeChild($foreignRecordToRemove); - } - $localRecordToRemove = $this->recordIndex->getRecord( - FileRecord::CLASSIFICATION, - $localCombinedIdentifier, - ); - if (null !== $localRecordToRemove) { - $folderRecord->removeChild($localRecordToRemove); - } - $recordToAdd = $this->recordFactory->createFileRecord( - $files[$localIdentifier]['local'] ?? [], - $files[$localIdentifier]['foreign'] ?? [], - ); - if (null !== $recordToAdd) { - if (null !== $localRecordToRemove) { - $sysFileRecord->removeParent($localRecordToRemove); - } - if (null !== $foreignRecordToRemove) { - $sysFileRecord->removeParent($foreignRecordToRemove); - } - $recordToAdd->addChild($sysFileRecord); - $folderRecord->addChild($recordToAdd); - } - } - } - } - } - - $recordTree = new RecordTree([$folderRecord]); $this->eventDispatcher->dispatch(new RecordRelationsWereResolved($recordTree)); @@ -321,18 +152,17 @@ public function findFolderRecord(?string $combinedIdentifier, bool $onlyRoot = f public function findFileRecord(?string $combinedIdentifier): RecordTree { [$storage, $fileIdentifier] = explode(':', $combinedIdentifier); + $storage = (int)$storage; + $request = [$storage => [$fileIdentifier]]; - $localProps = []; - $localFileInfo = $this->fileSystemInfoService->getFileInfo([$storage => [$fileIdentifier]]); + $localFileInfo = $this->localFileInfoService->getFileInfo($request); + $fileInformation = $localFileInfo->getInfo($storage, $fileIdentifier); + $localProps = $fileInformation->toArray(); + + $foreignFileInfo = $this->foreignFileInfoService->getFileInfo($request); + $fileInformation = $foreignFileInfo->getInfo($storage, $fileIdentifier); + $foreignProps = $fileInformation->toArray(); - if (!empty($localFileInfo[$storage][$fileIdentifier])) { - $localProps = $localFileInfo[$storage][$fileIdentifier]; - } - $foreignProps = []; - $foreignFileInfo = $this->foreignFileSystemInfoService->getFileInfo([$storage => [$fileIdentifier]]); - if (!empty($foreignFileInfo[$storage][$fileIdentifier])) { - $foreignProps = $foreignFileInfo[$storage][$fileIdentifier]; - } $fileRecord = $this->recordFactory->createFileRecord($localProps, $foreignProps); if (null === $fileRecord) { return new RecordTree(); @@ -344,7 +174,7 @@ public function findFileRecord(?string $combinedIdentifier): RecordTree 'sys_file', 'storage = ' . $fileRecord->getProp('storage'), 'identifier_hash', - sha1($fileRecord->getProp('identifier')), + $fileRecord->getProp('identifier_hash'), $fileRecord, ), ); @@ -362,15 +192,13 @@ public function findFileRecord(?string $combinedIdentifier): RecordTree $foreignIdentifier = $foreignSysFileProps['identifier']; $foreignStorage = (int)$foreignSysFileProps['storage']; if ($localIdentifier !== $foreignIdentifier) { - $foreignFileInfo = $this->foreignFileSystemInfoService->getFileInfo( + $fileInfoCollection = $this->foreignFileInfoService->getFileInfo( [$foreignStorage => [$foreignIdentifier]], ); - if (!empty($foreignFileInfo[$foreignStorage][$foreignIdentifier])) { - $foreignProps = $foreignFileInfo[$foreignStorage][$foreignIdentifier]; - $fileRecord = $this->recordFactory->createFileRecord( - $localProps, - $foreignProps, - ); + $fileInfo = $fileInfoCollection->getInfo($foreignStorage, $foreignIdentifier); + if ($fileInfo instanceof FileInfo) { + $foreignProps = $fileInfo->toArray(); + $fileRecord = $this->recordFactory->createFileRecord($localProps, $foreignProps); if (null === $fileRecord) { return new RecordTree(); } @@ -387,4 +215,13 @@ public function findFileRecord(?string $combinedIdentifier): RecordTree return $recordTree; } + + protected function normalizeCombinedIdentifier(string $combinedIdentifier): string + { + if (str_contains($combinedIdentifier, ':')) { + [$storage, $name] = explode(':', $combinedIdentifier); + $combinedIdentifier = (int)$storage . ':/' . trim($name, '/'); + } + return $combinedIdentifier; + } } diff --git a/Classes/Component/Core/FileHandling/Service/FileSystemInfoService.php b/Classes/Component/Core/FileHandling/Service/FileSystemInfoService.php deleted file mode 100644 index ed16bc8b3..000000000 --- a/Classes/Component/Core/FileHandling/Service/FileSystemInfoService.php +++ /dev/null @@ -1,64 +0,0 @@ -falDriverService->getDriver($storageUid); - - try { - $folders = $driver->getFoldersInFolder($identifier); - $fileIdentifiers = $driver->getFilesInFolder($identifier); - } catch (InvalidArgumentException $exception) { - return []; - } - $files = $this->getFileInfo([$storageUid => $fileIdentifiers]); - return [ - 'folders' => $folders, - 'files' => $files, - ]; - } - - /** - * @param array> $files - */ - public function getFileInfo(array $files): array - { - $info = []; - - foreach ($files as $storage => $fileIdentifiers) { - $driver = $this->falDriverService->getDriver($storage); - foreach ($fileIdentifiers as $fileIdentifier) { - try { - $fileInfo = $driver->getFileInfoByIdentifier($fileIdentifier, self::PROPERTIES); - $fileInfo['sha1'] = $driver->hash($fileIdentifier, 'sha1'); - $fileInfo['publicUrl'] = $driver->getPublicUrl($fileInfo['identifier']); - $info[$storage][$fileIdentifier] = $fileInfo; - } catch (Throwable $exception) { - $info[$storage][$fileIdentifier] = null; - } - } - } - - return $info; - } -} diff --git a/Classes/Component/Core/FileHandling/Service/FileSystemInfoServiceInjection.php b/Classes/Component/Core/FileHandling/Service/FileSystemInfoServiceInjection.php deleted file mode 100644 index 2a5c366a1..000000000 --- a/Classes/Component/Core/FileHandling/Service/FileSystemInfoServiceInjection.php +++ /dev/null @@ -1,21 +0,0 @@ -fileSystemInfoService = $fileSystemInfoService; - } -} diff --git a/Classes/Component/Core/FileHandling/Service/ForeignFileSystemInfoService.php b/Classes/Component/Core/FileHandling/Service/ForeignFileSystemInfoService.php deleted file mode 100644 index e84c4569c..000000000 --- a/Classes/Component/Core/FileHandling/Service/ForeignFileSystemInfoService.php +++ /dev/null @@ -1,89 +0,0 @@ - $storageUid, 'folderIdentifier' => $identifier], - ); - return $this->executeEnvelope($envelope); - } - - public function fileExists(int $storageUid, string $identifier): bool - { - $envelope = new Envelope( - EnvelopeDispatcher::CMD_FILE_EXISTS, - ['storage' => $storageUid, 'fileIdentifier' => $identifier], - ); - return $this->executeEnvelope($envelope); - } - - public function listFolderContents(int $storageUid, string $identifier): array - { - $envelope = new Envelope( - EnvelopeDispatcher::CMD_LIST_FOLDER_CONTENTS, - ['storageUid' => $storageUid, 'identifier' => $identifier], - ); - return $this->executeEnvelope($envelope); - } - - public function getFileInfo(array $files): array - { - $envelope = new Envelope( - EnvelopeDispatcher::CMD_GET_FILE_INFO, - ['files' => $files], - ); - return $this->executeEnvelope($envelope); - } - - protected function executeEnvelope(Envelope $envelope) - { - $uid = $this->letterbox->sendEnvelope($envelope); - if (!is_int($uid)) { - throw new EnvelopeSendingFailedException(); - } - $request = new RemoteCommandRequest(ExecuteCommand::IDENTIFIER, [], [$uid]); - $response = $this->remoteCommandDispatcher->dispatch($request); - - if (!$response->isSuccessful()) { - throw new RuntimeException( - sprintf( - 'Could not execute RPC [%d]. Errors and Output: %s %s', - $uid, - $response->getErrorsString(), - $response->getOutputString(), - ), - 1655988997, - ); - } - - $envelope = $this->letterbox->receiveEnvelope($uid); - - if (false === $envelope) { - throw new In2publishCoreException('Could not receive envelope [' . $uid . ']', 1655990891); - } - return $envelope->getResponse(); - } -} diff --git a/Classes/Component/Core/FileHandling/Service/ForeignFileSystemInfoServiceInjection.php b/Classes/Component/Core/FileHandling/Service/ForeignFileSystemInfoServiceInjection.php deleted file mode 100644 index e8bafce47..000000000 --- a/Classes/Component/Core/FileHandling/Service/ForeignFileSystemInfoServiceInjection.php +++ /dev/null @@ -1,21 +0,0 @@ -foreignFileSystemInfoService = $foreignFileSystemInfoService; - } -} diff --git a/Classes/Component/Core/PreProcessing/CachedTcaPreProcessingServiceInjection.php b/Classes/Component/Core/PreProcessing/CachedTcaPreProcessingServiceInjection.php new file mode 100644 index 000000000..b555cb407 --- /dev/null +++ b/Classes/Component/Core/PreProcessing/CachedTcaPreProcessingServiceInjection.php @@ -0,0 +1,22 @@ +cachedTcaPreProcessingService = $cachedTcaPreprocessingService; + } +} diff --git a/Classes/Component/Core/PreProcessing/CachedTcaPreprocessingService.php b/Classes/Component/Core/PreProcessing/CachedTcaPreprocessingService.php new file mode 100644 index 000000000..98013a9d3 --- /dev/null +++ b/Classes/Component/Core/PreProcessing/CachedTcaPreprocessingService.php @@ -0,0 +1,80 @@ + + */ + protected array $compatibleTca; + /** + * Stores the part of the TCA that can not be used for relation resolving including reasons + * + * @var array[] + */ + protected array $incompatibleTca; + + public function getIncompatibleTcaParts(): array + { + $this->initialize(); + return $this->incompatibleTca; + } + + public function getCompatibleTcaParts(): array + { + $this->initialize(); + return $this->compatibleTca; + } + + protected function initialize(): void + { + if (isset($this->compatibleTca, $this->incompatibleTca)) { + return; + } + + if ($this->cache->has('tca_preprocessing_results')) { + $cacheEntry = $this->cache->get('tca_preprocessing_results'); + if ($this->isCacheValid($cacheEntry['compatibleTca'])) { + $this->compatibleTca = $cacheEntry['compatibleTca']; + $this->incompatibleTca = $cacheEntry['incompatibleTca']; + return; + } + } + + $tcaPreProcessingService = GeneralUtility::makeInstance(TcaPreProcessingService::class); + $this->compatibleTca = $tcaPreProcessingService->getCompatibleTcaParts(); + $this->incompatibleTca = $tcaPreProcessingService->getIncompatibleTcaParts(); + + $cacheEntry = [ + 'compatibleTca' => $this->compatibleTca, + 'incompatibleTca' => $this->incompatibleTca, + ]; + $this->cache->set('tca_preprocessing_results', $cacheEntry); + } + + protected function isCacheValid(array $compatibleTca): bool + { + foreach ($compatibleTca as $properties) { + foreach (array_column($properties, 'resolver') as $resolver) { + if ($resolver instanceof __PHP_Incomplete_Class) { + $this->cache->remove('tca_preprocessing_results'); + return false; + } + } + } + return true; + } +} diff --git a/Classes/Component/Core/PreProcessing/PreProcessor/FilePreprocessor.php b/Classes/Component/Core/PreProcessing/PreProcessor/FilePreprocessor.php new file mode 100644 index 000000000..d9bf27916 --- /dev/null +++ b/Classes/Component/Core/PreProcessing/PreProcessor/FilePreprocessor.php @@ -0,0 +1,29 @@ +container->get(InlineSelectResolver::class); + $resolver->configure( + $foreignTable, + $foreignField, + $foreignTableField, + '', + ); + return $resolver; + } +} diff --git a/Classes/Component/Core/PreProcessing/PreProcessor/LinkProcessor.php b/Classes/Component/Core/PreProcessing/PreProcessor/LinkProcessor.php new file mode 100755 index 000000000..5ca257499 --- /dev/null +++ b/Classes/Component/Core/PreProcessing/PreProcessor/LinkProcessor.php @@ -0,0 +1,20 @@ +container->get(TextResolver::class); + $resolver->configure($column); + return $resolver; + } +} diff --git a/Classes/Component/Core/PreProcessing/TcaPreProcessingService.php b/Classes/Component/Core/PreProcessing/TcaPreProcessingService.php index 955aa1295..24225ecb2 100644 --- a/Classes/Component/Core/PreProcessing/TcaPreProcessingService.php +++ b/Classes/Component/Core/PreProcessing/TcaPreProcessingService.php @@ -49,23 +49,23 @@ protected function getProcessor(string $type, string $table, string $column): ?T public function getIncompatibleTcaParts(): array { - if (!$this->initialized) { - $this->initialize(); - } + $this->initialize(); return $this->incompatibleTca; } public function getCompatibleTcaParts(): array { - if (!$this->initialized) { - $this->initialize(); - } + $this->initialize(); return $this->compatibleTca; } public function initialize(): void { + if ($this->initialized) { + return; + } + $this->initialized = true; $tables = $this->excludedTablesService->getAllNonExcludedTcaTables(); diff --git a/Classes/Component/Core/Publisher/AbstractFilesystemPublisher.php b/Classes/Component/Core/Publisher/AbstractFilesystemPublisher.php index e2a781fd4..bd4100733 100644 --- a/Classes/Component/Core/Publisher/AbstractFilesystemPublisher.php +++ b/Classes/Component/Core/Publisher/AbstractFilesystemPublisher.php @@ -11,47 +11,68 @@ use function bin2hex; use function get_class; +use function implode; use function json_encode; use function random_bytes; -abstract class AbstractFilesystemPublisher implements Publisher, FinishablePublisher +use const JSON_THROW_ON_ERROR; + +abstract class AbstractFilesystemPublisher implements Publisher, TransactionalPublisher { use ForeignDatabaseReconnectedInjection; use RemoteCommandDispatcherInjection; - protected string $requestToken; protected array $instructions = []; - public function __construct() + public function start(): void + { + if (!$this->foreignDatabase->isTransactionActive()) { + $this->foreignDatabase->beginTransaction(); + } + } + + public function cancel(): void { - $this->requestToken = bin2hex(random_bytes(16)); + if ($this->foreignDatabase->isTransactionActive()) { + $this->foreignDatabase->rollBack(); + } } public function finish(): void { - if (!empty($this->instructions)) { - $instructions = $this->instructions; - $this->instructions = []; - $data = []; - foreach ($instructions as $instruction) { - $class = get_class($instruction); - $configuration = json_encode($instruction->getConfiguration(), JSON_THROW_ON_ERROR); - $data[] = [ - 'request_token' => $this->requestToken, - 'crdate' => $GLOBALS['EXEC_TIME'], - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'instruction' => $class, - 'configuration' => $configuration, - ]; + if (empty($this->instructions)) { + if ($this->foreignDatabase->isTransactionActive()) { + $this->foreignDatabase->commit(); } + return; + } - $this->foreignDatabase->bulkInsert('tx_in2publishcore_filepublisher_instruction', $data); + $requestTokens = []; + $instructions = $this->instructions; + $this->instructions = []; + $data = []; + foreach ($instructions as $instruction) { + $requestTokens[] = $requestToken = bin2hex(random_bytes(16)); + $class = get_class($instruction); + $configuration = json_encode($instruction->getConfiguration(), JSON_THROW_ON_ERROR); + $data[] = [ + 'request_token' => $requestToken, + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => $class, + 'configuration' => $configuration, + ]; + } - $request = new RemoteCommandRequest('in2publish_core:core:falpublisher', [], [$this->requestToken]); - $response = $this->remoteCommandDispatcher->dispatch($request); - if (!$response->isSuccessful()) { - throw new FalPublisherExecutionFailedException($response); - } + $this->foreignDatabase->bulkInsert('tx_in2publishcore_filepublisher_instruction', $data); + if ($this->foreignDatabase->isTransactionActive()) { + $this->foreignDatabase->commit(); + } + + $request = new RemoteCommandRequest('in2publish_core:core:falpublisher', [], [implode(',', $requestTokens)]); + $response = $this->remoteCommandDispatcher->dispatch($request); + if (!$response->isSuccessful()) { + throw new FalPublisherExecutionFailedException($response); } } } diff --git a/Classes/Component/Core/Publisher/Command/FalPublisherCommand.php b/Classes/Component/Core/Publisher/Command/FalPublisherCommand.php index d3f612043..f4880efa9 100644 --- a/Classes/Component/Core/Publisher/Command/FalPublisherCommand.php +++ b/Classes/Component/Core/Publisher/Command/FalPublisherCommand.php @@ -5,7 +5,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Command; use In2code\In2publishCore\CommonInjection\LocalDatabaseInjection; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverServiceInjection; use In2code\In2publishCore\Component\Core\Publisher\Instruction\PublishInstruction; use In2code\In2publishCore\Service\Context\ContextServiceInjection; use Symfony\Component\Console\Command\Command; @@ -13,6 +13,9 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use TYPO3\CMS\Core\Database\Connection; + +use function explode; use function json_decode; class FalPublisherCommand extends Command @@ -28,18 +31,24 @@ public function isEnabled(): bool protected function configure(): void { - $this->addArgument('requestToken', InputArgument::REQUIRED); + $this->addArgument('requestTokens', InputArgument::REQUIRED); } protected function execute(InputInterface $input, OutputInterface $output): int { - $requestToken = $input->getArgument('requestToken'); + $requestTokens = $input->getArgument('requestTokens'); + $requestTokens = explode(',', $requestTokens); $query = $this->localDatabase->createQueryBuilder(); $query->select('*') ->from('tx_in2publishcore_filepublisher_instruction') - ->where($query->expr()->eq('request_token', $query->createNamedParameter($requestToken))); - $result = $query->execute(); + ->where( + $query->expr()->in( + 'request_token', + $query->createNamedParameter($requestTokens, Connection::PARAM_STR_ARRAY) + ) + ); + $result = $query->executeQuery(); $rows = $result->fetchAllAssociative(); /** @var array $instructions */ diff --git a/Classes/Component/Core/Publisher/DatabaseRecordPublisher.php b/Classes/Component/Core/Publisher/DatabaseRecordPublisher.php index 731c1c5d7..ea7494308 100644 --- a/Classes/Component/Core/Publisher/DatabaseRecordPublisher.php +++ b/Classes/Component/Core/Publisher/DatabaseRecordPublisher.php @@ -5,6 +5,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher; use In2code\In2publishCore\CommonInjection\ForeignDatabaseInjection; +use In2code\In2publishCore\CommonInjection\ForeignDatabaseReconnectedInjection; use In2code\In2publishCore\Component\Core\Record\Model\AbstractDatabaseRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; @@ -12,7 +13,7 @@ class DatabaseRecordPublisher implements Publisher, TransactionalPublisher { - use ForeignDatabaseInjection; + use ForeignDatabaseReconnectedInjection; public function canPublish(Record $record): bool { @@ -43,7 +44,9 @@ public function publish(Record $record) public function start(): void { - $this->foreignDatabase->beginTransaction(); + if (!$this->foreignDatabase->isTransactionActive()) { + $this->foreignDatabase->beginTransaction(); + } } public function cancel(): void diff --git a/Classes/Component/Core/Publisher/FileRecordPublisher.php b/Classes/Component/Core/Publisher/FileRecordPublisher.php index d42ebc936..6eeae6aea 100644 --- a/Classes/Component/Core/Publisher/FileRecordPublisher.php +++ b/Classes/Component/Core/Publisher/FileRecordPublisher.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverServiceInjection; use In2code\In2publishCore\Component\Core\Publisher\Instruction\AddFileInstruction; use In2code\In2publishCore\Component\Core\Publisher\Instruction\DeleteFileInstruction; use In2code\In2publishCore\Component\Core\Publisher\Instruction\MoveFileInstruction; @@ -12,12 +12,20 @@ use In2code\In2publishCore\Component\Core\Publisher\Instruction\ReplaceFileInstruction; use In2code\In2publishCore\Component\Core\Record\Model\FileRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; +use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandDispatcherInjection; use In2code\In2publishCore\Component\TemporaryAssetTransmission\AssetTransmitterInjection; +use Psr\Log\LoggerAwareInterface; +use Psr\Log\LoggerAwareTrait; -class FileRecordPublisher extends AbstractFilesystemPublisher +class FileRecordPublisher extends AbstractFilesystemPublisher implements LoggerAwareInterface { use AssetTransmitterInjection; use FalDriverServiceInjection; + use RemoteCommandDispatcherInjection; + use LoggerAwareTrait; + + /** @var array */ + protected array $transmittedFiles = []; public function canPublish(Record $record): bool { @@ -72,6 +80,7 @@ public function publish(Record $record): void ); } else { $transmitTemporaryFile = $this->transmitTemporaryFile($record); + $this->transmittedFiles[] = $transmitTemporaryFile; $instruction = new ReplaceAndRenameFileInstruction( $storage, $foreignFileIdentifier, @@ -81,6 +90,7 @@ public function publish(Record $record): void } } else { $transmitTemporaryFile = $this->transmitTemporaryFile($record); + $this->transmittedFiles[] = $transmitTemporaryFile; $instruction = new ReplaceFileInstruction( $storage, $localFileIdentifier, diff --git a/Classes/Component/Core/Publisher/FinishablePublisher.php b/Classes/Component/Core/Publisher/FinishablePublisher.php index cf154b0fc..fd1833af2 100644 --- a/Classes/Component/Core/Publisher/FinishablePublisher.php +++ b/Classes/Component/Core/Publisher/FinishablePublisher.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher; -interface FinishablePublisher +interface FinishablePublisher extends Publisher { public function finish(): void; } diff --git a/Classes/Component/Core/Publisher/Instruction/AddFileInstruction.php b/Classes/Component/Core/Publisher/Instruction/AddFileInstruction.php index 8e512b7d6..140055efb 100644 --- a/Classes/Component/Core/Publisher/Instruction/AddFileInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/AddFileInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; use TYPO3\CMS\Core\Resource\Driver\DriverInterface; use TYPO3\CMS\Core\Utility\PathUtility; diff --git a/Classes/Component/Core/Publisher/Instruction/AddFolderInstruction.php b/Classes/Component/Core/Publisher/Instruction/AddFolderInstruction.php index b119473ad..7b28152da 100644 --- a/Classes/Component/Core/Publisher/Instruction/AddFolderInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/AddFolderInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; use TYPO3\CMS\Core\Utility\PathUtility; class AddFolderInstruction implements PublishInstruction diff --git a/Classes/Component/Core/Publisher/Instruction/DeleteFileInstruction.php b/Classes/Component/Core/Publisher/Instruction/DeleteFileInstruction.php index 797fa3ab9..cc27f4aa8 100644 --- a/Classes/Component/Core/Publisher/Instruction/DeleteFileInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/DeleteFileInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; class DeleteFileInstruction implements PublishInstruction { diff --git a/Classes/Component/Core/Publisher/Instruction/DeleteFolderInstruction.php b/Classes/Component/Core/Publisher/Instruction/DeleteFolderInstruction.php index 030937de6..627ce0561 100644 --- a/Classes/Component/Core/Publisher/Instruction/DeleteFolderInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/DeleteFolderInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; class DeleteFolderInstruction implements PublishInstruction { diff --git a/Classes/Component/Core/Publisher/Instruction/MoveFileInstruction.php b/Classes/Component/Core/Publisher/Instruction/MoveFileInstruction.php index bb6347dd8..edd976ab3 100644 --- a/Classes/Component/Core/Publisher/Instruction/MoveFileInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/MoveFileInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; use TYPO3\CMS\Core\Utility\PathUtility; class MoveFileInstruction implements PublishInstruction diff --git a/Classes/Component/Core/Publisher/Instruction/PublishInstruction.php b/Classes/Component/Core/Publisher/Instruction/PublishInstruction.php index 05e4b40b4..2b4870b9e 100644 --- a/Classes/Component/Core/Publisher/Instruction/PublishInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/PublishInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; interface PublishInstruction { diff --git a/Classes/Component/Core/Publisher/Instruction/ReplaceAndRenameFileInstruction.php b/Classes/Component/Core/Publisher/Instruction/ReplaceAndRenameFileInstruction.php index fb51bfce5..68081751a 100644 --- a/Classes/Component/Core/Publisher/Instruction/ReplaceAndRenameFileInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/ReplaceAndRenameFileInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; use TYPO3\CMS\Core\Utility\PathUtility; class ReplaceAndRenameFileInstruction implements PublishInstruction diff --git a/Classes/Component/Core/Publisher/Instruction/ReplaceFileInstruction.php b/Classes/Component/Core/Publisher/Instruction/ReplaceFileInstruction.php index fd73814b4..26d86f661 100644 --- a/Classes/Component/Core/Publisher/Instruction/ReplaceFileInstruction.php +++ b/Classes/Component/Core/Publisher/Instruction/ReplaceFileInstruction.php @@ -4,7 +4,7 @@ namespace In2code\In2publishCore\Component\Core\Publisher\Instruction; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\FalDriverService; class ReplaceFileInstruction implements PublishInstruction { diff --git a/Classes/Component/Core/Publisher/PublisherCollection.php b/Classes/Component/Core/Publisher/PublisherCollection.php index 2e903cdee..c640fc8a6 100644 --- a/Classes/Component/Core/Publisher/PublisherCollection.php +++ b/Classes/Component/Core/Publisher/PublisherCollection.php @@ -46,6 +46,16 @@ public function addPublisher(Publisher $publisher): void krsort($this->publishers); } + public function canPublish(Record $record): bool + { + foreach ($this->publishers as $publisher) { + if ($publisher->canPublish($record)) { + return true; + } + } + return false; + } + public function publish(Record $record): void { foreach ($this->publishers as $publisher) { diff --git a/Classes/Component/Core/Publisher/PublisherService.php b/Classes/Component/Core/Publisher/PublisherService.php index 4c7b1c1f2..68494e6ea 100644 --- a/Classes/Component/Core/Publisher/PublisherService.php +++ b/Classes/Component/Core/Publisher/PublisherService.php @@ -7,7 +7,7 @@ use In2code\In2publishCore\CommonInjection\EventDispatcherInjection; use In2code\In2publishCore\Component\Core\Record\Model\Record; use In2code\In2publishCore\Component\Core\RecordTree\RecordTree; -use In2code\In2publishCore\Component\PostPublishTaskExecution\Service\TaskExecutionService; +use In2code\In2publishCore\Component\PostPublishTaskExecution\Service\TaskExecutionServiceInjection; use In2code\In2publishCore\Event\PublishingOfOneRecordBegan; use In2code\In2publishCore\Event\PublishingOfOneRecordEnded; use In2code\In2publishCore\Event\RecordWasPublished; @@ -16,12 +16,21 @@ use In2code\In2publishCore\Event\RecursiveRecordPublishingEnded; use Throwable; +use function debug_backtrace; +use function user_error; + +use const DEBUG_BACKTRACE_IGNORE_ARGS; +use const E_USER_DEPRECATED; + class PublisherService { use EventDispatcherInjection; + use TaskExecutionServiceInjection; + protected const DEPRECATION_DIRECTLY_INVOKED = '%s%s%s directly invoked \In2code\In2publishCore\Component\Core\Publisher\PublisherService::publishRecordTree. This will not work in in2publish_core v13 anymore. Please use \In2code\In2publishCore\Component\Core\Publisher\PublisherService::publish instead.'; protected PublisherCollection $publisherCollection; - protected TaskExecutionService $taskExecutionService; + /** @var array> */ + protected array $visitedRecords = []; public function __construct() { @@ -29,36 +38,46 @@ public function __construct() } /** - * @codeCoverageIgnore - * @noinspection PhpUnused + * Called by the DI container when constructing this service */ - public function injectTaskExecutionService(TaskExecutionService $taskExecutionService): void - { - $this->taskExecutionService = $taskExecutionService; - } - public function addPublisher(Publisher $publisher): void { $this->publisherCollection->addPublisher($publisher); } + /** + * @throws Throwable + */ public function publish(PublishingContext $publishingContext): void { $recordTree = $publishingContext->getRecordTree(); - $this->publishRecordTree($recordTree); + $this->publishRecordTree($recordTree, $publishingContext->publishChildPages); } + /** + * @throws Throwable + * @internal This method will be made non-public in in2publish_core v13. Use publish() with PublishingContext + * instead. + */ public function publishRecordTree(RecordTree $recordTree, bool $includeChildPages = false): void { + // Check if method was called by something else than self::publish and trigger a deprecation. + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $frame = $backtrace[1]; + if ($frame['function'] !== 'publish' || $frame['class'] !== self::class) { + $message = sprintf(self::DEPRECATION_DIRECTLY_INVOKED, $frame['class'], $frame['type'], $frame['function']); + user_error($message, E_USER_DEPRECATED); + } + unset($backtrace, $frame, $message); + $this->eventDispatcher->dispatch(new RecursiveRecordPublishingBegan($recordTree)); $this->publisherCollection->start(); try { - $visitedRecords = []; foreach ($recordTree->getChildren() as $records) { foreach ($records as $record) { - $this->publishRecord($record, $visitedRecords, $includeChildPages); + $this->publishRecord($record, $includeChildPages); } } } catch (Throwable $exception) { @@ -79,15 +98,15 @@ public function publishRecordTree(RecordTree $recordTree, bool $includeChildPage $this->taskExecutionService->runTasks(); } - protected function publishRecord(Record $record, array &$visitedRecords = [], bool $includeChildPages = false): void + protected function publishRecord(Record $record, bool $includeChildPages = false): void { $classification = $record->getClassification(); $id = $record->getId(); - if (isset($visitedRecords[$classification][$id])) { + if (isset($this->visitedRecords[$classification][$id])) { return; } - $visitedRecords[$classification][$id] = true; + $this->visitedRecords[$classification][$id] = true; $this->eventDispatcher->dispatch(new RecordWasSelectedForPublishing($record)); @@ -106,16 +125,11 @@ protected function publishRecord(Record $record, array &$visitedRecords = [], bo } foreach ($record->getChildren() as $table => $children) { - if ('pages' !== $table) { - foreach ($children as $child) { - $this->publishRecord($child, $visitedRecords, true); - } - } else { - if ($includeChildPages === true) { - foreach ($children as $child) { - $this->publishRecord($child, $visitedRecords, true); - } - } + if ('pages' === $table && !$includeChildPages) { + continue; + } + foreach ($children as $child) { + $this->publishRecord($child, true); } } } diff --git a/Classes/Component/Core/Publisher/PublishingContext.php b/Classes/Component/Core/Publisher/PublishingContext.php index 5acb66b9d..99fb605fd 100644 --- a/Classes/Component/Core/Publisher/PublishingContext.php +++ b/Classes/Component/Core/Publisher/PublishingContext.php @@ -9,10 +9,16 @@ class PublishingContext { protected RecordTree $recordTree; + public bool $publishChildPages = false; - public function __construct(RecordTree $recordTree) - { + public function __construct( + RecordTree $recordTree, + bool $publishChildPages = null + ) { $this->recordTree = $recordTree; + if (null !== $publishChildPages) { + $this->publishChildPages = $publishChildPages; + } } public function getRecordTree(): RecordTree diff --git a/Classes/Component/Core/Record/Factory/RecordFactory.php b/Classes/Component/Core/Record/Factory/RecordFactory.php index 6e99513ec..1bc500e86 100755 --- a/Classes/Component/Core/Record/Factory/RecordFactory.php +++ b/Classes/Component/Core/Record/Factory/RecordFactory.php @@ -36,6 +36,7 @@ use In2code\In2publishCore\Component\Core\Record\Model\MmDatabaseRecord; use In2code\In2publishCore\Component\Core\Record\Model\PageTreeRootRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; +use In2code\In2publishCore\Component\Core\Record\Model\StorageRootFolderRecord; use In2code\In2publishCore\Component\Core\RecordIndexInjection; use In2code\In2publishCore\Event\DecideIfRecordShouldBeIgnored; use In2code\In2publishCore\Event\RecordWasCreated; @@ -97,7 +98,8 @@ public function createMmRecord( public function createFileRecord(array $localProps, array $foreignProps): ?FileRecord { - $record = new FileRecord($localProps, $foreignProps); + $ignoredFields = $this->ignoredFieldsService->getIgnoredFields(FileRecord::CLASSIFICATION); + $record = new FileRecord($localProps, $foreignProps, $ignoredFields); if ($this->shouldIgnoreRecord($record)) { return null; } @@ -105,12 +107,13 @@ public function createFileRecord(array $localProps, array $foreignProps): ?FileR return $record; } - public function createFolderRecord( - string $combinedIdentifier, - array $localProps, - array $foreignProps - ): ?FolderRecord { - $record = new FolderRecord($combinedIdentifier, $localProps, $foreignProps); + public function createFolderRecord(array $localProps, array $foreignProps): ?FolderRecord + { + if ('/' === ($localProps['identifier'] ?? null) || '/' === ($foreignProps['identifier'] ?? null)) { + $record = new StorageRootFolderRecord($localProps, $foreignProps); + } else { + $record = new FolderRecord($localProps, $foreignProps); + } if ($this->shouldIgnoreRecord($record)) { return null; } diff --git a/Classes/Component/Core/Record/Model/AbstractRecord.php b/Classes/Component/Core/Record/Model/AbstractRecord.php index 259de68fc..7967cd351 100644 --- a/Classes/Component/Core/Record/Model/AbstractRecord.php +++ b/Classes/Component/Core/Record/Model/AbstractRecord.php @@ -6,6 +6,8 @@ use Generator; use In2code\In2publishCore\Component\Core\Reason\Reasons; +use In2code\In2publishCore\Component\Core\Record\Iterator\IterationControls\StopIteration; +use In2code\In2publishCore\Component\Core\Record\Iterator\RecordIterator; use In2code\In2publishCore\Component\Core\Record\Model\Extension\RecordExtensionTrait; use In2code\In2publishCore\Event\CollectReasonsWhyTheRecordIsNotPublishable; use LogicException; @@ -15,6 +17,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use function array_diff_assoc; +use function array_diff_key; +use function array_flip; use function array_keys; use function array_pop; use function count; @@ -33,7 +37,6 @@ abstract class AbstractRecord implements Record * @var array */ protected array $dependencies = []; - protected bool $hasBeenAskedForRecursiveState = false; /** * @var array> */ @@ -47,6 +50,8 @@ abstract class AbstractRecord implements Record */ protected array $translations = []; protected ?Record $translationParent = null; + /** @var array */ + protected array $ignoredProps = []; /** @var array{reasons: Reasons} */ protected array $rtc = []; @@ -162,6 +167,14 @@ public function isChanged(): bool return $this->localProps !== $this->foreignProps; } + protected function calculateChangedProps(): array + { + $ignoredProps = array_flip($this->ignoredProps); + $relevantLocalProps = array_diff_key($this->localProps, $ignoredProps); + $relevantForeignProps = array_diff_key($this->foreignProps, $ignoredProps); + return array_keys(array_diff_assoc($relevantLocalProps, $relevantForeignProps)); + } + protected function calculateState(): string { $localRecordExists = [] !== $this->localProps; @@ -220,24 +233,21 @@ public function getState(): string public function getStateRecursive(): string { $state = $this->getState(); - if ($state !== Record::S_UNCHANGED || $this->hasBeenAskedForRecursiveState) { + if ($state !== Record::S_UNCHANGED) { return $state; } - $this->hasBeenAskedForRecursiveState = true; - foreach ($this->children as $table => $records) { - if ('pages' === $table) { - continue; + $recordState = Record::S_UNCHANGED; + $recordIterator = new RecordIterator(); + $recordIterator->recurse($this, function (Record $record) use (&$recordState) { + if ($record->getClassification() === 'pages') { + return; } - foreach ($records as $record) { - $state = $record->getStateRecursive(); - if ($state !== Record::S_UNCHANGED) { - $this->hasBeenAskedForRecursiveState = false; - return Record::S_CHANGED; - } + if ($record->getState() !== Record::S_UNCHANGED) { + $recordState = Record::S_CHANGED; + throw new StopIteration(); } - } - $this->hasBeenAskedForRecursiveState = false; - return Record::S_UNCHANGED; + }); + return $recordState; } public function calculateDependencies(): array @@ -257,7 +267,7 @@ public function getTransOrigPointer(): int public function getChangedProps(): array { - return array_keys(array_diff_assoc($this->localProps, $this->foreignProps)); + return $this->calculateChangedProps(); } public function getDependencies(): array diff --git a/Classes/Component/Core/Record/Model/DatabaseRecord.php b/Classes/Component/Core/Record/Model/DatabaseRecord.php index 05eebfc11..3a321adb3 100644 --- a/Classes/Component/Core/Record/Model/DatabaseRecord.php +++ b/Classes/Component/Core/Record/Model/DatabaseRecord.php @@ -4,15 +4,10 @@ namespace In2code\In2publishCore\Component\Core\Record\Model; -use function array_diff_assoc; -use function array_diff_key; -use function array_flip; -use function array_keys; - class DatabaseRecord extends AbstractDatabaseRecord implements DatabaseEntityRecord { protected int $id; - protected array $ignoredProps; + /** @var array */ protected array $changedProps; public function __construct(string $table, int $id, array $localProps, array $foreignProps, array $ignoredProps) @@ -24,10 +19,7 @@ public function __construct(string $table, int $id, array $localProps, array $fo $this->foreignProps = $foreignProps; $this->ignoredProps = $ignoredProps; - $relevantLocalProps = array_diff_key($this->localProps, array_flip($ignoredProps)); - $relevantForeignProps = array_diff_key($this->foreignProps, array_flip($ignoredProps)); - $this->changedProps = array_keys(array_diff_assoc($relevantLocalProps, $relevantForeignProps)); - + $this->changedProps = $this->calculateChangedProps(); $this->state = $this->calculateState(); $this->dependencies = $this->calculateDependencies(); } diff --git a/Classes/Component/Core/Record/Model/FileRecord.php b/Classes/Component/Core/Record/Model/FileRecord.php index 415d89730..fcec77489 100644 --- a/Classes/Component/Core/Record/Model/FileRecord.php +++ b/Classes/Component/Core/Record/Model/FileRecord.php @@ -12,10 +12,15 @@ class FileRecord extends AbstractRecord { public const CLASSIFICATION = '_file'; - public function __construct(array $localProps, array $foreignProps) + /** @var array */ + protected array $changedProps; + + public function __construct(array $localProps, array $foreignProps, array $ignoredProps = []) { $this->localProps = $localProps; $this->foreignProps = $foreignProps; + $this->ignoredProps = $ignoredProps; + $this->changedProps = $this->calculateChangedProps(); $this->state = $this->calculateState(); } @@ -64,4 +69,9 @@ public function isMovedToDifferentFolder(): bool return PathUtility::dirname($this->localProps['identifier']) !== PathUtility::dirname($this->foreignProps['identifier']); } + + public function getChangedProps(): array + { + return $this->changedProps; + } } diff --git a/Classes/Component/Core/Record/Model/FolderRecord.php b/Classes/Component/Core/Record/Model/FolderRecord.php index b39737e74..afe3ca872 100644 --- a/Classes/Component/Core/Record/Model/FolderRecord.php +++ b/Classes/Component/Core/Record/Model/FolderRecord.php @@ -4,16 +4,17 @@ namespace In2code\In2publishCore\Component\Core\Record\Model; +use In2code\In2publishCore\Component\Core\Record\Iterator\IterationControls\SkipChildren; +use In2code\In2publishCore\Component\Core\Record\Iterator\IterationControls\StopIteration; +use In2code\In2publishCore\Component\Core\Record\Iterator\RecordIterator; use LogicException; class FolderRecord extends AbstractRecord { public const CLASSIFICATION = '_folder'; - private string $combinedIdentifier; - public function __construct(string $combinedIdentifier, array $localProps, array $foreignProps) + public function __construct(array $localProps, array $foreignProps) { - $this->combinedIdentifier = $combinedIdentifier; $this->localProps = $localProps; $this->foreignProps = $foreignProps; @@ -25,9 +26,9 @@ public function getClassification(): string return self::CLASSIFICATION; } - public function getId() + public function getId(): string { - return $this->combinedIdentifier; + return $this->getProp('storage') . ':' . $this->getProp('identifier'); } public function getForeignIdentificationProps(): array @@ -35,4 +36,31 @@ public function getForeignIdentificationProps(): array throw new LogicException('NOT IMPLEMENTED'); return $this->getId(); } + + public function getStateRecursive(): string + { + $state = $this->getState(); + if ($state !== Record::S_UNCHANGED) { + return $state; + } + $children = $this->getChildren(); + unset($children[FolderRecord::CLASSIFICATION], $children[FileRecord::CLASSIFICATION]); + + $stateRecursive = Record::S_UNCHANGED; + $recordIterator = new RecordIterator(); + foreach ($children as $childRecords) { + foreach ($childRecords as $childRecord) { + $recordIterator->recurse($childRecord, function (Record $record) use (&$stateRecursive) { + if ($record instanceof FolderRecord || $record instanceof FileRecord) { + throw new SkipChildren(); + } + if ($record->getState() !== Record::S_UNCHANGED) { + $stateRecursive = Record::S_CHANGED; + throw new StopIteration(); + } + }); + } + } + return $stateRecursive; + } } diff --git a/Classes/Component/Core/Record/Model/PageTreeRootRecord.php b/Classes/Component/Core/Record/Model/PageTreeRootRecord.php index 6417d5602..31ded5ec7 100644 --- a/Classes/Component/Core/Record/Model/PageTreeRootRecord.php +++ b/Classes/Component/Core/Record/Model/PageTreeRootRecord.php @@ -18,4 +18,9 @@ public function getPageId(): int { return 0; } + + public function __toString(): string + { + return $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']; + } } diff --git a/Classes/Component/Core/Record/Model/StorageRootFolderRecord.php b/Classes/Component/Core/Record/Model/StorageRootFolderRecord.php new file mode 100644 index 000000000..643ea370c --- /dev/null +++ b/Classes/Component/Core/Record/Model/StorageRootFolderRecord.php @@ -0,0 +1,9 @@ +children[$table][$id] ?? null; + return $this->children[$classification][$id] ?? null; } public function getCurrentPage(): ?Record diff --git a/Classes/Component/Core/RecordTree/RecordTreeBuilder.php b/Classes/Component/Core/RecordTree/RecordTreeBuilder.php index 053b10c08..2ac5b6e83 100644 --- a/Classes/Component/Core/RecordTree/RecordTreeBuilder.php +++ b/Classes/Component/Core/RecordTree/RecordTreeBuilder.php @@ -16,7 +16,7 @@ use In2code\In2publishCore\Component\Core\RecordIndexInjection; use In2code\In2publishCore\Component\Core\Service\RelevantTablesServiceInjection; use In2code\In2publishCore\Event\RecordRelationsWereResolved; -use In2code\In2publishCore\Service\Configuration\TcaServiceInjection; +use In2code\In2publishCore\Service\Configuration\PageTypeServiceInjection; use In2code\In2publishCore\Service\Database\RawRecordServiceInjection; use function array_flip; @@ -33,7 +33,7 @@ class RecordTreeBuilder use DemandsFactoryInjection; use DemandResolverInjection; use DemandBuilderInjection; - use TcaServiceInjection; + use PageTypeServiceInjection; use RawRecordServiceInjection; public function buildRecordTree(RecordTreeBuildRequest $request): RecordTree @@ -155,7 +155,7 @@ public function findAllRecordsOnPages(): RecordCollection $tables = array_flip($tablesAsKeys); foreach ($pages as $page) { - $tablesAllowedOnPage = $this->tcaService->getTablesAllowedOnPage( + $tablesAllowedOnPage = $this->pageTypeService->getTablesAllowedOnPage( $page->getId(), $page->getProp('doktype'), ); diff --git a/Classes/Component/Core/Repository/SingleDatabaseRepository.php b/Classes/Component/Core/Repository/SingleDatabaseRepository.php index 8c6113304..a33f84691 100644 --- a/Classes/Component/Core/Repository/SingleDatabaseRepository.php +++ b/Classes/Component/Core/Repository/SingleDatabaseRepository.php @@ -73,7 +73,7 @@ public function findByProperty( $query->orderBy('uid'); } - $result = $query->execute(); + $result = $query->executeQuery(); return array_column($result->fetchAllAssociative(), null, 'uid'); } @@ -127,7 +127,7 @@ public function findByPropertyWithJoin( $query->orderBy('uid'); } - $result = $query->execute(); + $result = $query->executeQuery(); $rows = $result->fetchAllAssociative(); @@ -198,7 +198,7 @@ public function findByWhere($table, string $andWhere): array $query->orderBy('uid'); } - $result = $query->execute(); + $result = $query->executeQuery(); return array_column($result->fetchAllAssociative(), null, 'uid'); } } diff --git a/Classes/Component/Core/Resolver/AbstractResolver.php b/Classes/Component/Core/Resolver/AbstractResolver.php index 790ba5fe2..447203827 100644 --- a/Classes/Component/Core/Resolver/AbstractResolver.php +++ b/Classes/Component/Core/Resolver/AbstractResolver.php @@ -11,7 +11,7 @@ abstract class AbstractResolver implements Resolver { - private array $metaInfo = []; + protected array $metaInfo = []; public function __construct() { @@ -23,7 +23,11 @@ public function __construct() && $frame['object'] instanceof AbstractProcessor && 'buildResolver' === $frame['function'] ) { - $this->metaInfo['builtBy'] = $frame; + $this->metaInfo['builtBy'] = [ + 'class' => $frame['class'], + 'args' => $frame['args'], + ]; + break; } } } diff --git a/Classes/Component/Core/Resolver/FlexResolver.php b/Classes/Component/Core/Resolver/FlexResolver.php index ea22fd276..e7ec8ac5a 100644 --- a/Classes/Component/Core/Resolver/FlexResolver.php +++ b/Classes/Component/Core/Resolver/FlexResolver.php @@ -8,10 +8,15 @@ use In2code\In2publishCore\CommonInjection\FlexFormToolsInjection; use In2code\In2publishCore\Component\Core\Demand\Demands; use In2code\In2publishCore\Component\Core\Demand\ResolverServiceInjection; +use In2code\In2publishCore\Component\Core\PreProcessing\Service\FlexFormFlatteningService; use In2code\In2publishCore\Component\Core\PreProcessing\Service\FlexFormFlatteningServiceInjection; use In2code\In2publishCore\Component\Core\Record\Model\DatabaseEntityRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; use In2code\In2publishCore\Component\Core\Record\Model\VirtualFlexFormRecord; +use In2code\In2publishCore\Component\Core\Service\ResolverService; +use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools; +use TYPO3\CMS\Core\Service\FlexFormService; +use TYPO3\CMS\Core\Utility\GeneralUtility; use function array_keys; use function array_merge; @@ -87,7 +92,7 @@ public function resolve(Demands $demands, Record $record): void $flexFormTableName = $this->table . '/' . $this->column . '/' . $dataStructureKey; $virtualRecord = new VirtualFlexFormRecord($record, $flexFormTableName, $localValues, $foreignValues); - $resolvers = $this->resolverService->getResolversForTable($flexFormTableName); + $resolvers = $this->resolverService->getResolversForClassification($flexFormTableName); $expressions = []; foreach ($resolvers as $field => $resolver) { @@ -137,4 +142,25 @@ protected function convertAndFlattenFlexFormData($data, $record): array } return $data; } + + public function __serialize(): array + { + return [ + 'metaInfo' => $this->metaInfo, + 'table' => $this->table, + 'column' => $this->column, + 'processedTca' => $this->processedTca, + ]; + } + + public function __unserialize(array $data): void + { + $this->metaInfo = $data['metaInfo']; + unset($data['metaInfo']); + $this->configure(...$data); + $this->injectResolverService(GeneralUtility::makeInstance(ResolverService::class)); + $this->injectFlexFormFlatteningService(GeneralUtility::makeInstance(FlexFormFlatteningService::class)); + $this->injectFlexFormService(GeneralUtility::makeInstance(FlexFormService::class)); + $this->injectFlexFormTools(GeneralUtility::makeInstance(FlexFormTools::class)); + } } diff --git a/Classes/Component/Core/Resolver/SelectMmResolver.php b/Classes/Component/Core/Resolver/SelectMmResolver.php index e53648931..fa2ad20a8 100644 --- a/Classes/Component/Core/Resolver/SelectMmResolver.php +++ b/Classes/Component/Core/Resolver/SelectMmResolver.php @@ -8,7 +8,9 @@ use In2code\In2publishCore\Component\Core\Demand\Type\JoinDemand; use In2code\In2publishCore\Component\Core\PreProcessing\PreProcessor\AbstractProcessor; use In2code\In2publishCore\Component\Core\Record\Model\Record; +use In2code\In2publishCore\Service\ReplaceMarkersService; use In2code\In2publishCore\Service\ReplaceMarkersServiceInject; +use TYPO3\CMS\Core\Utility\GeneralUtility; use function preg_match; @@ -64,4 +66,24 @@ public function resolve(Demands $demands, Record $record): void ); $demands->addDemand($demand); } + + public function __serialize(): array + { + return [ + 'metaInfo' => $this->metaInfo, + 'foreignTableWhere' => $this->foreignTableWhere, + 'column' => $this->column, + 'mmTable' => $this->mmTable, + 'foreignTable' => $this->foreignTable, + 'selectField' => $this->selectField, + ]; + } + + public function __unserialize(array $data): void + { + $this->metaInfo = $data['metaInfo']; + unset($data['metaInfo']); + $this->configure(...$data); + $this->injectReplaceMarkersService(GeneralUtility::makeInstance(ReplaceMarkersService::class)); + } } diff --git a/Classes/Component/Core/Resolver/SelectResolver.php b/Classes/Component/Core/Resolver/SelectResolver.php index c2eb4a090..b503231b0 100644 --- a/Classes/Component/Core/Resolver/SelectResolver.php +++ b/Classes/Component/Core/Resolver/SelectResolver.php @@ -8,6 +8,7 @@ use In2code\In2publishCore\Component\Core\Demand\Type\SelectDemand; use In2code\In2publishCore\Component\Core\PreProcessing\PreProcessor\AbstractProcessor; use In2code\In2publishCore\Component\Core\Record\Model\Record; +use In2code\In2publishCore\Service\ReplaceMarkersService; use In2code\In2publishCore\Service\ReplaceMarkersServiceInject; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -60,4 +61,22 @@ public function resolve(Demands $demands, Record $record): void $demands->addDemand(new SelectDemand($this->foreignTable, $additionalWhere, 'uid', $splitValue, $record)); } } + + public function __serialize(): array + { + return [ + 'metaInfo' => $this->metaInfo, + 'column' => $this->column, + 'foreignTable' => $this->foreignTable, + 'foreignTableWhere' => $this->foreignTableWhere, + ]; + } + + public function __unserialize(array $data): void + { + $this->metaInfo = $data['metaInfo']; + unset($data['metaInfo']); + $this->configure(...$data); + $this->injectReplaceMarkersService(GeneralUtility::makeInstance(ReplaceMarkersService::class)); + } } diff --git a/Classes/Component/Core/Resolver/StaticResolver.php b/Classes/Component/Core/Resolver/StaticResolver.php new file mode 100644 index 000000000..74d95f253 --- /dev/null +++ b/Classes/Component/Core/Resolver/StaticResolver.php @@ -0,0 +1,17 @@ +addDemand(new SelectDemand('sys_file_storage', '', 'uid', $record->getProp('storage'), $record)); + } + } +} diff --git a/Classes/Component/Core/Resolver/TextResolver.php b/Classes/Component/Core/Resolver/TextResolver.php index 944bd6b84..26e14ecf6 100644 --- a/Classes/Component/Core/Resolver/TextResolver.php +++ b/Classes/Component/Core/Resolver/TextResolver.php @@ -9,6 +9,8 @@ use In2code\In2publishCore\Component\Core\Demand\Type\SelectDemand; use In2code\In2publishCore\Component\Core\Record\Model\Record; use In2code\In2publishCore\Event\DemandsForTextWereCollected; +use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; +use TYPO3\CMS\Core\Utility\GeneralUtility; use function htmlspecialchars_decode; use function parse_str; @@ -69,4 +71,20 @@ protected function findRelationsInText(Demands $demands, string $text, Record $r $this->eventDispatcher->dispatch(new DemandsForTextWereCollected($demands, $record, $text)); } + + public function __serialize(): array + { + return [ + 'metaInfo' => $this->metaInfo, + 'column' => $this->column, + ]; + } + + public function __unserialize(array $data): void + { + $this->metaInfo = $data['metaInfo']; + unset($data['metaInfo']); + $this->configure(...$data); + $this->injectEventDispatcher(GeneralUtility::makeInstance(EventDispatcher::class)); + } } diff --git a/Classes/Component/Core/Service/ResolverService.php b/Classes/Component/Core/Service/ResolverService.php index cb9475031..848542097 100644 --- a/Classes/Component/Core/Service/ResolverService.php +++ b/Classes/Component/Core/Service/ResolverService.php @@ -4,30 +4,44 @@ namespace In2code\In2publishCore\Component\Core\Service; -use In2code\In2publishCore\Component\Core\PreProcessing\TcaPreProcessingServiceInjection; +use In2code\In2publishCore\Component\Core\PreProcessing\CachedTcaPreProcessingServiceInjection; use In2code\In2publishCore\Component\Core\Resolver\Resolver; +use In2code\In2publishCore\Component\Core\Resolver\StaticResolver; class ResolverService { use RelevantTablesServiceInjection; - use TcaPreProcessingServiceInjection; + use CachedTcaPreProcessingServiceInjection; /** * @var array> */ - protected array $resolvers; + protected array $resolvers = []; + + /** + * @noinspection PhpUnused Called via DI + * @see \In2code\In2publishCore\Component\Core\DependencyInjection\StaticResolverPass + */ + public function addStaticResolver(StaticResolver $staticResolver): void + { + foreach ($staticResolver->getTargetClassification() as $classification) { + foreach ($staticResolver->getTargetProperties() as $property) { + $this->resolvers[$classification][$property] = $staticResolver; + } + } + } public function initializeObject(): void { - $compatibleTcaParts = $this->tcaPreProcessingService->getCompatibleTcaParts(); - foreach ($compatibleTcaParts as $table => $properties) { + $compatibleTcaParts = $this->cachedTcaPreProcessingService->getCompatibleTcaParts(); + foreach ($compatibleTcaParts as $classification => $properties) { foreach ($properties as $property => $array) { /** @var Resolver $resolver */ $resolver = $array['resolver']; $targetTables = $resolver->getTargetTables(); $relevantTables = $this->relevantTablesService->removeExcludedAndEmptyTables($targetTables); if (!empty($relevantTables)) { - $this->resolvers[$table][$property] = $resolver; + $this->resolvers[$classification][$property] = $resolver; } } } @@ -36,8 +50,8 @@ public function initializeObject(): void /** * @return array */ - public function getResolversForTable(string $table): array + public function getResolversForClassification(string $classification): array { - return $this->resolvers[$table] ?? []; + return $this->resolvers[$classification] ?? []; } } diff --git a/Classes/Component/PostPublishTaskExecution/Domain/Repository/TaskRepository.php b/Classes/Component/PostPublishTaskExecution/Domain/Repository/TaskRepository.php index bbc8bd04e..b09bd18ec 100755 --- a/Classes/Component/PostPublishTaskExecution/Domain/Repository/TaskRepository.php +++ b/Classes/Component/PostPublishTaskExecution/Domain/Repository/TaskRepository.php @@ -104,7 +104,7 @@ public function findByExecutionBegin(DateTime $executionBegin = null): array $tasksPropertiesArray = $query->select('*') ->from(self::TASK_TABLE_NAME) ->where($predicates) - ->execute() + ->executeQuery() ->fetchAllAssociative(); foreach ($tasksPropertiesArray as $taskProperties) { $taskObjects[] = $this->taskFactory->convertToObject($taskProperties); @@ -125,6 +125,6 @@ public function deleteObsolete(): void ->where( $query->expr()->lte('execution_end', $query->createNamedParameter($executionEnd)), ); - $query->execute(); + $query->executeStatement(); } } diff --git a/Classes/Component/PostPublishTaskExecution/Service/TaskExecutionServiceInjection.php b/Classes/Component/PostPublishTaskExecution/Service/TaskExecutionServiceInjection.php new file mode 100644 index 000000000..71d918919 --- /dev/null +++ b/Classes/Component/PostPublishTaskExecution/Service/TaskExecutionServiceInjection.php @@ -0,0 +1,21 @@ +taskExecutionService = $taskExecutionService; + } +} diff --git a/Classes/Component/RemoteProcedureCall/EnvelopeDispatcher.php b/Classes/Component/RemoteProcedureCall/EnvelopeDispatcher.php index cdffd9801..4dd936f57 100644 --- a/Classes/Component/RemoteProcedureCall/EnvelopeDispatcher.php +++ b/Classes/Component/RemoteProcedureCall/EnvelopeDispatcher.php @@ -30,9 +30,10 @@ */ use In2code\In2publishCore\CommonInjection\ResourceFactoryInjection; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FileSystemInfoServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FilesystemInformationCollection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\LocalFileInfoServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\LocalFolderInfoServiceInjection; use In2code\In2publishCore\Component\RemoteProcedureCall\Exception\StorageIsOfflineException; -use InvalidArgumentException; use ReflectionProperty; use TYPO3\CMS\Core\Resource\Driver\DriverInterface; @@ -42,12 +43,12 @@ class EnvelopeDispatcher { use ResourceFactoryInjection; - use FileSystemInfoServiceInjection; + use LocalFolderInfoServiceInjection; + use LocalFileInfoServiceInjection; - public const CMD_FOLDER_EXISTS = 'folderExists'; - public const CMD_FILE_EXISTS = 'fileExists'; - public const CMD_LIST_FOLDER_CONTENTS = 'listFolderContents'; + public const CMD_GET_FOLDER_INFO = 'getFolderInfo'; public const CMD_GET_FILE_INFO = 'getFileInfo'; + public const CMD_FILE_EXISTS = 'fileExists'; public function dispatch(Envelope $envelope): bool { @@ -62,30 +63,19 @@ public function dispatch(Envelope $envelope): bool return false; } - protected function folderExists(array $request): bool - { - return $this->getStorageDriver($request)->folderExists($request['folderIdentifier']); - } - - protected function fileExists(array $request): bool + protected function getFolderInfo(array $request): FilesystemInformationCollection { - return $this->getStorageDriver($request)->fileExists($request['fileIdentifier']); + return $this->localFolderInfoService->getFolderInfo($request); } - public function listFolderContents(array $request): array + public function getFileInfo(array $request): FilesystemInformationCollection { - $storageUid = $request['storageUid']; - $identifier = $request['identifier']; - try { - return $this->fileSystemInfoService->listFolderContents($storageUid, $identifier); - } catch (InvalidArgumentException $e) { - return []; - } + return $this->localFileInfoService->getFileInfo($request); } - public function getFileInfo(array $request): array + protected function fileExists(array $request): bool { - return $this->fileSystemInfoService->getFileInfo($request['files']); + return $this->getStorageDriver($request)->fileExists($request['fileIdentifier']); } protected function getStorageDriver(array $request): DriverInterface diff --git a/Classes/Component/Core/FileHandling/Service/Exception/EnvelopeSendingFailedException.php b/Classes/Component/RemoteProcedureCall/Exception/EnvelopeSendingFailedException.php similarity index 85% rename from Classes/Component/Core/FileHandling/Service/Exception/EnvelopeSendingFailedException.php rename to Classes/Component/RemoteProcedureCall/Exception/EnvelopeSendingFailedException.php index 49960c6eb..64705ea04 100644 --- a/Classes/Component/Core/FileHandling/Service/Exception/EnvelopeSendingFailedException.php +++ b/Classes/Component/RemoteProcedureCall/Exception/EnvelopeSendingFailedException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace In2code\In2publishCore\Component\Core\FileHandling\Service\Exception; +namespace In2code\In2publishCore\Component\RemoteProcedureCall\Exception; use In2code\In2publishCore\In2publishCoreException; use Throwable; diff --git a/Classes/Component/RemoteProcedureCall/ExecuteCommandDispatcher.php b/Classes/Component/RemoteProcedureCall/ExecuteCommandDispatcher.php new file mode 100644 index 000000000..affa7905d --- /dev/null +++ b/Classes/Component/RemoteProcedureCall/ExecuteCommandDispatcher.php @@ -0,0 +1,57 @@ +letterbox->sendEnvelope($envelope); + if (!is_int($uid)) { + throw new EnvelopeSendingFailedException(); + } + $request = new RemoteCommandRequest(ExecuteCommand::IDENTIFIER, [], [$uid]); + $response = $this->remoteCommandDispatcher->dispatch($request); + + if (!$response->isSuccessful()) { + throw new RuntimeException( + sprintf( + 'Could not execute RPC [%d]. Errors and Output: %s %s', + $uid, + $response->getErrorsString(), + $response->getOutputString(), + ), + 1699621336, + ); + } + + $envelope = $this->letterbox->receiveEnvelope($uid); + + if (false === $envelope) { + throw new In2publishCoreException('Could not receive envelope [' . $uid . ']', 1699641778); + } + return $envelope->getResponse(); + } +} diff --git a/Classes/Component/RemoteProcedureCall/ExecuteCommandDispatcherInjection.php b/Classes/Component/RemoteProcedureCall/ExecuteCommandDispatcherInjection.php new file mode 100644 index 000000000..244188613 --- /dev/null +++ b/Classes/Component/RemoteProcedureCall/ExecuteCommandDispatcherInjection.php @@ -0,0 +1,21 @@ +executeCommandDispatcher = $executeCommandDispatcher; + } +} diff --git a/Classes/Component/RemoteProcedureCall/Letterbox.php b/Classes/Component/RemoteProcedureCall/Letterbox.php index 57b7d639f..911053e62 100644 --- a/Classes/Component/RemoteProcedureCall/Letterbox.php +++ b/Classes/Component/RemoteProcedureCall/Letterbox.php @@ -110,7 +110,7 @@ public function receiveEnvelope(int $uid, bool $burnEnvelope = true) ->where($query->expr()->eq('uid', $uid)) ->orderBy('data.sorting'); try { - $result = $query->execute(); + $result = $query->executeQuery(); $rows = $result->fetchAllAssociative(); } catch (Throwable $exception) { $this->logger->error( @@ -157,7 +157,7 @@ public function hasUnAnsweredEnvelopes(): bool ->from('tx_in2code_rpc_request', 'req') ->join('req', 'tx_in2code_rpc_data', 'data', 'req.uid = data.request') ->where($query->expr()->eq('data.data_type', $query->createNamedParameter('response'))); - return $query->execute()->fetchOne() > 0; + return $query->executeQuery()->fetchOne() > 0; } public function removeAnsweredEnvelopes(): void @@ -168,7 +168,7 @@ public function removeAnsweredEnvelopes(): void ->from('tx_in2code_rpc_request', 'req') ->join('req', 'tx_in2code_rpc_data', 'data', 'req.uid = data.request') ->where($query->expr()->eq('data.data_type', $query->createNamedParameter('response'))); - $uid = $query->execute()->fetchColumn(); + $uid = $query->executeQuery()->fetchOne(); $this->databaseOfForeign->delete('tx_in2code_rpc_request', ['uid' => $uid]); $this->databaseOfForeign->delete('tx_in2code_rpc_data', ['request' => $uid]); } diff --git a/Classes/Controller/FileController.php b/Classes/Controller/FileController.php index 1c2ea7cc2..8a0f9e61e 100755 --- a/Classes/Controller/FileController.php +++ b/Classes/Controller/FileController.php @@ -35,6 +35,7 @@ use In2code\In2publishCore\Component\Core\FileHandling\DefaultFalFinderInjection; use In2code\In2publishCore\Component\Core\FileHandling\Exception\FolderDoesNotExistOnBothSidesException; use In2code\In2publishCore\Component\Core\Publisher\PublisherServiceInjection; +use In2code\In2publishCore\Component\Core\Publisher\PublishingContext; use In2code\In2publishCore\Component\Core\RecordTree\RecordTree; use In2code\In2publishCore\Controller\Traits\CommonViewVariables; use In2code\In2publishCore\Controller\Traits\ControllerFilterStatus; @@ -47,19 +48,13 @@ use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; -use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use function array_keys; -use function explode; use function http_build_query; use function implode; -use function is_string; use function json_encode; use function parse_str; -use function strlen; -use function strpos; -use function trim; use const JSON_THROW_ON_ERROR; @@ -88,9 +83,6 @@ class FileController extends ActionController public function injectPageRenderer(PageRenderer $pageRenderer): void { $this->actualInjectPageRenderer($pageRenderer); - $this->pageRenderer->addInlineLanguageLabelFile( - 'EXT:in2publish_core/Resources/Private/Language/locallang_js.xlf', - ); $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/In2publishCore/BackendModule'); $this->pageRenderer->addCssFile( 'EXT:in2publish_core/Resources/Public/Css/Modules.css', @@ -101,14 +93,11 @@ public function injectPageRenderer(PageRenderer $pageRenderer): void ); } - /** - * @throws StopActionException - */ public function indexAction(): ResponseInterface { $pid = BackendUtility::getPageIdentifier(); try { - $recordTree = $this->tryToGetFolderInstance($pid === 0 ? null : $pid); + $recordTree = $this->defaultFalFinder->findFolderRecord($pid === 0 ? null : $pid); } catch (FolderDoesNotExistOnBothSidesException $e) { $uri = $this->request->getUri(); $queryParts = []; @@ -131,21 +120,23 @@ public function indexAction(): ResponseInterface $moduleTemplate = $this->moduleTemplateFactory->create($this->request); $moduleTemplate->setFlashMessageQueue($this->getFlashMessageQueue()); $moduleTemplate->setContent($this->view->render()); + /** @see packages/in2publish_core/Resources/Private/Templates/File/Index.html */ return $this->htmlResponse($moduleTemplate->renderContent()); } /** * @param bool $skipNotification Used by the Enterprise Edition. Do not remove despite unused in the CE. * - * @throws StopActionException + * @throws FolderDoesNotExistOnBothSidesException * @SuppressWarnings(PHPMD.BooleanArgumentFlag) On purpose */ - public function publishFolderAction(string $combinedIdentifier, bool $skipNotification = false): void + public function publishFolderAction(string $combinedIdentifier, bool $skipNotification = false): ResponseInterface { - $recordTree = $this->tryToGetFolderInstance($combinedIdentifier, true); + $recordTree = $this->defaultFalFinder->findFolderRecord($combinedIdentifier, true); + $publishingContext = new PublishingContext($recordTree); try { - $this->publisherService->publishRecordTree($recordTree); + $this->publisherService->publish($publishingContext); if (!$skipNotification) { $this->addFlashMessage( LocalizationUtility::translate('file_publishing.folder', 'in2publish_core', [$combinedIdentifier]), @@ -166,20 +157,20 @@ public function publishFolderAction(string $combinedIdentifier, bool $skipNotifi } } - $this->redirect('index'); + return $this->redirect('index'); } /** * @param bool $skipNotification Used by the Enterprise Edition. Do not remove despite unused in the CE. - * @throws StopActionException * @SuppressWarnings(PHPMD.BooleanArgumentFlag) On purpose */ - public function publishFileAction(string $combinedIdentifier, bool $skipNotification = false): void + public function publishFileAction(string $combinedIdentifier, bool $skipNotification = false): ResponseInterface { $recordTree = $this->defaultFalFinder->findFileRecord($combinedIdentifier); + $publishingContext = new PublishingContext($recordTree); try { - $this->publisherService->publishRecordTree($recordTree); + $this->publisherService->publish($publishingContext); if (!$skipNotification) { $this->addFlashMessage( LocalizationUtility::translate( @@ -216,7 +207,7 @@ public function publishFileAction(string $combinedIdentifier, bool $skipNotifica } } - $this->redirect('index'); + return $this->redirect('index'); } /** @@ -235,15 +226,6 @@ public function toggleFilterStatusAction(string $filter): ResponseInterface */ protected function tryToGetFolderInstance(?string $combinedIdentifier, bool $onlyRoot = false): ?RecordTree { - if (is_string($combinedIdentifier) && strpos($combinedIdentifier, ':') < strlen($combinedIdentifier)) { - [$storage, $name] = explode(':', $combinedIdentifier); - $name = trim($name, '/'); - if (!empty($name)) { - $combinedIdentifier = $storage . ':/' . $name . '/'; - } else { - $combinedIdentifier = $storage . ':/'; - } - } return $this->defaultFalFinder->findFolderRecord($combinedIdentifier, $onlyRoot); } } diff --git a/Classes/Controller/RecordController.php b/Classes/Controller/RecordController.php index fc428c13b..eea3312a5 100755 --- a/Classes/Controller/RecordController.php +++ b/Classes/Controller/RecordController.php @@ -93,9 +93,6 @@ class RecordController extends ActionController public function injectPageRenderer(PageRenderer $pageRenderer): void { $this->actualInjectPageRenderer($pageRenderer); - $this->pageRenderer->addInlineLanguageLabelFile( - 'EXT:in2publish_core/Resources/Private/Language/locallang_js.xlf', - ); $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/In2publishCore/BackendModule'); $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/In2publishCore/BackendEnhancements'); $this->pageRenderer->addCssFile( @@ -109,17 +106,18 @@ public function injectPageRenderer(PageRenderer $pageRenderer): void public function initializeIndexAction(): void { - /** @var BackendUserAuthentication $BE_USER */ - $BE_USER = $GLOBALS['BE_USER']; - $data = $BE_USER->getModuleData('tx_in2publishcore_m1') ?? ['pageRecursionLimit' => 1]; + $backendUser = $this->getBackendUser(); + $data = $backendUser->getModuleData('tx_in2publishcore_m1') ?? ['pageRecursionLimit' => 1]; if ($this->request->hasArgument('pageRecursionLimit')) { $pageRecursionLimit = (int)$this->request->getArgument('pageRecursionLimit'); $data['pageRecursionLimit'] = $pageRecursionLimit; - $BE_USER->pushModuleData('tx_in2publishcore_m1', $data); + $backendUser->pushModuleData('tx_in2publishcore_m1', $data); } else { - $this->request->setArgument('pageRecursionLimit', $data['pageRecursionLimit'] ?? 1); + $this->request = $this->request->withArgument('pageRecursionLimit', $data['pageRecursionLimit'] ?? 1); } + $this->moduleTemplate->setModuleClass('in2publish_core_m1'); + $menuRegistry = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry(); $menu = $menuRegistry->makeMenu(); $menu->setIdentifier('depth'); @@ -179,20 +177,20 @@ public function initializePublishRecordAction(): void } } - public function publishRecordAction(int $id): void + public function publishRecordAction(int $recordId): ResponseInterface { - $request = new RecordTreeBuildRequest('pages', $id, 0); + $request = new RecordTreeBuildRequest('pages', $recordId, 0); $recordTree = $this->recordTreeBuilder->buildRecordTree($request); - $actualRecord = $recordTree->getChild('pages', $id); + $actualRecord = $recordTree->getChild('pages', $recordId); if (null === $actualRecord) { - $this->addFlashMessagesAndRedirectToIndex(); + return $this->addFlashMessagesAndRedirectToIndex(); } $subRecordTree = new RecordTree([$actualRecord], $request); $publishingContext = new PublishingContext($subRecordTree); $this->publisherService->publish($publishingContext); - $this->addFlashMessagesAndRedirectToIndex(); + return $this->addFlashMessagesAndRedirectToIndex(); } /** @@ -209,10 +207,8 @@ public function toggleFilterStatusAction(string $filter): ResponseInterface /** * Add success message and redirect to indexAction - * - * @throws StopActionException */ - protected function addFlashMessagesAndRedirectToIndex(): void + protected function addFlashMessagesAndRedirectToIndex(): ResponseInterface { $failures = $this->failureCollector->getFailures(); @@ -233,6 +229,17 @@ protected function addFlashMessagesAndRedirectToIndex(): void } $this->addFlashMessage($message, $title, $severity); - $this->redirect('index', 'Record'); + $arguments = []; + $queryParams = $this->request->getQueryParams(); + if (isset($queryParams['id'])) { + $arguments['id'] = (int)$queryParams['id']; + } + + return $this->redirect('index', 'Record', null, $arguments); + } + + public function getBackendUser(): BackendUserAuthentication + { + return $GLOBALS['BE_USER']; } } diff --git a/Classes/Controller/Traits/CommonViewVariables.php b/Classes/Controller/Traits/CommonViewVariables.php index 3f5a68105..81e95c1ce 100644 --- a/Classes/Controller/Traits/CommonViewVariables.php +++ b/Classes/Controller/Traits/CommonViewVariables.php @@ -7,7 +7,8 @@ use In2code\In2publishCore\Component\ConfigContainer\ConfigContainerInjection; use In2code\In2publishCore\Service\Environment\EnvironmentServiceInjection; use In2code\In2publishCore\Service\Extension\ExtensionServiceInjection; -use TYPO3\CMS\Extbase\Mvc\View\ViewInterface; +use Psr\Http\Message\ResponseInterface; +use TYPO3\CMS\Extbase\Mvc\RequestInterface; trait CommonViewVariables { @@ -15,10 +16,11 @@ trait CommonViewVariables use EnvironmentServiceInjection; use ExtensionServiceInjection; - protected function initializeView(ViewInterface $view): void + protected function callActionMethod(RequestInterface $request): ResponseInterface { - $view->assign('extensionVersion', $this->extensionService->getExtensionVersion('in2publish_core')); - $view->assign('config', $this->configContainer->get()); - $view->assign('testStatus', $this->environmentService->getTestStatus()); + $this->view->assign('extensionVersion', $this->extensionService->getExtensionVersion('in2publish_core')); + $this->view->assign('config', $this->configContainer->get()); + $this->view->assign('testStatus', $this->environmentService->getTestStatus()); + return parent::callActionMethod($request); } } diff --git a/Classes/Event/ExtTablesPostProcessingEvent.php b/Classes/Event/ExtTablesPostProcessingEvent.php deleted file mode 100644 index 4608401e7..000000000 --- a/Classes/Event/ExtTablesPostProcessingEvent.php +++ /dev/null @@ -1,48 +0,0 @@ - - * - * All rights reserved - * - * This script is part of the TYPO3 project. The TYPO3 project is - * free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * The GNU General Public License can be found at - * http://www.gnu.org/copyleft/gpl.html. - * - * This script is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This copyright notice MUST APPEAR in all copies of the script! - */ - -/** - * This class replaces the "ExtTablesPostProcessingHook" which was registered using - * $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['extTablesInclusion-PostProcessing'] The actual hook was removed - * and the suggested replacement is listening to the TYPO3\CMS\Core\Core\Event\BootCompletedEvent. The problem with - * that event is though, that IT IS DISPATCHED BEFORE ext_tables.php files are included, which ultimately defeats the - * purpose. So we here we go again and do it ourselves. - * - * Required until the patch got merged and released. - * - * Issue: https://forge.typo3.org/issues/95962 - * Patch: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72160 - * - * @codeCoverageIgnore - */ -final class ExtTablesPostProcessingEvent -{ -} diff --git a/Classes/Factory/ConnectionFactory.php b/Classes/Factory/ConnectionFactory.php index dc70dbdd9..7943e3a7e 100644 --- a/Classes/Factory/ConnectionFactory.php +++ b/Classes/Factory/ConnectionFactory.php @@ -29,7 +29,7 @@ * This copyright notice MUST APPEAR in all copies of the script! */ -use Doctrine\DBAL\Driver\Connection; +use TYPO3\CMS\Core\Database\Connection; use In2code\In2publishCore\Factory\Exception\ConnectionUnavailableException; use In2code\In2publishCore\Service\Context\ContextServiceInjection; use In2code\In2publishCore\Utility\DatabaseUtility; diff --git a/Classes/Features/AdminTools/Backend/Button/AdminToolButton.php b/Classes/Features/AdminTools/Backend/Button/AdminToolButton.php index 8402ada88..23a265a11 100644 --- a/Classes/Features/AdminTools/Backend/Button/AdminToolButton.php +++ b/Classes/Features/AdminTools/Backend/Button/AdminToolButton.php @@ -38,6 +38,10 @@ class AdminToolButton extends LinkButton { protected $showLabelText = true; + protected $defaultClasses = [ + 'btn','btn-default' + ]; + public function isValid(): bool { return trim($this->getHref()) !== '' @@ -49,7 +53,7 @@ public function render(): string { $attributes = [ 'href' => $this->getHref(), - 'class' => 'btn btn-default ' . $this->getClasses(), + 'class' => $this->getDefaultClassesAsString() . ' ' . $this->getClasses(), 'title' => $this->getTitle(), ]; $labelText = ''; @@ -71,4 +75,18 @@ public function render(): string return '' . $labelText . ''; } + + protected function getDefaultClassesAsString(): string + { + return implode(' ', $this->defaultClasses); + } + + public function makeButtonPrimary(): void + { + $index = array_search('btn-default', $this->defaultClasses, true); + if (false !== $index) { + $this->defaultClasses[$index] = 'btn-primary'; + } + } + } diff --git a/Classes/Features/AdminTools/Controller/AbstractAdminToolsController.php b/Classes/Features/AdminTools/Controller/AbstractAdminToolsController.php new file mode 100644 index 000000000..7fbfd9b10 --- /dev/null +++ b/Classes/Features/AdminTools/Controller/AbstractAdminToolsController.php @@ -0,0 +1,62 @@ + + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use In2code\In2publishCore\CommonInjection\PageRendererInjection; +use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; +use TYPO3\CMS\Core\Page\PageRenderer; +use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; + +abstract class AbstractAdminToolsController extends ActionController +{ + use AdminToolsModuleTemplate; + use PageRendererInjection { + injectPageRenderer as actualInjectPageRenderer; + } + + /** + * @codeCoverageIgnore + * @noinspection PhpUnused + */ + public function injectPageRenderer(PageRenderer $pageRenderer): void + { + $this->actualInjectPageRenderer($pageRenderer); + $this->pageRenderer->addInlineLanguageLabelFile( + 'EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf', + ); + $this->pageRenderer->addCssFile( + 'EXT:in2publish_core/Resources/Public/Css/Modules.css', + 'stylesheet', + 'all', + '', + false, + ); + } +} diff --git a/Classes/Features/AdminTools/Controller/LetterboxController.php b/Classes/Features/AdminTools/Controller/LetterboxController.php index a08b0bc30..8a6e3b264 100644 --- a/Classes/Features/AdminTools/Controller/LetterboxController.php +++ b/Classes/Features/AdminTools/Controller/LetterboxController.php @@ -30,15 +30,11 @@ */ use In2code\In2publishCore\Component\RemoteProcedureCall\LetterboxInjection; -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; -use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; -class LetterboxController extends ActionController +class LetterboxController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; use LetterboxInjection; public function indexAction(): ResponseInterface @@ -47,16 +43,14 @@ public function indexAction(): ResponseInterface return $this->htmlResponse(); } - /** @throws StopActionException */ - public function flushEnvelopesAction(): void + public function flushEnvelopesAction(): ResponseInterface { $this->letterbox->removeAnsweredEnvelopes(); $this->addFlashMessage( LocalizationUtility::translate( - 'module.m4.superfluous_envelopes_flushed', - 'in2publish_core', + 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:flush_envelopes.flushed', ), ); - $this->redirect('index'); + return $this->redirect('index'); } } diff --git a/Classes/Features/AdminTools/Controller/RegistryController.php b/Classes/Features/AdminTools/Controller/RegistryController.php index 1c5dcf108..a1bb7604e 100644 --- a/Classes/Features/AdminTools/Controller/RegistryController.php +++ b/Classes/Features/AdminTools/Controller/RegistryController.php @@ -30,15 +30,12 @@ */ use In2code\In2publishCore\CommonInjection\RegistryInjection; -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; -class RegistryController extends ActionController +class RegistryController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; use RegistryInjection; public function indexAction(): ResponseInterface @@ -47,10 +44,14 @@ public function indexAction(): ResponseInterface } /** @throws StopActionException */ - public function flushRegistryAction(): void + public function flushRegistryAction(): ResponseInterface { $this->registry->removeAllByNamespace('tx_in2publishcore'); - $this->addFlashMessage(LocalizationUtility::translate('module.m4.registry_flushed', 'in2publish_core')); - $this->redirect('index'); + $this->addFlashMessage( + LocalizationUtility::translate( + 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:flush_registry.registry_flushed' + ) + ); + return $this->redirect('index'); } } diff --git a/Classes/Features/AdminTools/Controller/ShowConfigurationController.php b/Classes/Features/AdminTools/Controller/ShowConfigurationController.php index ae39e91f9..77824159d 100644 --- a/Classes/Features/AdminTools/Controller/ShowConfigurationController.php +++ b/Classes/Features/AdminTools/Controller/ShowConfigurationController.php @@ -31,13 +31,10 @@ use In2code\In2publishCore\Component\ConfigContainer\ConfigContainerInjection; use In2code\In2publishCore\Component\ConfigContainer\Dumper\ConfigContainerDumper; -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; -class ShowConfigurationController extends ActionController +class ShowConfigurationController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; use ConfigContainerInjection; private ConfigContainerDumper $configContainerDumper; diff --git a/Classes/Features/AdminTools/Controller/TcaController.php b/Classes/Features/AdminTools/Controller/TcaController.php index 4aa8411f7..9bf2d8d14 100644 --- a/Classes/Features/AdminTools/Controller/TcaController.php +++ b/Classes/Features/AdminTools/Controller/TcaController.php @@ -29,20 +29,17 @@ * This copyright notice MUST APPEAR in all copies of the script! */ -use In2code\In2publishCore\Component\Core\PreProcessing\TcaPreProcessingServiceInjection; -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; +use In2code\In2publishCore\Component\Core\PreProcessing\CachedTcaPreProcessingServiceInjection; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; -class TcaController extends ActionController +class TcaController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; - use TcaPreProcessingServiceInjection; + use CachedTcaPreProcessingServiceInjection; public function indexAction(): ResponseInterface { - $this->view->assign('incompatibleTca', $this->tcaPreProcessingService->getIncompatibleTcaParts()); - $this->view->assign('compatibleTca', $this->tcaPreProcessingService->getCompatibleTcaParts()); + $this->view->assign('incompatibleTca', $this->cachedTcaPreProcessingService->getIncompatibleTcaParts()); + $this->view->assign('compatibleTca', $this->cachedTcaPreProcessingService->getCompatibleTcaParts()); return $this->htmlResponse(); } diff --git a/Classes/Features/AdminTools/Controller/TestController.php b/Classes/Features/AdminTools/Controller/TestController.php index d045e1aad..00509e3eb 100644 --- a/Classes/Features/AdminTools/Controller/TestController.php +++ b/Classes/Features/AdminTools/Controller/TestController.php @@ -29,17 +29,14 @@ * This copyright notice MUST APPEAR in all copies of the script! */ -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; use In2code\In2publishCore\In2publishCoreException; use In2code\In2publishCore\Service\Environment\EnvironmentServiceInjection; use In2code\In2publishCore\Testing\Service\TestingServiceInjection; use In2code\In2publishCore\Testing\Tests\TestResult; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; -class TestController extends ActionController +class TestController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; use EnvironmentServiceInjection; use TestingServiceInjection; diff --git a/Classes/Features/AdminTools/Controller/ToolsController.php b/Classes/Features/AdminTools/Controller/ToolsController.php index 2370bf731..247912ac0 100644 --- a/Classes/Features/AdminTools/Controller/ToolsController.php +++ b/Classes/Features/AdminTools/Controller/ToolsController.php @@ -30,20 +30,17 @@ */ use In2code\In2publishCore\Event\CreatedDefaultHelpLabels; -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; use In2code\In2publishCore\Service\Environment\EnvironmentServiceInjection; use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Core\Messaging\AbstractMessage; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use function implode; use const PHP_EOL; -class ToolsController extends ActionController +class ToolsController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; use EnvironmentServiceInjection; public function indexAction(): ResponseInterface @@ -63,8 +60,12 @@ public function indexAction(): ResponseInterface } $supports = [ - LocalizationUtility::translate('help.github_issues', 'in2publish_core'), - LocalizationUtility::translate('help.slack_channel', 'in2publish_core'), + LocalizationUtility::translate( + 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:introduction.help.github_issues' + ), + LocalizationUtility::translate( + 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:introduction.help.slack_channel' + ), ]; $event = new CreatedDefaultHelpLabels($supports); diff --git a/Classes/Features/AdminTools/Controller/Traits/AdminToolsModuleTemplate.php b/Classes/Features/AdminTools/Controller/Traits/AdminToolsModuleTemplate.php index d777f0020..3b077dad5 100644 --- a/Classes/Features/AdminTools/Controller/Traits/AdminToolsModuleTemplate.php +++ b/Classes/Features/AdminTools/Controller/Traits/AdminToolsModuleTemplate.php @@ -80,7 +80,7 @@ protected function render(): string $this->request->getControllerObjectName() === $entry['controller'] && in_array($this->request->getControllerActionName(), explode(',', $entry['action'])) ) { - $button->setClasses('btn-primary'); + $button->makeButtonPrimary(); } $buttonBar->addButton($button); } diff --git a/Classes/Features/AdminTools/Service/ToolsRegistry.php b/Classes/Features/AdminTools/Service/ToolsRegistry.php index 43e298488..15d0e2ac5 100644 --- a/Classes/Features/AdminTools/Service/ToolsRegistry.php +++ b/Classes/Features/AdminTools/Service/ToolsRegistry.php @@ -32,9 +32,10 @@ use In2code\In2publishCore\CommonInjection\ExtensionConfigurationInjection; use In2code\In2publishCore\Component\ConfigContainer\ConfigContainerInjection; use In2code\In2publishCore\Features\AdminTools\Service\Exception\ClassNotFoundException; +use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationExtensionNotConfiguredException; +use TYPO3\CMS\Core\Configuration\Exception\ExtensionConfigurationPathDoesNotExistException; use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; use TYPO3\CMS\Extbase\Utility\ExtensionUtility; use function class_exists; @@ -63,25 +64,21 @@ public function addTool( ]; } + /** + * @throws ExtensionConfigurationPathDoesNotExistException + * @throws ExtensionConfigurationExtensionNotConfiguredException + */ public function getEntries(): array { - // Do not inject the ConfigurationManager, because it will not contain the configured tools. - $configurationManager = GeneralUtility::makeInstance(ConfigurationManagerInterface::class); - $configuration = $configurationManager->getConfiguration( - ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, - ); $processedTools = []; - $controllerConfig = $configuration['controllerConfiguration']; foreach ($this->entries as $key => $config) { if ($this->evaluateCondition($config)) { $controller = $config['controller']; $processedTools[$key] = $config; $actions = GeneralUtility::trimExplode(',', $processedTools[$key]['action']); $processedTools[$key]['initialAction'] = $actions[0]; - if (isset($controllerConfig[$controller]['alias'])) { - $processedTools[$key]['alias'] = $controllerConfig[$controller]['alias']; - } + $processedTools[$key]['alias'] = ExtensionUtility::resolveControllerAliasFromControllerClassName($controller); } } @@ -90,13 +87,11 @@ public function getEntries(): array /** * @throws ClassNotFoundException + * @throws ExtensionConfigurationExtensionNotConfiguredException + * @throws ExtensionConfigurationPathDoesNotExistException */ - public function processData(): void + public function processData(): array { - if (!$this->configContainer->get('module.m4')) { - return; - } - $controllerActions = []; foreach ($this->entries as $entry) { if ($this->evaluateCondition($entry)) { @@ -113,24 +108,28 @@ public function processData(): void } } + return $controllerActions; + } + + /** + * @throws ClassNotFoundException + * @throws ExtensionConfigurationExtensionNotConfiguredException + * @throws ExtensionConfigurationPathDoesNotExistException + * @deprecated Will be removed in TYPO3 v13 + */ + public function processDataForTypo3V11(): array + { + $controllerActions = $this->processData(); foreach ($controllerActions as $controllerName => $actions) { $controllerActions[$controllerName] = implode(',', $actions); } - - ExtensionUtility::registerModule( - 'in2publish_core', - 'tools', - 'm4', - '', - $controllerActions, - [ - 'access' => 'admin', - 'icon' => 'EXT:in2publish_core/Resources/Public/Icons/Tools.svg', - 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf', - ], - ); + return $controllerActions; } + /** + * @throws ExtensionConfigurationPathDoesNotExistException + * @throws ExtensionConfigurationExtensionNotConfiguredException + */ protected function evaluateCondition(array $config): bool { if (null === $config['condition']) { diff --git a/Classes/Features/CompareDatabaseTool/Controller/CompareDatabaseToolController.php b/Classes/Features/CompareDatabaseTool/Controller/CompareDatabaseToolController.php index 578bc0e45..0596f7b1f 100644 --- a/Classes/Features/CompareDatabaseTool/Controller/CompareDatabaseToolController.php +++ b/Classes/Features/CompareDatabaseTool/Controller/CompareDatabaseToolController.php @@ -37,7 +37,7 @@ use In2code\In2publishCore\Service\Configuration\IgnoredFieldsServiceInjection; use In2code\In2publishCore\Utility\ArrayUtility; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Core\Messaging\AbstractMessage; +use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; @@ -120,23 +120,23 @@ public function compareAction(ComparisonRequest $comparisonRequest = null): Resp $localResult = $localQuery->select('*') ->from($table) ->where( - $localQuery->expr()->andX( + $localQuery->expr()->and( $localQuery->expr()->gte('uid', $offset), $localQuery->expr()->lt('uid', $limit), ), ) - ->execute(); + ->executeQuery(); $localRows = array_column($localResult->fetchAllAssociative(), null, 'uid'); $foreignQuery = $this->foreignDatabase->createQueryBuilder(); $foreignResult = $foreignQuery->select('*') ->from($table) ->where( - $foreignQuery->expr()->andX( + $foreignQuery->expr()->and( $foreignQuery->expr()->gte('uid', $offset), $foreignQuery->expr()->lt('uid', $limit), ), ) - ->execute(); + ->executeQuery(); $foreignRows = array_column($foreignResult->fetchAllAssociative(), null, 'uid'); $uidList = array_unique(array_merge(array_keys($localRows), array_keys($foreignRows))); @@ -190,7 +190,7 @@ public function transferAction(string $table, int $uid, string $expected): void ->from($table) ->where($localQuery->expr()->eq('uid', $localQuery->createNamedParameter($uid))) ->setMaxResults(1); - $localResult = $localQuery->execute(); + $localResult = $localQuery->executeQuery(); $localRow = $localResult->fetchAssociative(); $foreignQuery = $this->foreignDatabase->createQueryBuilder(); @@ -199,14 +199,14 @@ public function transferAction(string $table, int $uid, string $expected): void ->from($table) ->where($foreignQuery->expr()->eq('uid', $foreignQuery->createNamedParameter($uid))) ->setMaxResults(1); - $foreignResult = $foreignQuery->execute(); + $foreignResult = $foreignQuery->executeQuery(); $foreignRow = $foreignResult->fetchAssociative(); if (empty($localRow) && empty($foreignRow)) { $this->addFlashMessage( LocalizationUtility::translate('compare_database.transfer.record_missing', 'in2publish_core'), LocalizationUtility::translate('compare_database.transfer.error', 'in2publish_core'), - AbstractMessage::ERROR, + ContextualFeedbackSeverity::ERROR, ); $this->redirect('index'); } @@ -216,14 +216,14 @@ public function transferAction(string $table, int $uid, string $expected): void $this->addFlashMessage( LocalizationUtility::translate('compare_database.transfer.exists_on_foreign', 'in2publish_core'), LocalizationUtility::translate('compare_database.transfer.error', 'in2publish_core'), - AbstractMessage::ERROR, + ContextualFeedbackSeverity::ERROR, ); $this->redirect('index'); } - $foreignQuery = $foreignDatabase->createQueryBuilder(); + $foreignQuery = $this->foreignDatabase->createQueryBuilder(); $foreignQuery->delete($table) ->where($localQuery->expr()->eq('uid', $foreignQuery->createNamedParameter($uid))); - $foreignResult = $foreignQuery->execute(); + $foreignResult = $foreignQuery->executeStatement(); if (1 === $foreignResult) { $this->addFlashMessage( LocalizationUtility::translate( @@ -241,14 +241,14 @@ public function transferAction(string $table, int $uid, string $expected): void $this->addFlashMessage( LocalizationUtility::translate('compare_database.transfer.exists_on_local', 'in2publish_core'), LocalizationUtility::translate('compare_database.transfer.error', 'in2publish_core'), - AbstractMessage::ERROR, + ContextualFeedbackSeverity::ERROR, ); $this->redirect('index'); } - $foreignQuery = $foreignDatabase->createQueryBuilder(); + $foreignQuery = $this->foreignDatabase->createQueryBuilder(); $foreignQuery->insert($table) ->values($localRow); - $foreignResult = $foreignQuery->execute(); + $foreignResult = $foreignQuery->executeStatement(); if (1 === $foreignResult) { $this->addFlashMessage( LocalizationUtility::translate( @@ -269,11 +269,11 @@ public function transferAction(string $table, int $uid, string $expected): void 'in2publish_core', ), LocalizationUtility::translate('compare_database.transfer.error', 'in2publish_core'), - AbstractMessage::ERROR, + ContextualFeedbackSeverity::ERROR, ); $this->redirect('index'); } - $foreignQuery = $foreignDatabase->createQueryBuilder(); + $foreignQuery = $this->foreignDatabase->createQueryBuilder(); $foreignQuery->update($table); foreach ($localRow as $field => $value) { if ($foreignRow[$field] !== $value) { @@ -281,7 +281,7 @@ public function transferAction(string $table, int $uid, string $expected): void } } $foreignQuery->where($foreignQuery->expr()->eq('uid', $foreignQuery->createNamedParameter($uid))); - $foreignResult = $foreignQuery->execute(); + $foreignResult = $foreignQuery->executeStatement(); if (1 === $foreignResult) { $this->addFlashMessage( LocalizationUtility::translate( @@ -299,7 +299,7 @@ public function transferAction(string $table, int $uid, string $expected): void protected function getAllNonExcludedTables(): array { - $tables = $this->localDatabase->getSchemaManager()->listTableNames(); + $tables = $this->localDatabase->createSchemaManager()->listTableNames(); $excludedTables = $this->configContainer->get('excludeRelatedTables'); return array_diff($tables, $excludedTables); } diff --git a/Classes/Features/ContextMenuPublishEntry/ContextMenu/PublishItemProvider.php b/Classes/Features/ContextMenuPublishEntry/ContextMenu/PublishItemProvider.php index 036a9cb60..28bbeabdb 100644 --- a/Classes/Features/ContextMenuPublishEntry/ContextMenu/PublishItemProvider.php +++ b/Classes/Features/ContextMenuPublishEntry/ContextMenu/PublishItemProvider.php @@ -37,6 +37,10 @@ use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; use TYPO3\CMS\Core\Utility\GeneralUtility; +use function func_get_args; + +use const In2code\In2publishCore\TYPO3_V11; + class PublishItemProvider extends AbstractProvider { protected $itemsConfiguration = [ @@ -48,9 +52,10 @@ class PublishItemProvider extends AbstractProvider ]; protected PermissionService $permissionService; - public function __construct(string $table, string $identifier, string $context = '') + public function __construct() { - parent::__construct($table, $identifier, $context); + parent::__construct(...func_get_args()); + // Sorry, no DI available for Context Menu Item Provider $this->permissionService = GeneralUtility::makeInstance(PermissionService::class); } @@ -88,10 +93,12 @@ protected function getAdditionalAttributes(string $itemName): array 'ajax_in2publishcore_contextmenupublishentry_publish', ['id' => $this->identifier], ); - $attributes += [ - 'data-publish-url' => $publishUrl, - 'data-callback-module' => 'TYPO3/CMS/In2publishCore/ContextMenuPublishEntry', - ]; + $attributes['data-publish-url'] = $publishUrl; + if (TYPO3_V11) { + $attributes['data-callback-module'] = 'TYPO3/CMS/In2publishCore/ContextMenuPublishEntry'; + } else { + $attributes['data-callback-module'] = '@in2code/in2publish_core/context-menu-actions'; + } } return $attributes; } diff --git a/Classes/Features/ContextMenuPublishEntry/Controller/PublishPageAjaxController.php b/Classes/Features/ContextMenuPublishEntry/Controller/PublishPageAjaxController.php index bc7e872a3..9ca493a6d 100644 --- a/Classes/Features/ContextMenuPublishEntry/Controller/PublishPageAjaxController.php +++ b/Classes/Features/ContextMenuPublishEntry/Controller/PublishPageAjaxController.php @@ -30,6 +30,7 @@ */ use In2code\In2publishCore\Component\Core\Publisher\PublisherServiceInjection; +use In2code\In2publishCore\Component\Core\Publisher\PublishingContext; use In2code\In2publishCore\Component\Core\RecordTree\RecordTreeBuilderInjection; use In2code\In2publishCore\Component\Core\RecordTree\RecordTreeBuildRequest; use In2code\In2publishCore\Component\PostPublishTaskExecution\Service\Exception\TaskExecutionFailedException; @@ -55,7 +56,7 @@ public function publishPage(ServerRequestInterface $request): ResponseInterface { $response = new Response(); - $page = $request->getQueryParams()['page'] ?? null; + $page = $request->getQueryParams()['id'] ?? null; $content = [ 'success' => false, @@ -77,9 +78,10 @@ public function publishPage(ServerRequestInterface $request): ResponseInterface $recordTreeBuildRequest = new RecordTreeBuildRequest('pages', (int)$page, 0); $recordTree = $this->recordTreeBuilder->buildRecordTree($recordTreeBuildRequest); $record = $recordTree->getChild('pages', (int)$page, 0); + $publishingContext = new PublishingContext($recordTree); if (null !== $record && $record->isPublishable()) { try { - $this->publisherService->publishRecordTree($recordTree); + $this->publisherService->publish($publishingContext); $content['success'] = true; $content['error'] = false; $content['label'] = 'context_menu_publish_entry.page_published'; diff --git a/Classes/Features/FileEdgeCacheInvalidator/Domain/Service/FileEdgeCacheInvalidationService.php b/Classes/Features/FileEdgeCacheInvalidator/Domain/Service/FileEdgeCacheInvalidationService.php index 699fb6ada..5a9e03a81 100644 --- a/Classes/Features/FileEdgeCacheInvalidator/Domain/Service/FileEdgeCacheInvalidationService.php +++ b/Classes/Features/FileEdgeCacheInvalidator/Domain/Service/FileEdgeCacheInvalidationService.php @@ -37,6 +37,8 @@ use function array_key_exists; use function in_array; +use const In2code\In2publishCore\TYPO3_V11; + class FileEdgeCacheInvalidationService { use LocalDatabaseInjection; @@ -77,12 +79,12 @@ protected function selectSysRefIndexRecords(array $uidList): Result $query = $this->localDatabase->createQueryBuilder(); $query->getRestrictions()->removeAll(); $query->select('tablename as table', 'recuid as uid')->from('sys_refindex')->where( - $query->expr()->andX( + $query->expr()->and( $query->expr()->eq('ref_table', '"sys_file"'), $query->expr()->in('ref_uid', $uidList), ), ); - return $query->execute(); + return $query->executeQuery(); } /** @@ -96,13 +98,11 @@ protected function selectSysFileReferenceRecords(array $uidList): Result $query->getRestrictions()->removeAll(); $query->select('tablenames as table', 'uid_foreign as uid') ->from('sys_file_reference') - ->where( - $query->expr()->andX( - $query->expr()->eq('table_local', '"sys_file"'), - $query->expr()->in('uid_local', $uidList), - ), - ); - return $query->execute(); + ->where($query->expr()->in('uid_local', $uidList)); + if (TYPO3_V11) { + $query->andWhere($query->expr()->eq('table_local', '"sys_file"')); + } + return $query->executeQuery(); } protected function addResultsToCollection(Result $statement, RecordCollection $recordCollection): void @@ -116,15 +116,15 @@ protected function addResultsToCollection(Result $statement, RecordCollection $r protected function resolveRecordsToPages(RecordCollection $recordCollection): void { - $schemaManager = $this->localDatabase->getSchemaManager(); + $schemaManager = $this->localDatabase->createSchemaManager(); $tableNames = $schemaManager->listTableNames(); foreach ($recordCollection->getRecords() as $table => $recordUidList) { if ( - !in_array($table, $tableNames, true) - || !array_key_exists('pid', $schemaManager->listTableColumns($table)) - || $table === '_file' + $table === '_file' || $table === '_folder' + || !in_array($table, $tableNames, true) + || !array_key_exists('pid', $schemaManager->listTableColumns($table)) ) { continue; } @@ -133,7 +133,7 @@ protected function resolveRecordsToPages(RecordCollection $recordCollection): vo $query->select('pid')->from($table); $query->where($query->expr()->in('uid', $recordUidList)); $query->groupBy('pid'); - $statement = $query->execute(); + $statement = $query->executeQuery(); while ($page = $statement->fetchOne()) { $recordCollection->addRecord('pages', $page); } diff --git a/Classes/Features/FullTablePublishing/Service/TableBackupService.php b/Classes/Features/FullTablePublishing/Service/TableBackupService.php index 52a3a985e..50338cd38 100644 --- a/Classes/Features/FullTablePublishing/Service/TableBackupService.php +++ b/Classes/Features/FullTablePublishing/Service/TableBackupService.php @@ -116,7 +116,7 @@ protected function dumpTableData(Connection $connection, string $table, $resourc { $query = $connection->createQueryBuilder(); $query->getRestrictions()->removeAll(); - $resultSet = $query->select('*')->from($table)->execute(); + $resultSet = $query->select('*')->from($table)->executeQuery(); $escapedTableName = $connection->quoteIdentifier($table); diff --git a/Classes/Features/FullTablePublishing/Service/TableTransferService.php b/Classes/Features/FullTablePublishing/Service/TableTransferService.php index b8bc6c8ed..fb2ea9889 100644 --- a/Classes/Features/FullTablePublishing/Service/TableTransferService.php +++ b/Classes/Features/FullTablePublishing/Service/TableTransferService.php @@ -14,7 +14,7 @@ public function copyTableContents(Connection $source, Connection $target, string $query = $source->createQueryBuilder(); $query->select('*')->from($table); - $result = $query->execute(); + $result = $query->executeQuery(); while ($row = $result->fetchAssociative()) { $target->insert($table, $row); } diff --git a/Classes/Features/LogsIntegration/Controller/LogController.php b/Classes/Features/LogsIntegration/Controller/LogController.php deleted file mode 100644 index 9e6af444f..000000000 --- a/Classes/Features/LogsIntegration/Controller/LogController.php +++ /dev/null @@ -1,67 +0,0 @@ - - * - * All rights reserved - * - * This script is part of the TYPO3 project. The TYPO3 project is - * free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * The GNU General Public License can be found at - * http://www.gnu.org/copyleft/gpl.html. - * - * This script is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This copyright notice MUST APPEAR in all copies of the script! - */ - -use CoStack\Logs\Controller\LogController as LogsController; -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; -use TYPO3\CMS\Fluid\View\TemplateView; -use TYPO3Fluid\Fluid\View\ViewInterface; - -class LogController extends LogsController -{ - use AdminToolsModuleTemplate; - - protected function initializeAction(): void - { - parent::initializeAction(); - $this->logConfiguration = $GLOBALS['TYPO3_CONF_VARS']['LOG']['In2code']['In2publishCore']; - } - - protected function resolveView(): ViewInterface - { - $view = parent::resolveView(); - if ($view instanceof TemplateView) { - $templatePaths = $view->getTemplatePaths(); - $templatePaths->setTemplateRootPaths([ - 0 => 'EXT:logs/Resources/Private/Templates/', - 10 => 'EXT:in2publish_core/Resources/Private/Templates/', - ]); - $templatePaths->setLayoutRootPaths([ - 0 => 'EXT:logs/Resources/Private/Layouts/', - 10 => 'EXT:in2publish_core/Resources/Private/Layouts/', - ]); - $templatePaths->setPartialRootPaths([ - 0 => 'EXT:logs/Resources/Private/Partials/', - 10 => 'EXT:in2publish_core/Resources/Private/Partials/', - ]); - } - return $view; - } -} diff --git a/Classes/Features/MetricsAndDebug/Database/Logging/ContentPublisherSqlLogger.php b/Classes/Features/MetricsAndDebug/Database/Logging/ContentPublisherSqlLogger.php index 27a858f7e..f518fd536 100644 --- a/Classes/Features/MetricsAndDebug/Database/Logging/ContentPublisherSqlLogger.php +++ b/Classes/Features/MetricsAndDebug/Database/Logging/ContentPublisherSqlLogger.php @@ -5,6 +5,7 @@ namespace In2code\In2publishCore\Features\MetricsAndDebug\Database\Logging; use Doctrine\DBAL\Logging\SQLLogger; +use In2code\In2publishCore\Cache\CachedRuntimeCache; use function array_key_last; use function array_shift; @@ -33,7 +34,7 @@ class ContentPublisherSqlLogger implements SQLLogger ]; protected static array $queries = []; protected float $start; - protected int $currentQuery = 0; + protected static int $currentQuery = 0; public function startQuery($sql, array $params = null, array $types = null) { @@ -113,12 +114,12 @@ public function startQuery($sql, array $params = null, array $types = null) $entry['executionNS'] = 0; $entry['backtrace'] = $backtrace; - self::$queries[++$this->currentQuery] = $entry; + self::$queries[++self::$currentQuery] = $entry; } public function stopQuery(): void { - self::$queries[$this->currentQuery]['executionNS'] = (int)(hrtime(true) - $this->start); + self::$queries[self::$currentQuery]['executionNS'] = (int)(hrtime(true) - $this->start); } public static function getQueries(): array @@ -129,7 +130,12 @@ public static function getQueries(): array protected function findFirstCpFrame(array $backtrace): ?int { foreach ($backtrace as $index => $frame) { - if (isset($frame['class']) && str_starts_with($frame['class'], 'In2code\\In2publish')) { + if ( + isset($frame['class'], $frame['function']) + && $frame['class'] !== CachedRuntimeCache::class + && $frame['function'] !== 'executeCached' + && str_starts_with($frame['class'], 'In2code\\In2publish') + ) { return $index; } } diff --git a/Classes/Features/MetricsAndDebug/Middleware/MetricsAndDebugMiddleware.php b/Classes/Features/MetricsAndDebug/Middleware/MetricsAndDebugMiddleware.php index bdf6ec37a..d050f5162 100644 --- a/Classes/Features/MetricsAndDebug/Middleware/MetricsAndDebugMiddleware.php +++ b/Classes/Features/MetricsAndDebug/Middleware/MetricsAndDebugMiddleware.php @@ -21,6 +21,7 @@ use function date; use function str_replace; use function str_starts_with; +use function uniqid; class MetricsAndDebugMiddleware implements MiddlewareInterface { @@ -79,6 +80,8 @@ protected function debugSqlQueries(): void unset($query['caller']); $queriesByCaller[$caller][] = $query; } + uksort($queriesByCaller, static fn($a, $b) => count($queriesByCaller[$b]) - count($queriesByCaller[$a])); + foreach ($queriesByCaller as $caller => $callerQueries) { $times = array_column($callerQueries, 'executionNS'); $durationNS = array_sum($times); @@ -90,8 +93,11 @@ protected function debugSqlQueries(): void $queriesByCaller["$caller ($duration)"] = $callerQueries; } if (!empty($queries)) { - DebugUtility::debug($queries, 'Content Publisher Queries'); - DebugUtility::debug($queriesByCaller, 'Queries By Caller'); + /** @noinspection PhpRedundantOptionalArgumentInspection */ + $requestGroup = uniqid('request_', false); + DebugUtility::debug($queries, 'Content Publisher Queries', $requestGroup); + DebugUtility::debug($queriesByCaller, 'Queries By Caller', $requestGroup); + DebugUtility::debug(array_sum(array_column($queries, 'executionNS')), 'Timing', $requestGroup); } } } diff --git a/Classes/Features/PreventParallelPublishing/Domain/Repository/RunningRequestRepository.php b/Classes/Features/PreventParallelPublishing/Domain/Repository/RunningRequestRepository.php index 992167443..7dd32fe6e 100644 --- a/Classes/Features/PreventParallelPublishing/Domain/Repository/RunningRequestRepository.php +++ b/Classes/Features/PreventParallelPublishing/Domain/Repository/RunningRequestRepository.php @@ -58,7 +58,7 @@ public function flush(): void return; } foreach (array_chunk($this->inserts, 1000) as $chunk) { - $this->localDatabase->bulkInsert(self::RUNNING_REQUEST_TABLE_NAME, $this->inserts); + $this->localDatabase->bulkInsert(self::RUNNING_REQUEST_TABLE_NAME, $chunk); } $this->inserts = []; } @@ -73,8 +73,8 @@ public function isPublishingInDifferentRequest($identifier, string $tableName, s $query->select('*') ->from(self::RUNNING_REQUEST_TABLE_NAME) ->where($query->expr()->neq('request_token', $query->createNamedParameter($token))); - $result = $query->execute(); - foreach ($result->fetchAll() as $row) { + $result = $query->executeQuery(); + foreach ($result->fetchAllAssociative() as $row) { $this->rtc['content'][$row['table_name']][$row['record_id']] = true; } } @@ -86,6 +86,6 @@ public function deleteAllByToken(string $token): void $query = $this->localDatabase->createQueryBuilder(); $query->delete(self::RUNNING_REQUEST_TABLE_NAME) ->where($query->expr()->eq('request_token', $query->createNamedParameter($token))) - ->execute(); + ->executeStatement(); } } diff --git a/Classes/Features/PublishSorting/Domain/Anomaly/SortingPublisher.php b/Classes/Features/PublishSorting/Domain/Anomaly/SortingPublisher.php index 954bb131e..b184eb600 100644 --- a/Classes/Features/PublishSorting/Domain/Anomaly/SortingPublisher.php +++ b/Classes/Features/PublishSorting/Domain/Anomaly/SortingPublisher.php @@ -77,8 +77,8 @@ public function publishSortingRecursively(): void $query->select('uid', 'sorting') ->from($tableName) ->where($query->expr()->in('pid', $pidList)); - $statement = $query->execute(); - $localRows = $statement->fetchAllAssociative(); + $result = $query->executeQuery(); + $localRows = $result->fetchAllAssociative(); $updates = []; foreach ($localRows as $localRow) { @@ -93,7 +93,7 @@ public function publishSortingRecursively(): void $updateQuery->update($tableName) ->set($sortingField, $sorting) ->where($updateQuery->expr()->in('uid', $uidList)) - ->execute(); + ->executeStatement(); } } $this->sortingsToBePublished = []; diff --git a/Classes/Features/RecordInspector/Controller/RecordInspectorController.php b/Classes/Features/RecordInspector/Controller/RecordInspectorController.php index 19509ff7e..297a70713 100644 --- a/Classes/Features/RecordInspector/Controller/RecordInspectorController.php +++ b/Classes/Features/RecordInspector/Controller/RecordInspectorController.php @@ -9,17 +9,15 @@ use In2code\In2publishCore\Component\Core\Record\Model\FolderRecord; use In2code\In2publishCore\Component\Core\RecordTree\RecordTreeBuilderInjection; use In2code\In2publishCore\Component\Core\RecordTree\RecordTreeBuildRequest; -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; +use In2code\In2publishCore\Features\AdminTools\Controller\AbstractAdminToolsController; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use function array_combine; use function array_keys; use function array_merge; -class RecordInspectorController extends ActionController +class RecordInspectorController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; use RecordTreeBuilderInjection; use DefaultFalFinderInjection; diff --git a/Classes/Features/RedirectsSupport/Controller/RedirectController.php b/Classes/Features/RedirectsSupport/Controller/RedirectController.php index 4c49fed6c..4c8902d8e 100644 --- a/Classes/Features/RedirectsSupport/Controller/RedirectController.php +++ b/Classes/Features/RedirectsSupport/Controller/RedirectController.php @@ -37,6 +37,7 @@ use In2code\In2publishCore\Component\Core\Demand\Type\SysRedirectDemand; use In2code\In2publishCore\Component\Core\DemandResolver\DemandResolverInjection; use In2code\In2publishCore\Component\Core\Publisher\PublisherServiceInjection; +use In2code\In2publishCore\Component\Core\Publisher\PublishingContext; use In2code\In2publishCore\Component\Core\RecordCollection; use In2code\In2publishCore\Component\Core\RecordTree\RecordTree; use In2code\In2publishCore\Controller\Traits\ControllerModuleTemplate; @@ -47,10 +48,10 @@ use In2code\In2publishCore\Service\ForeignSiteFinderInjection; use Psr\Http\Message\ResponseInterface; use Throwable; -use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Pagination\ArrayPaginator; use TYPO3\CMS\Core\Pagination\SimplePagination; +use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; @@ -112,7 +113,7 @@ public function initializeListAction(): void } else { $filter = $GLOBALS['BE_USER']->getSessionData('tx_in2publishcore_redirects_filter'); if (null !== $filter) { - $this->request->setArgument('filter', $filter); + $this->request = $this->request->withArgument('filter', $filter); } $this->arguments->getArgument('filter')->getPropertyMappingConfiguration()->allowAllProperties(); } @@ -120,8 +121,6 @@ public function initializeListAction(): void /** * @param Filter|null $filter - * @param int $page - * @return ResponseInterface * @throws Throwable */ public function listAction(Filter $filter = null, int $page = 1): ResponseInterface @@ -129,7 +128,7 @@ public function listAction(Filter $filter = null, int $page = 1): ResponseInterf $query = $this->foreignDatabase->createQueryBuilder(); $query->getRestrictions()->removeAll(); $query->select('uid')->from('sys_redirect')->where($query->expr()->eq('deleted', 1)); - $foreignDeletedRedirects = $query->execute()->fetchAll(); + $foreignDeletedRedirects = $query->executeQuery()->fetchAllAssociative(); $additionalWhere = ''; if (!empty($foreignDeletedRedirects)) { $uidList = implode(',', array_column($foreignDeletedRedirects, 'uid')); @@ -170,7 +169,7 @@ public function publishAction(array $redirects): void $this->addFlashMessage( 'No redirect has been selected for publishing', 'Skipping publishing', - AbstractMessage::NOTICE, + ContextualFeedbackSeverity::NOTICE, ); $this->redirect('list'); } @@ -185,7 +184,9 @@ public function publishAction(array $redirects): void $recordCollection = new RecordCollection(); $this->demandResolver->resolveDemand($demands, $recordCollection); - $this->publisherService->publishRecordTree($recordTree); + $publishingContext = new PublishingContext($recordTree); + + $this->publisherService->publish($publishingContext); if (count($redirects) === 1) { $this->addFlashMessage(sprintf('Redirect %s published', reset($redirects))); @@ -196,9 +197,7 @@ public function publishAction(array $redirects): void } /** - * @param int $redirect * @param array|null $properties - * @return ResponseInterface * @throws Throwable */ public function selectSiteAction(int $redirect, array $properties = null): ResponseInterface diff --git a/Classes/Features/RedirectsSupport/Domain/Repository/SysRedirectRepository.php b/Classes/Features/RedirectsSupport/Domain/Repository/SysRedirectRepository.php index ab2a1d243..f8b247f6e 100644 --- a/Classes/Features/RedirectsSupport/Domain/Repository/SysRedirectRepository.php +++ b/Classes/Features/RedirectsSupport/Domain/Repository/SysRedirectRepository.php @@ -44,7 +44,7 @@ public function findHostsOfRedirects(): array ->from('sys_redirect') ->orderBy('source_host') ->groupBy('source_host') - ->execute() + ->executeQuery() ->fetchAllAssociative(); } @@ -55,7 +55,7 @@ public function findStatusCodesOfRedirects(): array ->from('sys_redirect') ->orderBy('target_statuscode') ->groupBy('target_statuscode') - ->execute() + ->executeQuery() ->fetchAllAssociative(); } @@ -66,7 +66,7 @@ public function findLocalRawByUid(int $redirect): ?Redirect ->from('sys_redirect') ->where('uid = :redirect') ->setParameter('redirect', $redirect) - ->execute() + ->executeQuery() ->fetchAssociative(); if (false === $row) { return null; diff --git a/Classes/Component/Core/FileHandling/FileRecordListener.php b/Classes/Features/ResolveFilesForIndices/EventListener/FileRecordListener.php similarity index 92% rename from Classes/Component/Core/FileHandling/FileRecordListener.php rename to Classes/Features/ResolveFilesForIndices/EventListener/FileRecordListener.php index 61cfc4e65..79a181660 100644 --- a/Classes/Component/Core/FileHandling/FileRecordListener.php +++ b/Classes/Features/ResolveFilesForIndices/EventListener/FileRecordListener.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace In2code\In2publishCore\Component\Core\FileHandling; +namespace In2code\In2publishCore\Features\ResolveFilesForIndices\EventListener; use In2code\In2publishCore\Component\Core\Demand\DemandsFactoryInjection; use In2code\In2publishCore\Component\Core\Demand\Type\FileDemand; @@ -17,18 +17,15 @@ class FileRecordListener use DemandsFactoryInjection; use DemandResolverInjection; - /** - * @var list - */ + /** @var array */ protected array $fileRecords = []; public function onRecordWasCreated(RecordWasCreated $event): void { $record = $event->getRecord(); - if ('sys_file' !== $record->getClassification()) { - return; + if ('sys_file' === $record->getClassification()) { + $this->fileRecords[] = $record; } - $this->fileRecords[] = $record; } public function onRecordRelationsWereResolved(): void diff --git a/Classes/Features/SysLogPublisher/Domain/Anomaly/SysLogPublisher.php b/Classes/Features/SysLogPublisher/Domain/Anomaly/SysLogPublisher.php index 2c976b46e..3eadf7ed5 100644 --- a/Classes/Features/SysLogPublisher/Domain/Anomaly/SysLogPublisher.php +++ b/Classes/Features/SysLogPublisher/Domain/Anomaly/SysLogPublisher.php @@ -64,7 +64,7 @@ protected function findLatestSysLogForPage(int $identifier): ?array ->where($query->expr()->eq('event_pid', $query->createNamedParameter($identifier))) ->orderBy('uid', 'DESC') ->setMaxResults(1); - $result = $query->execute(); + $result = $query->executeQuery(); $row = $result->fetchAssociative(); if (!$row) { return null; diff --git a/Classes/Features/SystemInformationExport/Controller/SystemInformationExportController.php b/Classes/Features/SystemInformationExport/Controller/SystemInformationExportController.php index 51a68681a..32ceb8bb4 100644 --- a/Classes/Features/SystemInformationExport/Controller/SystemInformationExportController.php +++ b/Classes/Features/SystemInformationExport/Controller/SystemInformationExportController.php @@ -29,12 +29,11 @@ * This copyright notice MUST APPEAR in all copies of the script! */ -use In2code\In2publishCore\Features\AdminTools\Controller\Traits\AdminToolsModuleTemplate; +use In2code\In2publishCore\Features\AdminTools\Controller\AbstractAdminToolsController; use In2code\In2publishCore\Features\SystemInformationExport\Service\SystemInformationExportService; use Psr\Http\Message\ResponseInterface; use TYPO3\CMS\Core\Messaging\AbstractMessage; use TYPO3\CMS\Extbase\Http\ForwardResponse; -use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; use TYPO3\CMS\Extbase\Mvc\Exception\NoSuchArgumentException; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; @@ -55,9 +54,8 @@ use const JSON_THROW_ON_ERROR; -class SystemInformationExportController extends ActionController +class SystemInformationExportController extends AbstractAdminToolsController { - use AdminToolsModuleTemplate; protected SystemInformationExportService $sysInfoExportService; @@ -92,8 +90,14 @@ public function sysInfoDecodeAction(string $json = ''): ResponseInterface } else { $args = [json_last_error(), json_last_error_msg()]; $this->addFlashMessage( - LocalizationUtility::translate('system_info.decode.json_error.details', 'in2publish_core', $args), - LocalizationUtility::translate('system_info.decode.json_error', 'in2publish_core'), + LocalizationUtility::translate( + 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:system_info.decode.json_error.details', + null, + $args + ), + LocalizationUtility::translate( + 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:system_info.decode.json_error', + ), AbstractMessage::ERROR, ); } diff --git a/Classes/Features/SystemInformationExport/Exporter/LogsExporter.php b/Classes/Features/SystemInformationExport/Exporter/LogsExporter.php index 560419e1f..ced48e1cd 100644 --- a/Classes/Features/SystemInformationExport/Exporter/LogsExporter.php +++ b/Classes/Features/SystemInformationExport/Exporter/LogsExporter.php @@ -34,7 +34,7 @@ use function json_decode; use function sprintf; -use function strftime; +use function date; use function substr; use const JSON_THROW_ON_ERROR; @@ -56,7 +56,7 @@ public function getInformation(): array ->where($logQueryBuilder->expr()->lte('level', 4)) ->setMaxResults(500) ->orderBy('uid', 'DESC') - ->execute() + ->executeQuery() ->fetchAllAssociative(); $logsFormatted = []; @@ -65,7 +65,7 @@ public function getInformation(): array '[%s] [lvl:%d] @%s "%s"', $log['component'], $log['level'], - strftime('%F %T', (int)$log['time_micro']), + date('Y-m-d H:i:s', (int)$log['time_micro']), $log['message'], ); $logData = $log['data']; diff --git a/Classes/Features/SystemInformationExport/Exporter/TcaExporter.php b/Classes/Features/SystemInformationExport/Exporter/TcaExporter.php index 5b72cc829..b71fe77ea 100644 --- a/Classes/Features/SystemInformationExport/Exporter/TcaExporter.php +++ b/Classes/Features/SystemInformationExport/Exporter/TcaExporter.php @@ -29,7 +29,7 @@ * This copyright notice MUST APPEAR in all copies of the script! */ -use In2code\In2publishCore\Component\Core\PreProcessing\TcaPreProcessingServiceInjection; +use In2code\In2publishCore\Component\Core\PreProcessing\CachedTcaPreProcessingServiceInjection; use ReflectionObject; use function get_class; @@ -38,7 +38,7 @@ class TcaExporter implements SystemInformationExporter { - use TcaPreProcessingServiceInjection; + use CachedTcaPreProcessingServiceInjection; public function getUniqueKey(): string { @@ -47,11 +47,11 @@ public function getUniqueKey(): string public function getInformation(): array { - $compatibleTcaParts = $this->tcaPreProcessingService->getCompatibleTcaParts(); + $compatibleTcaParts = $this->cachedTcaPreProcessingService->getCompatibleTcaParts(); return [ 'full' => $GLOBALS['TCA'], 'compatible' => $this->stripObjectsFromArray($compatibleTcaParts), - 'incompatible' => $this->tcaPreProcessingService->getIncompatibleTcaParts(), + 'incompatible' => $this->cachedTcaPreProcessingService->getIncompatibleTcaParts(), ]; } diff --git a/Classes/Middleware/BackendRouteInitialization.php b/Classes/Middleware/BackendRouteInitialization.php deleted file mode 100644 index 066f3e5d3..000000000 --- a/Classes/Middleware/BackendRouteInitialization.php +++ /dev/null @@ -1,100 +0,0 @@ - - * - * All rights reserved - * - * This script is part of the TYPO3 project. The TYPO3 project is - * free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * The GNU General Public License can be found at - * http://www.gnu.org/copyleft/gpl.html. - * - * This script is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This copyright notice MUST APPEAR in all copies of the script! - */ - -use In2code\In2publishCore\CommonInjection\EventDispatcherInjection; -use In2code\In2publishCore\Event\ExtTablesPostProcessingEvent; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Psr\Http\Server\MiddlewareInterface; -use Psr\Http\Server\RequestHandlerInterface; -use TYPO3\CMS\Backend\Routing\Exception\MethodNotAllowedException; -use TYPO3\CMS\Backend\Routing\Exception\ResourceNotFoundException; -use TYPO3\CMS\Backend\Routing\Router; -use TYPO3\CMS\Backend\Routing\UriBuilder; -use TYPO3\CMS\Core\Core\Bootstrap; -use TYPO3\CMS\Core\Http\RedirectResponse; -use TYPO3\CMS\Core\Http\Response; -use TYPO3\CMS\Core\Utility\GeneralUtility; - -/** - * This XCLASS dispatches an event after Bootstrap::loadExtTables() to truly replace ExtTablesPostProcessingHooks. - * - * XCLASS original \TYPO3\CMS\Backend\Middleware\BackendRouteInitialization - * Required until the patch got merged and released. - * - * Issue: https://forge.typo3.org/issues/95962 - * Patch: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72160 - * - * Have a look at the event ExtTablesPostProcessingEvent for more information. - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) Can't reduce. It is already as small as possible. And an XCLASS... - * @codeCoverageIgnore - */ -class BackendRouteInitialization implements MiddlewareInterface -{ - use EventDispatcherInjection; - - protected Router $router; - - public function __construct(Router $router) - { - $this->router = $router; - } - - /** - * Resolve the &route (or &M) GET/POST parameter, and also resolves a Route object - */ - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface - { - // Backend Routes from Configuration/Backend/{,Ajax}Routes.php will be implicitly loaded thanks to DI. - // Load ext_tables.php files to add routes from ExtensionManagementUtility::addModule() calls. - Bootstrap::loadExtTables(); - $this->eventDispatcher->dispatch(new ExtTablesPostProcessingEvent()); - - try { - $route = $this->router->matchRequest($request); - $request = $request->withAttribute('route', $route); - $request = $request->withAttribute('target', $route->getOption('target')); - // add the GET parameter "route" for backwards-compatibility - $queryParams = $request->getQueryParams(); - $queryParams['route'] = $route->getPath(); - $request = $request->withQueryParams($queryParams); - } catch (MethodNotAllowedException $e) { - return new Response(null, 405); - } catch (ResourceNotFoundException $e) { - // Route not found in system - $uri = GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute('login'); - return new RedirectResponse($uri); - } - - return $handler->handle($request); - } -} diff --git a/Classes/Middleware/InjectLoadingOverlayMiddleware.php b/Classes/Middleware/InjectLoadingOverlayMiddleware.php new file mode 100644 index 000000000..66e0e2780 --- /dev/null +++ b/Classes/Middleware/InjectLoadingOverlayMiddleware.php @@ -0,0 +1,90 @@ + +
+
+
+
+
+
+
+ +HTML; + protected const SUPPORTED_PATHS = [ + '/typo3/module/web/', + '/typo3/module/file/in2publish', + '/typo3/module/site/in2publish', + '/typo3/module/tools/in2publish', + '/typo3/module/in2publish', + '/module/web/In2publishM2', + ]; + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + if (!$this->isInSupportedPath($request)) { + return $handler->handle($request); + } + + /** @var PageRenderer $pageRenderer */ + $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); + $pageRenderer->addCssFile( + 'EXT:in2publish_core/Resources/Public/Css/LoadingOverlay.css', + 'stylesheet', + 'all', + '', + false, + ); + $pageRenderer->loadRequireJsModule('TYPO3/CMS/In2publishCore/LoadingOverlay'); + $pageRenderer->addInlineLanguageLabelFile('EXT:in2publish_core/Resources/Private/Language/locallang_js.xlf'); + + $response = $handler->handle($request); + + if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) { + $body = $response->getBody(); + if ($body->isSeekable()) { + $body->rewind(); + } + $contents = $body->getContents(); + + $offset = strpos($contents, '') + 6; + $contents = substr($contents, 0, $offset) . self::CODE . substr($contents, $offset); + + $streamFactory = GeneralUtility::makeInstance(StreamFactory::class); + $newBody = $streamFactory->createStream($contents); + $response = $response->withBody($newBody); + } + + return $response; + } + + protected function isInSupportedPath(ServerRequestInterface $request): bool + { + $requestPath = strtolower($request->getUri()->getPath()); + foreach (self::SUPPORTED_PATHS as $path) { + if (str_starts_with($requestPath, $path) || str_starts_with($_GET['route'] ?? '', $path)) { + return true; + } + } + return false; + } +} diff --git a/Classes/Service/Configuration/AbstractPageTypeService.php b/Classes/Service/Configuration/AbstractPageTypeService.php new file mode 100644 index 000000000..f76ce3c04 --- /dev/null +++ b/Classes/Service/Configuration/AbstractPageTypeService.php @@ -0,0 +1,67 @@ + + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use function array_keys; + +abstract class AbstractPageTypeService implements PageTypeService +{ + protected array $rtc = []; + + /** + * Finds all tables which are allowed on either self::TYPE_ROOT or self::TYPE_PAGE according to the table's TCA + * 'rootLevel' setting. + * + * @return array + */ + protected function getAllAllowedTableNames(string $type): array + { + if (!isset($this->rtc['_types'])) { + $allowed = [ + self::TYPE_ROOT => [], + self::TYPE_PAGE => [], + ]; + foreach (array_keys($GLOBALS['TCA']) as $table) { + switch ('pages' === $table ? -1 : (int)($GLOBALS['TCA'][$table]['ctrl']['rootLevel'] ?? 0)) { + case -1: + $allowed[self::TYPE_ROOT][] = $table; + $allowed[self::TYPE_PAGE][] = $table; + break; + case 0: + $allowed[self::TYPE_PAGE][] = $table; + break; + case 1: + $allowed[self::TYPE_ROOT][] = $table; + break; + } + } + $this->rtc['_types'] = $allowed; + } + return $this->rtc['_types'][$type]; + } +} diff --git a/Classes/Service/Configuration/IgnoredFieldsService.php b/Classes/Service/Configuration/IgnoredFieldsService.php index 69c57ce14..6812d97c1 100644 --- a/Classes/Service/Configuration/IgnoredFieldsService.php +++ b/Classes/Service/Configuration/IgnoredFieldsService.php @@ -34,6 +34,7 @@ use function explode; use function implode; use function is_array; +use function is_bool; use function is_string; use function preg_match; @@ -69,7 +70,7 @@ public function getIgnoredFields(string $table): array if (null === $ignoredCtrlFieldNames) { continue; } - if ($ignoredCtrl === 'versioningWS') { + if ($ignoredCtrl === 'versioningWS' && $ignoredCtrlFieldNames) { $ignoredCtrlFieldNames = 't3ver_oid,t3ver_wsid,t3ver_state,t3ver_stage'; } $ignoredCtrlFields = GeneralUtility::trimExplode(',', $ignoredCtrlFieldNames); @@ -86,7 +87,10 @@ public function getIgnoredFields(string $table): array return $this->rtc[$table]; } - protected function getValueByPath(array $array, string $path): ?string + /** + * @return string|bool|null + */ + protected function getValueByPath(array $array, string $path) { /** @var array|scalar $value */ $value = $array; @@ -101,7 +105,7 @@ protected function getValueByPath(array $array, string $path): ?string if (is_array($value)) { return implode(',', $value); } - if (!is_string($value)) { + if (!is_string($value) && !is_bool($value)) { return null; } return $value; diff --git a/Classes/Service/Configuration/LegacyPageTypeService.php b/Classes/Service/Configuration/LegacyPageTypeService.php new file mode 100644 index 000000000..0b9fd231f --- /dev/null +++ b/Classes/Service/Configuration/LegacyPageTypeService.php @@ -0,0 +1,64 @@ + + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use TYPO3\CMS\Core\Utility\GeneralUtility; + +use function strpos; + +class LegacyPageTypeService extends AbstractPageTypeService +{ + public function getTablesAllowedOnPage(int $pid, ?int $doktype): array + { + // The root page does not have a doktype. Just get all allowed tables. + if (0 === $pid) { + if (!isset($this->rtc[self::TYPE_ROOT])) { + $this->rtc[self::TYPE_ROOT] = $this->getAllAllowedTableNames(self::TYPE_ROOT); + } + return $this->rtc[self::TYPE_ROOT]; + } + + $type = isset($GLOBALS['PAGES_TYPES'][$doktype]['allowedTables']) ? $doktype : 'default'; + $key = self::TYPE_PAGE . '_' . $type; + + if (!isset($this->rtc[$key])) { + $allowedOnType = $this->getAllAllowedTableNames(self::TYPE_PAGE); + $allowedOnDoktype = $GLOBALS['PAGES_TYPES'][$type]['allowedTables']; + if (false === strpos($allowedOnDoktype, '*')) { + foreach ($allowedOnType as $index => $table) { + if (!GeneralUtility::inList($allowedOnDoktype, $table)) { + unset($allowedOnType[$index]); + } + } + } + + $this->rtc[$key] = $allowedOnType; + } + return $this->rtc[$key]; + } +} diff --git a/Classes/Service/Configuration/PageTypeRegistryService.php b/Classes/Service/Configuration/PageTypeRegistryService.php new file mode 100644 index 000000000..9a5164a21 --- /dev/null +++ b/Classes/Service/Configuration/PageTypeRegistryService.php @@ -0,0 +1,60 @@ + + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use In2code\In2publishCore\CommonInjection\PageDoktypeRegistryInjection; + +class PageTypeRegistryService extends AbstractPageTypeService +{ + use PageDoktypeRegistryInjection; + + public function getTablesAllowedOnPage(int $pid, ?int $doktype): array + { + // The root page does not have a doktype. Just get all allowed tables. + if (0 === $pid) { + if (!isset($this->rtc[self::TYPE_ROOT])) { + $this->rtc[self::TYPE_ROOT] = $this->getAllAllowedTableNames(self::TYPE_ROOT); + } + return $this->rtc[self::TYPE_ROOT]; + } + + $key = self::TYPE_PAGE . '_' . $doktype; + + if (!isset($this->rtc[$key])) { + $allowedOnType = $this->getAllAllowedTableNames(self::TYPE_PAGE); + foreach ($allowedOnType as $index => $table) { + if (!$this->pageDoktypeRegistry->isRecordTypeAllowedForDoktype($table, $doktype)) { + unset($allowedOnType[$index]); + } + } + + $this->rtc[$key] = $allowedOnType; + } + return $this->rtc[$key]; + } +} diff --git a/Classes/Service/Configuration/PageTypeService.php b/Classes/Service/Configuration/PageTypeService.php new file mode 100644 index 000000000..abf73b7ea --- /dev/null +++ b/Classes/Service/Configuration/PageTypeService.php @@ -0,0 +1,36 @@ + + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +interface PageTypeService +{ + public const TYPE_ROOT = 'root'; + public const TYPE_PAGE = 'page'; + + public function getTablesAllowedOnPage(int $pid, ?int $doktype): array; +} diff --git a/Classes/Service/Configuration/PageTypeServiceInjection.php b/Classes/Service/Configuration/PageTypeServiceInjection.php new file mode 100644 index 000000000..0bf343b1d --- /dev/null +++ b/Classes/Service/Configuration/PageTypeServiceInjection.php @@ -0,0 +1,21 @@ +pageTypeService = $pageTypeService; + } +} diff --git a/Classes/Service/Configuration/TcaService.php b/Classes/Service/Configuration/TcaService.php index d3d50fbb7..3d480711a 100644 --- a/Classes/Service/Configuration/TcaService.php +++ b/Classes/Service/Configuration/TcaService.php @@ -32,10 +32,8 @@ use TYPO3\CMS\Core\SingletonInterface; use TYPO3\CMS\Core\Utility\GeneralUtility; -use function array_keys; use function implode; use function in_array; -use function strpos; use function ucfirst; use function user_error; @@ -43,11 +41,6 @@ class TcaService implements SingletonInterface { - protected const TYPE_ROOT = 'root'; - protected const TYPE_PAGE = 'page'; - /** @var array RunTime Cache */ - protected array $rtc = []; - public function getRecordLabel(array $row, string $table): string { $labelField = $GLOBALS['TCA'][$table]['ctrl']['label'] ?? null; @@ -98,68 +91,6 @@ public function isHiddenRootTable(string $tableName): bool && in_array($GLOBALS['TCA'][$tableName]['ctrl']['rootLevel'], [1, -1], true); } - public function getTablesAllowedOnPage(int $pid, ?int $doktype): array - { - // The root page does not have a doktype. Just get all allowed tables. - if (0 === $pid) { - if (!isset($this->rtc[self::TYPE_ROOT])) { - $this->rtc[self::TYPE_ROOT] = $this->getAllAllowedTableNames(self::TYPE_ROOT); - } - return $this->rtc[self::TYPE_ROOT]; - } - - $type = isset($GLOBALS['PAGES_TYPES'][$doktype]['allowedTables']) ? $doktype : 'default'; - $key = self::TYPE_PAGE . '_' . $type; - - if (!isset($this->rtc[$key])) { - $allowedOnType = $this->getAllAllowedTableNames(self::TYPE_PAGE); - $allowedOnDoktype = $GLOBALS['PAGES_TYPES'][$type]['allowedTables']; - if (false === strpos($allowedOnDoktype, '*')) { - foreach ($allowedOnType as $index => $table) { - if (!GeneralUtility::inList($allowedOnDoktype, $table)) { - unset($allowedOnType[$index]); - } - } - } - - $this->rtc[$key] = $allowedOnType; - } - return $this->rtc[$key]; - } - - /** - * Finds all tables which are allowed on either self::TYPE_ROOT or self::TYPE_PAGE according to the table's TCA - * 'rootLevel' setting. - * - * @param string $type - * @return array - */ - protected function getAllAllowedTableNames(string $type): array - { - if (!isset($this->rtc['_types'])) { - $allowed = [ - self::TYPE_ROOT => [], - self::TYPE_PAGE => [], - ]; - foreach (array_keys($GLOBALS['TCA']) as $table) { - switch ('pages' === $table ? -1 : (int)($GLOBALS['TCA'][$table]['ctrl']['rootLevel'] ?? 0)) { - case -1: - $allowed[self::TYPE_ROOT][] = $table; - $allowed[self::TYPE_PAGE][] = $table; - break; - case 0: - $allowed[self::TYPE_PAGE][] = $table; - break; - case 1: - $allowed[self::TYPE_ROOT][] = $table; - break; - } - } - $this->rtc['_types'] = $allowed; - } - return $this->rtc['_types'][$type]; - } - /** * @deprecated Please access $GLOBALS['TCA'] directly. This Method will be removed in in2publish_core v13. */ diff --git a/Classes/Service/Database/RawRecordService.php b/Classes/Service/Database/RawRecordService.php index c5953976d..52e942ea9 100644 --- a/Classes/Service/Database/RawRecordService.php +++ b/Classes/Service/Database/RawRecordService.php @@ -89,7 +89,7 @@ protected function fetchRecord(string $table, int $uid, string $side): ?array ->from($table) ->where($query->expr()->eq('uid', $query->createNamedParameter($uid, PDO::PARAM_INT))) ->setMaxResults(1); - $statement = $query->execute(); + $statement = $query->executeQuery(); $result = $statement->fetchAssociative(); return is_array($result) ? $result : null; } diff --git a/Classes/Service/Environment/ForeignEnvironmentService.php b/Classes/Service/Environment/ForeignEnvironmentService.php index ef3296327..0fd8c0c00 100644 --- a/Classes/Service/Environment/ForeignEnvironmentService.php +++ b/Classes/Service/Environment/ForeignEnvironmentService.php @@ -29,10 +29,11 @@ * This copyright notice MUST APPEAR in all copies of the script! */ +use In2code\In2publishCore\Cache\CachedRuntimeCacheInjection; +use In2code\In2publishCore\Cache\Exception\CacheableValueCanNotBeGeneratedException; use In2code\In2publishCore\Command\Foreign\Status\CreateMasksCommand; use In2code\In2publishCore\Command\Foreign\Status\DbInitQueryEncodedCommand; use In2code\In2publishCore\Command\Foreign\Status\EncryptionKeyCommand; -use In2code\In2publishCore\CommonInjection\CacheInjection; use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandDispatcherInjection; use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandRequest; use Psr\Log\LoggerAwareInterface; @@ -53,20 +54,26 @@ class ForeignEnvironmentService implements LoggerAwareInterface { use LoggerAwareTrait; use RemoteCommandDispatcherInjection; - use CacheInjection; + use CachedRuntimeCacheInjection; public function getDatabaseInitializationCommands(): string { - if ($this->cache->has('foreign_db_init')) { - return $this->cache->get('foreign_db_init'); - } + return $this->cachedRuntimeCache->get('foreign_db_init', function (): string { + $request = new RemoteCommandRequest(DbInitQueryEncodedCommand::IDENTIFIER); + $response = $this->remoteCommandDispatcher->dispatch($request); - $request = new RemoteCommandRequest(DbInitQueryEncodedCommand::IDENTIFIER); - $response = $this->remoteCommandDispatcher->dispatch($request); + if (!$response->isSuccessful()) { + $this->logger->error( + 'Could not get DB init. Falling back to empty configuration value', + [ + 'errors' => $response->getErrors(), + 'exit_status' => $response->getExitStatus(), + 'output' => $response->getOutput(), + ], + ); + throw new CacheableValueCanNotBeGeneratedException(''); + } - $decodedDbInit = ''; - if ($response->isSuccessful()) { - // String (two double quotes): "" $encodedDbInit = 'IiI='; foreach ($response->getOutput() as $line) { if (false !== strpos($line, 'DBinit: ')) { @@ -74,25 +81,13 @@ public function getDatabaseInitializationCommands(): string break; } } - $decodedDbInit = json_decode(base64_decode($encodedDbInit), true, 512, JSON_THROW_ON_ERROR); - $this->cache->set('foreign_db_init', $decodedDbInit, [], 86400); - } else { - $this->logger->error( - 'Could not get DB init. Falling back to empty configuration value', - [ - 'errors' => $response->getErrors(), - 'exit_status' => $response->getExitStatus(), - 'output' => $response->getOutput(), - ], - ); - } - - return $decodedDbInit; + return json_decode(base64_decode($encodedDbInit), true, 512, JSON_THROW_ON_ERROR); + }); } public function getCreateMasks(): array { - if (!$this->cache->has('create_masks')) { + return $this->cachedRuntimeCache->get('create_masks', function (): ?array { $request = new RemoteCommandRequest(CreateMasksCommand::IDENTIFIER); $response = $this->remoteCommandDispatcher->dispatch($request); @@ -124,15 +119,13 @@ public function getCreateMasks(): array ]; } - $this->cache->set('create_masks', $createMasks, [], 86400); - } - - return (array)$this->cache->get('create_masks'); + return $createMasks; + }); } public function getEncryptionKey(): ?string { - if (!$this->cache->has('encryption_key')) { + return $this->cachedRuntimeCache->get('encryption_key', function (): ?string { $encryptionKey = null; $request = new RemoteCommandRequest(EncryptionKeyCommand::IDENTIFIER); @@ -144,10 +137,8 @@ public function getEncryptionKey(): ?string $encryptionKey = base64_decode($values['EKey']); } } - $this->cache->set('encryption_key', $encryptionKey, [], 86400); - } - - return $this->cache->get('encryption_key'); + return $encryptionKey; + }); } protected function tokenizeResponse(array $output): array diff --git a/Classes/Service/ForeignSiteFinder.php b/Classes/Service/ForeignSiteFinder.php index 0fb9d3180..8785e7ff0 100644 --- a/Classes/Service/ForeignSiteFinder.php +++ b/Classes/Service/ForeignSiteFinder.php @@ -29,10 +29,9 @@ * This copyright notice MUST APPEAR in all copies of the script! */ -use Closure; +use In2code\In2publishCore\Cache\CachedRuntimeCacheInjection; use In2code\In2publishCore\Command\Foreign\Status\AllSitesCommand; use In2code\In2publishCore\Command\Foreign\Status\SiteConfigurationCommand; -use In2code\In2publishCore\CommonInjection\CacheInjection; use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandDispatcherInjection; use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandRequest; use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandResponse; @@ -43,8 +42,10 @@ use TYPO3\CMS\Core\Exception\Page\PageNotFoundException; use TYPO3\CMS\Core\Exception\SiteNotFoundException; use TYPO3\CMS\Core\Http\Uri; +use TYPO3\CMS\Core\Localization\Locale; use TYPO3\CMS\Core\Site\Entity\Site; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; +use TYPO3\CMS\Core\Site\Entity\SiteSettings; use function array_key_exists; use function base64_decode; @@ -55,9 +56,15 @@ class ForeignSiteFinder implements LoggerAwareInterface { use LoggerAwareTrait; use RemoteCommandDispatcherInjection; - use CacheInjection; + use CachedRuntimeCacheInjection; - private const UNSERIALIZE_ALLOWED_CLASS = [Site::class, Uri::class, SiteLanguage::class]; + private const UNSERIALIZE_ALLOWED_CLASS = [ + Site::class, + Uri::class, + SiteLanguage::class, + Locale::class, + SiteSettings::class, + ]; public function getSiteByPageId(int $pageId): Site { @@ -87,7 +94,7 @@ public function getSiteByPageId(int $pageId): Site ); throw new In2publishCoreException('An error occurred while fetching a remote site config', 1620723511); }; - return $this->executeCached('site_page_' . $pageId, $closure); + return $this->cachedRuntimeCache->get('site_page_' . $pageId, $closure); } /** @return Site[] */ @@ -110,7 +117,8 @@ public function getAllSites(): array ); throw new AllSitesCommandException($exitStatus, $errors, $output); }; - return $this->executeCached('sites', $closure); + + return $this->cachedRuntimeCache->get('sites', $closure); } public function getSiteByIdentifier(string $identifier): ?Site @@ -133,14 +141,4 @@ protected function processCommandResult(RemoteCommandResponse $response) } return $result; } - - protected function executeCached(string $cacheKey, Closure $closure) - { - if (!$this->cache->has($cacheKey)) { - $result = $closure(); - $this->cache->set($cacheKey, $result); - return $result; - } - return $this->cache->get($cacheKey); - } } diff --git a/Classes/Service/ReplaceMarkersService.php b/Classes/Service/ReplaceMarkersService.php index db40ae78f..c55c3c283 100755 --- a/Classes/Service/ReplaceMarkersService.php +++ b/Classes/Service/ReplaceMarkersService.php @@ -33,7 +33,6 @@ use In2code\In2publishCore\CommonInjection\FlexFormToolsInjection; use In2code\In2publishCore\CommonInjection\LocalDatabaseInjection; use In2code\In2publishCore\CommonInjection\SiteFinderInjection; -use In2code\In2publishCore\Component\Core\PreProcessing\TcaPreProcessingService; use In2code\In2publishCore\Component\Core\Record\Model\DatabaseEntityRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; use In2code\In2publishCore\Component\Core\Record\Model\VirtualFlexFormRecord; @@ -71,12 +70,6 @@ class ReplaceMarkersService implements LoggerAwareInterface // Also replace optional quotes around the REC_FIELD_ because we will quote the actual value protected const REC_FIELD_REGEX = '~\'?###REC_FIELD_(.*?)###\'?~'; protected const SITE_FIELD_REGEX = '(###SITE:([^#]+)###)'; - protected TcaPreProcessingService $tcaPreProcessingService; - - public function injectTcaPreProcessingService(TcaPreProcessingService $tcaPreProcessingService): void - { - $this->tcaPreProcessingService = $tcaPreProcessingService; - } /** * replaces ###MARKER### where possible. It's missing diff --git a/Classes/Testing/Data/FalStorageTestSubjectsProvider.php b/Classes/Testing/Data/FalStorageTestSubjectsProvider.php index 1155ca57d..154047422 100644 --- a/Classes/Testing/Data/FalStorageTestSubjectsProvider.php +++ b/Classes/Testing/Data/FalStorageTestSubjectsProvider.php @@ -97,7 +97,7 @@ protected function fetchStorages(Connection $connection): array $rows = $query->select('*') ->from('sys_file_storage') ->where($query->expr()->eq('deleted', 0)) - ->execute() + ->executeQuery() ->fetchAllAssociative(); return array_combine(array_column($rows, 'uid'), $rows); } diff --git a/Classes/Testing/Data/RequiredTablesDataProvider.php b/Classes/Testing/Data/RequiredTablesDataProvider.php index ca61bef1b..c9abccae5 100644 --- a/Classes/Testing/Data/RequiredTablesDataProvider.php +++ b/Classes/Testing/Data/RequiredTablesDataProvider.php @@ -49,6 +49,7 @@ public function getRequiredTables(): array 'tx_in2publishcore_running_request', 'tx_in2code_rpc_request', 'tx_in2code_rpc_data', + 'tx_in2publishcore_filepublisher_instruction', ]; $requiredTables = $this->overruleTables($requiredTables); $this->cache = $requiredTables; diff --git a/Classes/Testing/Tests/Adapter/TransmissionAdapterTest.php b/Classes/Testing/Tests/Adapter/TransmissionAdapterTest.php index b790c8c59..04bbcc638 100644 --- a/Classes/Testing/Tests/Adapter/TransmissionAdapterTest.php +++ b/Classes/Testing/Tests/Adapter/TransmissionAdapterTest.php @@ -35,6 +35,7 @@ use In2code\In2publishCore\Component\TemporaryAssetTransmission\AssetTransmitterInjection; use In2code\In2publishCore\Component\TemporaryAssetTransmission\TransmissionAdapter\AdapterInterface; use In2code\In2publishCore\Testing\Tests\Configuration\ConfigurationFormatTest; +use In2code\In2publishCore\Testing\Tests\SshConnection\SshConnectionTest; use In2code\In2publishCore\Testing\Tests\TestCaseInterface; use In2code\In2publishCore\Testing\Tests\TestResult; use Throwable; @@ -174,6 +175,7 @@ public function getDependencies(): array $dependencies = [ ConfigurationFormatTest::class, AdapterSelectionTest::class, + SshConnectionTest::class, ]; if (isset($GLOBALS['in2publish_core']['virtual_tests'][AdapterInterface::class])) { $dependencies = array_merge( diff --git a/Classes/Testing/Tests/Application/AbstractDomainTest.php b/Classes/Testing/Tests/Application/AbstractDomainTest.php index 6cdd61d49..6c89ca44d 100644 --- a/Classes/Testing/Tests/Application/AbstractDomainTest.php +++ b/Classes/Testing/Tests/Application/AbstractDomainTest.php @@ -145,12 +145,12 @@ protected function findAllRootPages(): Result $query->select('uid') ->from('pages') ->where( - $query->expr()->andX( + $query->expr()->and( $query->expr()->eq('is_siteroot', $query->createNamedParameter(1)), $query->expr()->eq('sys_language_uid', $query->createNamedParameter(0)), ), ); - return $query->execute(); + return $query->executeQuery(); } protected function determineDomainTypes(array $pageIds): array diff --git a/Classes/Testing/Tests/Application/LocalInstanceTest.php b/Classes/Testing/Tests/Application/LocalInstanceTest.php index 6613e84bd..88f845a88 100644 --- a/Classes/Testing/Tests/Application/LocalInstanceTest.php +++ b/Classes/Testing/Tests/Application/LocalInstanceTest.php @@ -50,7 +50,7 @@ public function run(): TestResult } $excludedTables = $this->configContainer->get('excludeRelatedTables'); - $localTables = array_flip($this->localDatabase->getSchemaManager()->listTableNames()); + $localTables = array_flip($this->localDatabase->createSchemaManager()->listTableNames()); $missingTables = []; diff --git a/Classes/Testing/Tests/Database/DatabaseDifferencesTest.php b/Classes/Testing/Tests/Database/DatabaseDifferencesTest.php index 56c62590d..622218954 100644 --- a/Classes/Testing/Tests/Database/DatabaseDifferencesTest.php +++ b/Classes/Testing/Tests/Database/DatabaseDifferencesTest.php @@ -217,7 +217,7 @@ protected function areDifferentDatabases(Connection $local, Connection $foreign) $statement = $query->select('*') ->from('tx_in2code_in2publish_task') ->where($query->expr()->eq('task_type', $query->createNamedParameter('Backend Test'))) - ->execute(); + ->executeQuery(); $identical = false; while ($result = $statement->fetchAssociative()) { if ($uid === (int)$result['uid'] && $random === (int)$result['configuration']) { @@ -234,7 +234,7 @@ protected function areDifferentDatabases(Connection $local, Connection $foreign) protected function readTableStructure(Connection $database): array { $tableStructure = []; - $tables = $database->getSchemaManager()->listTables(); + $tables = $database->createSchemaManager()->listTables(); foreach ($tables as $table) { $tableName = $table->getName(); @@ -244,7 +244,7 @@ protected function readTableStructure(Connection $database): array } $fieldStructure = []; - $fields = $database->getSchemaManager()->listTableColumns($tableName); + $fields = $database->createSchemaManager()->listTableColumns($tableName); foreach ($fields as $field) { $fieldName = $field->getName(); $fieldStructure[$fieldName] = [ diff --git a/Classes/Testing/Tests/Database/TableGarbageCollectorTest.php b/Classes/Testing/Tests/Database/TableGarbageCollectorTest.php index 4a77387d7..938d377e5 100644 --- a/Classes/Testing/Tests/Database/TableGarbageCollectorTest.php +++ b/Classes/Testing/Tests/Database/TableGarbageCollectorTest.php @@ -54,9 +54,9 @@ public function run(): TestResult $query->createNamedParameter('%tx_in2publishcore_running_request%'), ), ); - $statement = $query->execute(); + $statement = $query->executeQuery(); - if (0 === $statement->fetchColumn()) { + if (0 === $statement->fetchOne()) { return new TestResult( 'database.garbage_collector_task_missing', TestResult::ERROR, diff --git a/Classes/Testing/Tests/Fal/MissingStoragesTest.php b/Classes/Testing/Tests/Fal/MissingStoragesTest.php index e5821beb8..d0af8f669 100644 --- a/Classes/Testing/Tests/Fal/MissingStoragesTest.php +++ b/Classes/Testing/Tests/Fal/MissingStoragesTest.php @@ -71,8 +71,8 @@ protected function getMissing(array $storages, array $messages, string $side): a if (!empty($missingOnSide)) { // fal.missing_on_local | fal.missing_on_foreign $messages[] = 'fal.missing_on_' . $side; - foreach ($missingOnSide as $storageUid) { - $messages[] = sprintf('[%d] %s', $storageUid, $storages[$opposite][$storageUid]['name']); + foreach ($missingOnSide as $storage) { + $messages[] = sprintf('[%d] %s', $storage, $storages[$opposite][$storage]['name']); } } return $messages; diff --git a/Classes/Testing/Tests/Fal/UniqueStorageTargetTest.php b/Classes/Testing/Tests/Fal/UniqueStorageTargetTest.php index 3a1a5d06c..3e6d33cb9 100644 --- a/Classes/Testing/Tests/Fal/UniqueStorageTargetTest.php +++ b/Classes/Testing/Tests/Fal/UniqueStorageTargetTest.php @@ -31,7 +31,8 @@ use Doctrine\DBAL\Driver\Exception as DriverException; use In2code\In2publishCore\CommonInjection\ResourceFactoryInjection; -use In2code\In2publishCore\Component\Core\FileHandling\Service\ForeignFileSystemInfoServiceInjection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FileInfo; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\ForeignFileInfoServiceInjection; use In2code\In2publishCore\Testing\Data\FalStorageTestSubjectsProviderInjection; use In2code\In2publishCore\Testing\Tests\Application\ForeignDatabaseConfigTest; use In2code\In2publishCore\Testing\Tests\Application\ForeignInstanceTest; @@ -57,7 +58,7 @@ class UniqueStorageTargetTest implements TestCaseInterface { use ResourceFactoryInjection; - use ForeignFileSystemInfoServiceInjection; + use ForeignFileInfoServiceInjection; use FalStorageTestSubjectsProviderInjection; /** @@ -91,12 +92,14 @@ public function run(): TestResult /** @var DriverInterface $localDriver */ $localDriver = $driverProperty->getValue($storageObject); + $storageUid = (int)$storages['foreign'][$key]['uid']; try { do { $uniqueFile = uniqid('tx_in2publish_testfile'); } while ( $localDriver->fileExists($uniqueFile) - || $this->foreignFileSystemInfoService->fileExists($storages['foreign'][$key]['uid'], $uniqueFile) + || $this->foreignFileInfoService->getFileInfo([$storageUid => [$uniqueFile]]) + ->getInfo($storageUid, $uniqueFile) instanceof FileInfo ); } catch (RuntimeException $e) { if (preg_match('/The storage \d+ is offline/', $e->getMessage())) { @@ -109,7 +112,8 @@ public function run(): TestResult $addedFile = $localDriver->addFile($sourceFile, $localDriver->getRootLevelFolder(), $uniqueFile); if ($uniqueFile === ltrim($addedFile, '/')) { - if ($this->foreignFileSystemInfoService->fileExists($storages['foreign'][$key]['uid'], $uniqueFile)) { + $fileInfoCollection = $this->foreignFileInfoService->getFileInfo([$storageUid => [$uniqueFile]]); + if ($fileInfoCollection->getInfo($storageUid, $uniqueFile) instanceof FileInfo) { $affectedStorages[] = '[' . $key . '] ' . $storages['local'][$key]['name']; } } else { diff --git a/Classes/Testing/Tests/SshConnection/SshConnectionTest.php b/Classes/Testing/Tests/SshConnection/SshConnectionTest.php index bddd63ce5..498466276 100644 --- a/Classes/Testing/Tests/SshConnection/SshConnectionTest.php +++ b/Classes/Testing/Tests/SshConnection/SshConnectionTest.php @@ -40,6 +40,8 @@ use function array_diff; use function preg_match; +use const In2code\In2publishCore\TYPO3_V11; + class SshConnectionTest implements TestCaseInterface { use ConfigContainerInjection; @@ -65,6 +67,14 @@ public function run(): TestResult // This is the first time a RCE is executed, so we have to test here for the missing document root folder if (!$response->isSuccessful()) { + if ($response->getExitStatus() === 1425401293 || $response->getExitStatus() === 1425401287) { + return new TestResult( + 'ssh_connection.connection_failed', + TestResult::ERROR, + ['ssh_connection.connection_failure_message', $response->getErrorsString()], + ); + } + return new TestResult( 'ssh_connection.foreign_document_root_missing', TestResult::ERROR, @@ -99,8 +109,10 @@ public function run(): TestResult $requiredNames = [ 'typo3', 'index.php', - 'typo3conf', ]; + if (TYPO3_V11) { + $requiredNames[] = 'typo3conf'; + } if (!empty(array_diff($requiredNames, $documentRootFiles))) { return new TestResult('ssh_connection.foreign_document_root_wrong', TestResult::ERROR); diff --git a/Classes/Testing/Tests/TestResult.php b/Classes/Testing/Tests/TestResult.php index faaf45c35..966c2b340 100644 --- a/Classes/Testing/Tests/TestResult.php +++ b/Classes/Testing/Tests/TestResult.php @@ -90,6 +90,21 @@ public function getSeverityLabel(): string } } + public function getSeverityState(): int + { + switch ($this->severity) { + case self::OK: + return 0; + case self::WARNING: + return 1; + case self::SKIPPED: + return -2; + case self::ERROR: + default: + return 2; + } + } + public function getTranslatedLabel(): string { return TestLabelLocalizer::translate($this->label, $this->labelArguments); diff --git a/Classes/Utility/BackendUtility.php b/Classes/Utility/BackendUtility.php index 814552f74..22ee1e44e 100755 --- a/Classes/Utility/BackendUtility.php +++ b/Classes/Utility/BackendUtility.php @@ -29,6 +29,7 @@ */ use Closure; +use Doctrine\DBAL\Exception; use In2code\In2publishCore\Service\Database\RawRecordService; use In2code\In2publishCore\Service\Environment\ForeignEnvironmentService; use In2code\In2publishCore\Service\Routing\SiteService; @@ -86,6 +87,7 @@ class BackendUtility * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.IfStatementAssignment) * @noinspection CallableParameterUseCaseInTypeContextInspection + * @throws Exception */ public static function getPageIdentifier($identifier = null, string $table = null) { @@ -133,7 +135,10 @@ public static function getPageIdentifier($identifier = null, string $table = nul } $localConnection = DatabaseUtility::buildLocalDatabaseConnection(); - $tableNames = $localConnection->getSchemaManager()->listTableNames(); + if (null === $localConnection) { + return 0; + } + $tableNames = $localConnection->createSchemaManager()->listTableNames(); // get id from record ?data[tt_content][13]=foo $data = GeneralUtility::_GP('data'); @@ -158,7 +163,7 @@ public static function getPageIdentifier($identifier = null, string $table = nul ->from($table) ->where($query->expr()->eq('uid', (int)key($data[$table]))) ->setMaxResults(1) - ->execute() + ->executeQuery() ->fetchAssociative(); if (false !== $result && isset($result['pid'])) { return (int)$result['pid']; @@ -180,7 +185,7 @@ public static function getPageIdentifier($identifier = null, string $table = nul ->from($rollbackData[0]) ->where($query->expr()->eq('uid', (int)$rollbackData[1])) ->setMaxResults(1) - ->execute() + ->executeQuery() ->fetchAssociative(); if (false !== $result && isset($result['pid'])) { return (int)$result['pid']; @@ -197,7 +202,7 @@ public static function getPageIdentifier($identifier = null, string $table = nul ->from($table) ->where($query->expr()->eq('uid', (int)$identifier)) ->setMaxResults(1) - ->execute() + ->executeQuery() ->fetchAssociative(); if (isset($row['pid'])) { return (int)$row['pid']; @@ -210,12 +215,6 @@ public static function getPageIdentifier($identifier = null, string $table = nul /** * Please don't blame me for this. * - * @param string $table - * @param int $identifier - * @param string $stagingLevel - * - * @return UriInterface|null - * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -338,8 +337,6 @@ protected static function parseAdditionalGetParameters(array &$parameters, array } /** - * @return VariableFrontend - * * @throws NoSuchCacheException */ protected static function getRuntimeCache(): VariableFrontend @@ -419,11 +416,6 @@ protected static function getLocalUriClosure(Site $site, int $pageUid, array $ad } /** - * @param Site $site - * @param int $language - * @param int $pageUid - * - * @return string * @throws AspectNotFoundException */ protected static function getPageRepositoryPageCacheIdentifier(Site $site, int $language, int $pageUid): string diff --git a/Classes/Utility/DatabaseUtility.php b/Classes/Utility/DatabaseUtility.php index 2e5374ad4..e775286a7 100755 --- a/Classes/Utility/DatabaseUtility.php +++ b/Classes/Utility/DatabaseUtility.php @@ -95,7 +95,7 @@ public static function buildForeignDatabaseConnection(): ?Connection try { $foreignConnection = $connectionPool->getConnectionByName('in2publish_foreign'); - foreach ($foreignConnection->getEventManager()->getListeners() as $event => $listeners) { + foreach ($foreignConnection->getEventManager()->getAllListeners() as $event => $listeners) { foreach ($listeners as $listener) { $foreignConnection->getEventManager()->removeEventListener($event, $listener); } @@ -201,7 +201,7 @@ public static function getTreeList($id, $depth, $begin = 0, $permClause = ''): s if ($permClause !== '') { $queryBuilder->andWhere(self::stripLogicalOperatorPrefix($permClause)); } - $statement = $queryBuilder->execute(); + $statement = $queryBuilder->executeQuery(); while ($row = $statement->fetchAssociative()) { if ($begin <= 0) { $theList .= ',' . $row['uid']; diff --git a/Classes/ViewHelpers/File/BuildResourcePathViewHelper.php b/Classes/ViewHelpers/File/BuildResourcePathViewHelper.php deleted file mode 100644 index 3b9c9e089..000000000 --- a/Classes/ViewHelpers/File/BuildResourcePathViewHelper.php +++ /dev/null @@ -1,90 +0,0 @@ -, - * Oliver Eglseder - * - * This script is part of the TYPO3 project. The TYPO3 project is - * free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * The GNU General Public License can be found at - * http://www.gnu.org/copyleft/gpl.html. - * - * This script is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * This copyright notice MUST APPEAR in all copies of the script! - */ - -use In2code\In2publishCore\CommonInjection\SiteFinderInjection; -use In2code\In2publishCore\Service\ForeignSiteFinderInjection; -use TYPO3\CMS\Core\Exception\SiteNotFoundException; -use TYPO3\CMS\Core\Http\Uri; -use TYPO3\CMS\Core\Resource\ResourceFactory; -use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; - -use function ltrim; -use function rtrim; - -class BuildResourcePathViewHelper extends AbstractViewHelper -{ - use SiteFinderInjection; - use ForeignSiteFinderInjection; - - protected ResourceFactory $resourceFactory; - /** @var Uri[] */ - protected array $domains; - - public function __construct( - ResourceFactory $resourceFactory - ) { - $this->resourceFactory = $resourceFactory; - } - - /** - * @throws SiteNotFoundException - */ - public function initialize(): void - { - $this->domains['local'] = $this->siteFinder->getSiteByIdentifier('main')->getBase(); - $this->domains['foreign'] = $this->foreignSiteFinder->getSiteByIdentifier('main')->getBase(); - } - - public function initializeArguments(): void - { - parent::initializeArguments(); - $this->registerArgument('publicUrl', 'string', 'Url of the record', true); - $this->registerArgument('stagingLevel', 'string', 'Sets the staging level [LOCAL/foreign]', true, 'local'); - } - - public function render(): string - { - /** @var string $stagingLevel */ - $stagingLevel = $this->arguments['stagingLevel']; - - /** @var string $publicUrl */ - $publicUrl = $this->arguments['publicUrl']; - - // If the URI is absolute we don't need to prefix it. - $resourceUri = new Uri($publicUrl); - if (!empty($resourceUri->getHost())) { - return $publicUrl; - } - - $uri = $this->domains[$stagingLevel]; - $uri = $uri->withPath(rtrim($uri->getPath(), '/') . '/' . ltrim($publicUrl, '/')); - return (string)$uri; - } -} diff --git a/Classes/ViewHelpers/File/IconViewHelper.php b/Classes/ViewHelpers/File/IconViewHelper.php index 59a1d254e..1fe371bd8 100644 --- a/Classes/ViewHelpers/File/IconViewHelper.php +++ b/Classes/ViewHelpers/File/IconViewHelper.php @@ -28,7 +28,9 @@ */ use In2code\In2publishCore\CommonInjection\IconFactoryInjection; +use In2code\In2publishCore\Component\Core\Record\Model\FolderRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; +use In2code\In2publishCore\Component\Core\Record\Model\StorageRootFolderRecord; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\Imaging\IconRegistry; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; @@ -70,6 +72,12 @@ public function render(): string protected function getIconIdentifier(Record $record): ?string { + if ($record instanceof StorageRootFolderRecord) { + return 'apps-filetree-mount'; + } + if ($record instanceof FolderRecord) { + return 'files-folder'; + } $mimeType = $record->getProp('mime_type'); if ($mimeType === null) { return $this->getIconIdentifierForFileExtension($record); diff --git a/Configuration/Backend/Modules.php b/Configuration/Backend/Modules.php new file mode 100644 index 000000000..b5e9dfe83 --- /dev/null +++ b/Configuration/Backend/Modules.php @@ -0,0 +1,108 @@ + + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use In2code\In2publishCore\Component\ConfigContainer\ConfigContainer; +use In2code\In2publishCore\Controller\FileController; +use In2code\In2publishCore\Controller\RecordController; +use In2code\In2publishCore\Features\AdminTools\Service\ToolsRegistry; +use In2code\In2publishCore\Features\RedirectsSupport\Controller\RedirectController; +use TYPO3\CMS\Core\Utility\GeneralUtility; + +$backendModulesToRegister = []; + +/** @var ConfigContainer $configContainer */ +$configContainer = GeneralUtility::makeInstance(ConfigContainer::class); + +if ($configContainer->get('module.m1')) { + $backendModulesToRegister['in2publish_core_m1'] = [ + 'parent' => 'web', + 'position' => [], + 'access' => 'user', + 'workspaces' => 'live', + 'path' => '/module/in2publish_core/m1', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod1.xlf', + 'extensionName' => 'in2publish_core', + 'iconIdentifier' => 'in2publish-core-overview-module', + 'controllerActions' => [ + RecordController::class => ['index', 'detail', 'publishRecord', 'toggleFilterStatus'], + ], + ]; +} + +if ($configContainer->get('module.m3')) { + $backendModulesToRegister['in2publish_core_m3'] = [ + 'parent' => 'file', + 'position' => [], + 'access' => 'user', + 'workspaces' => 'live', + 'path' => '/module/in2publish_core/m3', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod3.xlf', + 'extensionName' => 'in2publish_core', + 'iconIdentifier' => 'in2publish-core-file-module', + 'controllerActions' => [ + FileController::class => ['index', 'publishFolder', 'publishFile', 'toggleFilterStatus'], + ], + ]; +} + +if ($configContainer->get('module.m4')) { + $toolsRegistry = GeneralUtility::makeInstance(ToolsRegistry::class); + $controllerActions = $toolsRegistry->processData(); + if (!empty($controllerActions)) { + $backendModulesToRegister['in2publish_core_m4'] = [ + 'parent' => 'tools', + 'position' => [], + 'access' => 'admin', + 'workspaces' => 'live', + 'path' => '/module/in2publish_core/m4', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf', + 'extensionName' => 'in2publish_core', + 'iconIdentifier' => 'in2publish-core-tools-module', + 'controllerActions' => $controllerActions, + ]; + } +} + +if ($configContainer->get('features.redirectsSupport.enable')) { + $backendModulesToRegister['in2publish_core_m5'] = [ + 'parent' => 'site', + 'position' => ['after' => 'redirects'], + 'access' => 'user', + 'workspaces' => 'live', + 'path' => '/module/in2publish_core/m5', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod5.xlf', + 'extensionName' => 'in2publish_core', + 'iconIdentifier' => 'in2publish-core-redirect-module', + 'controllerActions' => [ + RedirectController::class => ['list','publish','selectSite'], + ], + ]; +} + +return $backendModulesToRegister; diff --git a/Configuration/Component/ConfigContainer/Services.yaml b/Configuration/Component/ConfigContainer/Services.yaml index 41d6f7c9c..46af9de0a 100644 --- a/Configuration/Component/ConfigContainer/Services.yaml +++ b/Configuration/Component/ConfigContainer/Services.yaml @@ -9,13 +9,6 @@ services: In2code\In2publishCore\Component\ConfigContainer\: resource: '../../../Classes/Component/ConfigContainer/*' - In2code\In2publishCore\Component\ConfigContainer\Provider\PageTsProvider: - tags: - - name: event.listener - identifier: 'in2publishcore-PageTsProvider-bootCompleted' - method: 'processData' - event: In2code\In2publishCore\Event\ExtTablesPostProcessingEvent - In2code\In2publishCore\Component\ConfigContainer\ConfigContainer: shared: true public: true diff --git a/Configuration/Component/Core/Services.php b/Configuration/Component/Core/Services.php index 060cefa27..21cc8f1eb 100644 --- a/Configuration/Component/Core/Services.php +++ b/Configuration/Component/Core/Services.php @@ -8,16 +8,19 @@ use In2code\In2publishCore\Component\Core\DependencyInjection\PublisherPass; use In2code\In2publishCore\Component\Core\DependencyInjection\RecordExtensionProvider\RecordExtensionsProvider; use In2code\In2publishCore\Component\Core\DependencyInjection\RecordExtensionTraitCompilerPass; +use In2code\In2publishCore\Component\Core\DependencyInjection\StaticResolverPass; use In2code\In2publishCore\Component\Core\DependencyInjection\TcaPreProcessorPass; use In2code\In2publishCore\Component\Core\PreProcessing\TcaPreProcessor; use In2code\In2publishCore\Component\Core\Publisher\Publisher; use In2code\In2publishCore\Component\Core\Resolver\Resolver; +use In2code\In2publishCore\Component\Core\Resolver\StaticResolver; use Symfony\Component\DependencyInjection\ContainerBuilder; use TYPO3\CMS\Core\DependencyInjection\PublicServicePass; return static function (ContainerBuilder $builder): void { $builder->registerForAutoconfiguration(TcaPreProcessor::class)->addTag('in2publish_core.tca.preprocessor'); $builder->registerForAutoconfiguration(Resolver::class)->addTag('in2publish_core.tca.resolver'); + $builder->registerForAutoconfiguration(StaticResolver::class)->addTag('in2publish_core.static_resolver'); $builder->registerForAutoconfiguration(Publisher::class)->addTag('in2publish_core.publisher'); $builder->registerForAutoconfiguration(DemandResolver::class)->addTag('in2publish_core.tca.demand_resolver'); $builder->registerForAutoconfiguration(RecordExtensionsProvider::class) @@ -25,6 +28,7 @@ $builder->addCompilerPass(new TcaPreProcessorPass('in2publish_core.tca.preprocessor')); $builder->addCompilerPass(new PublicServicePass('in2publish_core.tca.resolver', true)); + $builder->addCompilerPass(new StaticResolverPass('in2publish_core.static_resolver')); $builder->addCompilerPass(new PublisherPass('in2publish_core.publisher')); $builder->addCompilerPass(new DemandResolverPass('in2publish_core.tca.demand_resolver')); $builder->addCompilerPass(new DatabaseRecordFactoryFactoryCompilerPass('in2publish_core.factory.database_record')); diff --git a/Configuration/Component/Core/Services.yaml b/Configuration/Component/Core/Services.yaml index 6df2a5d27..260126f97 100644 --- a/Configuration/Component/Core/Services.yaml +++ b/Configuration/Component/Core/Services.yaml @@ -32,17 +32,6 @@ services: arguments: $connection: '@In2code.In2publishCore.Database.Foreign' - In2code\In2publishCore\Component\Core\FileHandling\FileRecordListener: - tags: - - name: event.listener - identifier: 'in2publishcore-FileRecordListener-RecordWasCreated' - method: 'onRecordWasCreated' - event: In2code\In2publishCore\Event\RecordWasCreated - - name: event.listener - identifier: 'in2publishcore-FileRecordListener-RecordRelationsWereResolved' - method: 'onRecordRelationsWereResolved' - event: In2code\In2publishCore\Event\RecordRelationsWereResolved - In2code\In2publishCore\Component\Core\Publisher\Command\FalPublisherCommand: tags: - name: 'console.command' @@ -52,3 +41,7 @@ services: In2code\In2publishCore\Component\Core\DemandResolver\DemandResolver: factory: [ '@In2code\In2publishCore\Component\Core\DemandResolver\DemandResolverFactory', 'createDemandResolver' ] + + In2code\In2publishCore\Component\Core\Publisher\PublisherService: + public: true + shared: true diff --git a/Configuration/Features/AdminTools/Services.yaml b/Configuration/Features/AdminTools/Services.yaml index 2ddee778a..612a523ed 100644 --- a/Configuration/Features/AdminTools/Services.yaml +++ b/Configuration/Features/AdminTools/Services.yaml @@ -7,52 +7,46 @@ services: In2code\In2publishCore\Features\AdminTools\: resource: '../../../Classes/Features/AdminTools/*' - In2code\In2publishCore\Features\AdminTools\Service\ToolsRegistry: - tags: - - name: 'event.listener' - identifier: 'in2publish_core-ToolsRegistry-BootCompletedEvent' - event: 'In2code\In2publishCore\Event\ExtTablesPostProcessingEvent' - method: 'processData' - In2code\In2publishCore\Features\AdminTools\Controller\LetterboxController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.flush_envelopes' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.flush_envelopes.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.flush_envelopes' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.flush_envelopes.description' actions: 'index,flushEnvelopes' In2code\In2publishCore\Features\AdminTools\Controller\RegistryController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.flush_registry' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.flush_registry.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.flush_registry' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.flush_registry.description' actions: 'index,flushRegistry' In2code\In2publishCore\Features\AdminTools\Controller\TcaController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.tca' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.tca.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.tca' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.tca.description' actions: 'index' In2code\In2publishCore\Features\AdminTools\Controller\ShowConfigurationController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.configuration' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.configuration.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.configuration' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.configuration.description' actions: 'index' In2code\In2publishCore\Features\AdminTools\Controller\TestController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.test' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.test.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.test' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.test.description' actions: 'index' + after: 'In2code\In2publishCore\Features\AdminTools\Controller\ToolsController' In2code\In2publishCore\Features\AdminTools\Controller\ToolsController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.index' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.index.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.index' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.index.description' actions: 'index' before: '*' diff --git a/Configuration/Features/CompareDatabaseTool/Services.yaml b/Configuration/Features/CompareDatabaseTool/Services.yaml index 3d5835e65..fdc030cec 100644 --- a/Configuration/Features/CompareDatabaseTool/Services.yaml +++ b/Configuration/Features/CompareDatabaseTool/Services.yaml @@ -13,6 +13,6 @@ services: In2code\In2publishCore\Features\CompareDatabaseTool\Controller\CompareDatabaseToolController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.compare' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.compare.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.compare' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.compare.description' actions: 'index,compare,transfer' diff --git a/Configuration/Features/LogsIntegration/Services.php b/Configuration/Features/LogsIntegration/Services.php deleted file mode 100644 index 004f924d6..000000000 --- a/Configuration/Features/LogsIntegration/Services.php +++ /dev/null @@ -1,32 +0,0 @@ -services(); - $defaults = $services->defaults(); - $defaults->autowire(true); - $defaults->autoconfigure(true); - $defaults->private(); - - $services->load( - 'In2code\\In2publishCore\\Features\\LogsIntegration\\', - __DIR__ . '/../../../Classes/Features/LogsIntegration/*', - ); - - $services->set(LogController::class) - ->tag( - 'in2publish_core.admin_tool', - [ - 'title' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.logs', - 'description' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.logs.description', - 'actions' => 'filter,delete,deleteAlike', - ], - ); - } -}; diff --git a/Configuration/Features/RecordInspector/Services.yaml b/Configuration/Features/RecordInspector/Services.yaml index 22851e0a7..26e47844b 100644 --- a/Configuration/Features/RecordInspector/Services.yaml +++ b/Configuration/Features/RecordInspector/Services.yaml @@ -10,6 +10,6 @@ services: In2code\In2publishCore\Features\RecordInspector\Controller\RecordInspectorController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.record_inspector' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.record_inspector.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.record_inspector' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.record_inspector.description' actions: 'index,inspect' diff --git a/Configuration/Features/ResolveFilesForIndices/Services.yaml b/Configuration/Features/ResolveFilesForIndices/Services.yaml new file mode 100644 index 000000000..a48794380 --- /dev/null +++ b/Configuration/Features/ResolveFilesForIndices/Services.yaml @@ -0,0 +1,19 @@ +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + In2code\In2publishCore\Features\ResolveFilesForIndices\: + resource: '../../../Classes/Features/ResolveFilesForIndices/*' + + In2code\In2publishCore\Features\ResolveFilesForIndices\EventListener\FileRecordListener: + tags: + - name: event.listener + identifier: 'in2publishcore-FileRecordListener-RecordWasCreated' + method: 'onRecordWasCreated' + event: In2code\In2publishCore\Event\RecordWasCreated + - name: event.listener + identifier: 'in2publishcore-FileRecordListener-RecordRelationsWereResolved' + method: 'onRecordRelationsWereResolved' + event: In2code\In2publishCore\Event\RecordRelationsWereResolved diff --git a/Configuration/Features/SystemInformationExport/Services.yaml b/Configuration/Features/SystemInformationExport/Services.yaml index bf3f6664d..30bb3d8db 100644 --- a/Configuration/Features/SystemInformationExport/Services.yaml +++ b/Configuration/Features/SystemInformationExport/Services.yaml @@ -13,6 +13,6 @@ services: In2code\In2publishCore\Features\SystemInformationExport\Controller\SystemInformationExportController: tags: - name: 'in2publish_core.admin_tool' - title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.system_info' - description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang.xlf:moduleselector.system_info.description' + title: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.system_info' + description: 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf:moduleselector.system_info.description' actions: 'sysInfoIndex,sysInfoShow,sysInfoDecode,sysInfoDownload,sysInfoUpload' diff --git a/Configuration/Icons.php b/Configuration/Icons.php new file mode 100644 index 000000000..b9d6edcbe --- /dev/null +++ b/Configuration/Icons.php @@ -0,0 +1,49 @@ + + * + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +use TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider; + +return [ + 'in2publish-core-overview-module' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:in2publish_core/Resources/Public/Icons/Overview.svg', + ], + 'in2publish-core-file-module' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:in2publish_core/Resources/Public/Icons/File.svg', + ], + 'in2publish-core-redirect-module' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:in2publish_core/Resources/Public/Icons/Redirect.svg', + ], + 'in2publish-core-tools-module' => [ + 'provider' => SvgIconProvider::class, + 'source' => 'EXT:in2publish_core/Resources/Public/Icons/Tools.svg', + ], +]; diff --git a/Configuration/JavaScriptModules.php b/Configuration/JavaScriptModules.php new file mode 100644 index 000000000..0dd6b7df0 --- /dev/null +++ b/Configuration/JavaScriptModules.php @@ -0,0 +1,14 @@ + [ + 'backend', + 'core', + ], + 'tags' => [ + 'backend.contextmenu', + ], + 'imports' => [ + '@in2code/in2publish_core/' => 'EXT:in2publish_core/Resources/Public/JavaScript/', + ], +]; diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php index f339213df..61af39eec 100644 --- a/Configuration/RequestMiddlewares.php +++ b/Configuration/RequestMiddlewares.php @@ -3,12 +3,17 @@ declare(strict_types=1); use In2code\In2publishCore\Features\MetricsAndDebug\Middleware\MetricsAndDebugMiddleware; +use In2code\In2publishCore\Middleware\InjectLoadingOverlayMiddleware; return [ 'backend' => [ - 'in2code/in2publish/debugging' => [ + 'in2code/in2publish_core/debugging' => [ 'target' => MetricsAndDebugMiddleware::class, 'after' => 'typo3/cms-core/response-propagation', ], + 'in2code/in2publish_core/inject-loading-overlay' => [ + 'target' => InjectLoadingOverlayMiddleware::class, + 'after' => 'typo3/cms-core/response-propagation', + ], ], ]; diff --git a/Configuration/Services.php b/Configuration/Services.php index 9081a2f0f..796b9bba9 100644 --- a/Configuration/Services.php +++ b/Configuration/Services.php @@ -3,13 +3,19 @@ declare(strict_types=1); use In2code\In2publishCore\Component\Core\Record\Factory\DatabaseRecordFactory; +use In2code\In2publishCore\Service\Configuration\LegacyPageTypeService; +use In2code\In2publishCore\Service\Configuration\PageTypeRegistryService; +use In2code\In2publishCore\Service\Configuration\PageTypeService; use In2code\In2publishCore\Service\Context\ContextService; use In2code\In2publishCore\Testing\Tests\TestCaseInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use TYPO3\CMS\Core\DependencyInjection\PublicServicePass; use TYPO3\CMS\Core\Utility\GeneralUtility; +use const In2code\In2publishCore\TYPO3_V11; + return static function (ContainerConfigurator $configurator, ContainerBuilder $builder): void { $configurator->import('Component/*/Services.php'); $configurator->import('Features/*/Services.php'); @@ -20,10 +26,27 @@ $configurator->import('ForeignServices.php'); } - $builder->registerForAutoconfiguration(TestCaseInterface::class)->addTag('in2publish_core.testing.test'); - $builder->registerForAutoconfiguration(DatabaseRecordFactory::class)->addTag( - 'in2publish_core.factory.database_record', - ); + if ($builder->hasDefinition(PageTypeService::class)) { + $pageTypeServiceDefinition = $builder->hasDefinition(PageTypeService::class); + } else { + $pageTypeServiceDefinition = new Definition(PageTypeService::class); + } + $pageTypeServiceDefinition->setAutoconfigured(true); + $pageTypeServiceDefinition->setAutowired(true); + $pageTypeServiceDefinition->setShared(true); + $pageTypeServiceDefinition->setPublic(true); + + if (TYPO3_V11) { + $pageTypeServiceDefinition->setClass(LegacyPageTypeService::class); + } else { + $pageTypeServiceDefinition->setClass(PageTypeRegistryService::class); + } + $builder->setDefinition(PageTypeService::class, $pageTypeServiceDefinition); + + $builder->registerForAutoconfiguration(TestCaseInterface::class) + ->addTag('in2publish_core.testing.test'); + $builder->registerForAutoconfiguration(DatabaseRecordFactory::class) + ->addTag('in2publish_core.factory.database_record',); $builder->addCompilerPass(new PublicServicePass('in2publish_core.testing.test')); }; diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml index cf54abfc4..ff9202c33 100644 --- a/Configuration/Services.yaml +++ b/Configuration/Services.yaml @@ -21,10 +21,6 @@ services: - '../Classes/Component/*' - '../Classes/Features/*' - TYPO3\CMS\Backend\Middleware\BackendRouteInitialization: - alias: In2code\In2publishCore\Middleware\BackendRouteInitialization - public: true - # Services In2code.In2publishCore.Database.Local: @@ -60,7 +56,6 @@ services: In2code\In2publishCore\Domain\Service\ForeignSiteFinder: arguments: [ '@cache.in2publish_core' ] - In2code\In2publishCore\Testing\Tests\SshConnection\SshFunctionAvailabilityTest: tags: - name: 'in2publish_core.adapter.ssh.remote_adapter_test' @@ -74,3 +69,6 @@ services: In2code\In2publishCore\Testing\Tests\SshConnection\SftpRequirementsTest: tags: - name: 'in2publish_core.adapter.ssh.transmission_adapter_test' + + In2code\In2publishCore\Service\ReplaceMarkersService: + public: true diff --git a/Configuration/Yaml/ForeignConfiguration.yaml.example b/Configuration/Yaml/ForeignConfiguration.yaml.example index 4b9e1fbe6..e0f770c44 100755 --- a/Configuration/Yaml/ForeignConfiguration.yaml.example +++ b/Configuration/Yaml/ForeignConfiguration.yaml.example @@ -2,8 +2,6 @@ # Example Configuration for in2publish on foreign # ---- - # Backup configuration backup: diff --git a/Configuration/Yaml/LocalConfiguration.yaml.example b/Configuration/Yaml/LocalConfiguration.yaml.example index 18613c035..e3a5c2aa9 100755 --- a/Configuration/Yaml/LocalConfiguration.yaml.example +++ b/Configuration/Yaml/LocalConfiguration.yaml.example @@ -4,8 +4,6 @@ # Fields annotated with "@user" can be overridden using PageTS or UserTS # ---- - # PHP & Database settings on foreign server (for SSH access see "sshConnection") foreign: @@ -61,9 +59,9 @@ excludeRelatedTables: - tx_in2code_in2publish_task - tx_in2code_rpc_data - tx_in2code_rpc_request - - tx_in2publishcore_filepublisher_task - tx_in2publishcore_log - tx_in2publishcore_running_request + - tx_in2publishcore_filepublisher_instruction # Ignore these fields for difference view (Publish Overview, Record Comparison). diff --git a/Documentation/Admins/Changelog/60149-Change-ReplaceSpycWithSymfonyYaml.md b/Documentation/Admins/Changelog/60149-Change-ReplaceSpycWithSymfonyYaml.md new file mode 100644 index 000000000..397149043 --- /dev/null +++ b/Documentation/Admins/Changelog/60149-Change-ReplaceSpycWithSymfonyYaml.md @@ -0,0 +1,62 @@ +# 60149 Change Replace Spyc with symfony/yaml + +Issue https://projekte.in2code.de/issues/60149 + +## Description + +in2publish_core uses a YAML file for configuration since it was first develop in 2015. YAML has not been established as +a configuration language back then, so it was necessary to ship a YAML parser with the extension. Time flew by and some +TYPO3 versions later, TYPO3 uses YAML for site configuration, the Form Framework, CKE Editor configuration, and more. +We have not required any YAML parser via composer, because we also supported TYPO3 in non-composer installations. + +So eventually, TYPO3 requires a YAML parser, too. We can rely on the implementation shipped with TYPO3 to reduce the +code we have to maintain, which we did in in2publish_core v12.3.0. We are now using the YAML parser shipped with TYPO3. + +## Impact + +symfony/yaml does not support multiple documents and a specific list type. You will have to change your configuration or +else you will experience exceptions and probably unwanted side effects. + +## Affected Installations + +Possibly all, probably only those who do not have the Content Publisher Enterprise Edition installed. + +## Migration + +1. Remove the "end of directives marker" `---` from any Content Publisher configuration YAML file. +2. Replace block collections with arrays (see Example #1 Replace block collections with arrays) +2. Quote all asterisks (see Example #2 Quote asterisk) + +## Examples + +### #1 Replace block collections with arrays +Before: +```yaml + permission: + definition: + 2: + 3 + 4 + 5 + 6 + 7 + 8 +``` +After +```yaml + permission: + definition: + 2: [3,4,5,6,7,8] +``` + +### #2 Quote asterisk +Before: +```yaml + workflowTreeColors: + groups: * +``` +After +```yaml + workflowTreeColors: + groups: '*' +``` diff --git a/Documentation/Admins/Features/README.md b/Documentation/Admins/Features/README.md index d3724827e..423764415 100644 --- a/Documentation/Admins/Features/README.md +++ b/Documentation/Admins/Features/README.md @@ -1,23 +1,63 @@ -# List of Features (WIP) - -* AdminTools -* CacheInvalidation -* CompareDatabaseTool -* ContextMenuPublishEntry -* Services.yaml -* FileEdgeCacheInvalidator -* FullTablePublishing -* LogsIntegration -* MetricsAndDebug -* NewsSupport -* PreventParallelPublishing -* PublishSorting -* RecordBreadcrumbs -* RecordInspector -* [**Redirects Support**](RedirectsSupport.md) Publish redirects. -* RefIndexUpdate -* [**TreatRemovedAndDeletedAsDifference**](TreatRemovedAndDeletedAsDifference.md) Show records that have been removed on - one side and deleted on the other side in the OverviewModule. -* SysLogPublisher -* SystemInformationExport -* WarningOnForeign +# List of Features EXT:in2publish_core + +The `in2publish_core` extension offers a variety of configurable features, each residing in its own subfolder under `Configuration/Features`. Here's an overview of these features: + +## AdminTools +- Provides tools for administrators to ensure correct functioning of the content publisher and to analyse the system setup. + +## CacheInvalidation +- Manages cache invalidation strategies for efficient content delivery and performance optimization. + +## CompareDatabaseTool +- Offers a tool for comparing database states between foreign and local development, aiding in synchronization and troubleshooting. + +## ContextMenuPublishEntry +- Enhances the TYPO3 context menu with publishing options. + +## FileEdgeCacheInvalidator +- Handles invalidation of edge caches for files, ensuring clearing of caches on foreign systems after publishing + +## FullTablePublishing +- Allows for the publishing of entire database tables, useful in scenarios requiring bulk data transfers without related records. + +## HideRecordsDeletedDifferently +##[**HideRecordsDeletedDifferently**](HideRecordsDeletedDifferently.md) +- Offers the possibility to hide records from the Publish Overview Module if they are deleted on one side and removed from the database on the other, e.g. if using the Recycler. +- Configurable feature. Default: enabled. + +## MetricsAndDebug +- Offers metrics collection and debugging tools, helpful for performance analysis and issue resolution. + +## NewsSupport +- Specific support for handling news records and related content. + +## PreventParallelPublishing +- Prevents parallel publishing operations, ensuring data integrity and avoiding conflicts during content deployment. + +## PublishSorting +- Adds sorting capabilities to the publishing process, allowing for prioritization and organized content rollout. + +## RecordBreadcrumbs +- Enhances the backend interface with breadcrumbs for records, improving navigation and context awareness. + +## RecordInspector +- Provides an inspection tool for content records, aiding in content review and data analysis. + +## [**Redirects Support**](RedirectsSupport.md) +- Integrates support for manual publishing of redirects. +- Configurable feature. Default: enabled. + +## RefIndexUpdate +- Handles the updating of TYPO3's reference index, ensuring accurate content linking and reference management. + +## ResolveFilesForIndices +- Resolves file paths for indexing, aiding in efficient content retrieval and search functionality. + +## SysLogPublisher +- Integrates with TYPO3's system log for publishing-related logging, enhancing monitoring and auditing capabilities. + +## SystemInformationExport +- Enables the export of system information, useful for diagnostics and system overviews. + +## WarningOnForeign +- Implements warnings for operations performed on foreign (non-local) systems, enhancing operational safety. diff --git a/Documentation/Developers/Events/ExtTablesPostProcessingEvent.md b/Documentation/Developers/Events/ExtTablesPostProcessingEvent.md deleted file mode 100644 index 66bc552a1..000000000 --- a/Documentation/Developers/Events/ExtTablesPostProcessingEvent.md +++ /dev/null @@ -1,16 +0,0 @@ -# ExtTablesPostProcessingEvent - -## When - -In the `BackendRouteInitialization` middleware - -## What - -nothing - -## Possibilities - -This event replaces the "ExtTablesPostProcessingHook" which was registered using -`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['extTablesInclusion-PostProcessing']`. -Since TYPO3 does not provide an equivalent replacement, we XCLASS the `BackendRouteInitialization` to introduce this -event ourselves. Have a look at the event class for more information. diff --git a/Documentation/KnownIssues.md b/Documentation/KnownIssues.md index 209ebdb0c..3bafe073b 100644 --- a/Documentation/KnownIssues.md +++ b/Documentation/KnownIssues.md @@ -1,5 +1,10 @@ # Known Issues +## File Preview URLs in Publish Files Module are not shown when using EXT:fal_securedownload + +File Preview URLs are not rendered in the Publish Files Module, if EXT:fal_securedownload is installed. +However, the functionality of EXT:fal_securedownload and file publishing is not affected. + ## File Preview URLs in Publish Files Module broken for non-public file storages File Preview URLs rendered for files on Foreign are broken, if the file storage is not marked as public. diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf index 9667a0c67..e21be6339 100755 --- a/Resources/Private/Language/de.locallang.xlf +++ b/Resources/Private/Language/de.locallang.xlf @@ -1,812 +1,502 @@ - - - + + +
- + Publish Overview - Publisher Übersicht + Publisher Übersicht - - + TYPO3 Content Publisher - TYPO3 Content Publisher + TYPO3 Content Publisher - + publish pages and records overview - Übersicht Seiten und Datensätze veröffentlichen + Übersicht Seiten und Datensätze veröffentlichen - + Publish files - Dateien veröffentlichen - - - administration tools - Tools für Administratoren + Dateien veröffentlichen - + Publish redirects - Redirects veröffentlichen + Redirects veröffentlichen - - + Depth - Tiefe + Tiefe - + %d level - %d Ebene + %d Ebene - + %d levels - %d Ebenen + %d Ebenen - - + Stage System - Redaktionssystem + Redaktionssystem - + Production System - Produktivsystem + Produktivsystem - + The selected record has been published successfully. (Duration: %s) - Der ausgewählte Datensatz wurde erfolgreich veröffentlicht. (Dauer: %s) + Der ausgewählte Datensatz wurde erfolgreich veröffentlicht. (Dauer: %s) - + One or more errors occurred during publishing. Please check the logs. (Duration: %s) - Ein oder mehrere Fehler traten während des Publizierungsvorgangs auf. Bitte prüfen Sie die Fehlerlogs. (Dauer: %s) + Ein oder mehrere Fehler traten während des Publizierungsvorgangs auf. Bitte prüfen Sie die Fehlerlogs. (Dauer: %s) - + One or more errors occurred during relation resolving. Please check the logs. - Ein oder mehrere Fehler traten auf während die Verknüpfungen aufgelöst wurden. Bitte prüfen Sie die Fehlerlogs. + Ein oder mehrere Fehler traten auf während die Verknüpfungen aufgelöst wurden. Bitte prüfen Sie die Fehlerlogs. - + The selected record has been published successfully. - Der ausgewählte Datensatz wurde erfolgreich veröffentlicht. + Der ausgewählte Datensatz wurde erfolgreich veröffentlicht. - + The selected folder has been created successfully. - Der ausgewählte Ordner wurde erfolgreich angelegt. + Der ausgewählte Ordner wurde erfolgreich angelegt. - + The selected folder could not be created. - Der ausgewählte Ordner konnte nicht angelegt werden. + Der ausgewählte Ordner konnte nicht angelegt werden. - + The selected folder has been removed successfully. - Der ausgewählte Ordner wurde erfolgreich gelöscht. + Der ausgewählte Ordner wurde erfolgreich gelöscht. - + The selected folder could not be removed. - Der ausgewählte Ordner konnte nicht gelöscht werden. + Der ausgewählte Ordner konnte nicht gelöscht werden. - - in2publish failed to retrieve information about the folder %s or any of it's sub folders. Please verify that is in2publish is working properly by running the tests. - in2publish konnte keine Informationen über den Ordner %s oder einen seiner Unterordner auslesen. Bitte überprüfen Sie ob in2publish korrekt eingerichtet ist, indem Sie die Tests durchführen. + + in2publish failed to retrieve information about the folder %s or any of it's sub folders. Please verify that is in2publish is working properly by running the tests. + in2publish konnte keine Informationen über den Ordner %s oder einen seiner Unterordner auslesen. Bitte überprüfen Sie ob in2publish korrekt eingerichtet ist, indem Sie die Tests durchführen. - - Publish page "%s" and its subpages. - Veröffentlichen der Seite "%s" und ihrer Unterseiten + + Publish page "%s" and its subpages. + Veröffentlichen der Seite "%s" und ihrer Unterseiten - - Do you really want to publish all pages? - Möchten Sie wirklich alle Seiten veröffentlichen? - - + Do you really want to publish this page? - Möchten Sie diese Seite wirklich veröffentlichen? + Möchten Sie diese Seite wirklich veröffentlichen? - + Publish Publizieren - + + Not publishable + Nicht Veröffentlichbar + + Publish this page - Diese Seite publizieren + Diese Seite publizieren - + This page is currently being published - Diese Seite wird gerade veröffentlicht + Diese Seite wird gerade veröffentlicht - + This file is currently being published - Diese Datei wird gerade veröffentlicht + Diese Datei wird gerade veröffentlicht - - Publish all files within folder "%s". - Veröffentlichen aller Dateien innerhalb von "%s" + + Publish all files within folder "%s". + Veröffentlichen aller Dateien innerhalb von "%s" - + Do you really want to publish all files? - Möchten Sie wirklich alle Dateien veröffentlichen? + Möchten Sie wirklich alle Dateien veröffentlichen? - + Do you really want to publish this file? - Möchten Sie diese Datei wirklich veröffentlichen? + Möchten Sie diese Datei wirklich veröffentlichen? - + Preview - Vorschau + Vorschau - + Compare - Vergleichsansicht + Vergleichsansicht - + History - Historie + Historie - + File preview - Dateivorschau + Dateivorschau - + Edit - Bearbeiten + Bearbeiten - + %s properties - Eigenschaften von %s + Eigenschaften von %s - + Page properties - Seiteneigenschaften + Seiteneigenschaften - + Record history - Datensatz Historie + Datensatz Historie - + Edit record - Datensatz bearbeiten + Datensatz bearbeiten - + Related Records - Verknüpfte Datensätze + Verknüpfte Datensätze - + Record State - Datensatz Status - - - - Introduction - Übersicht - - - This tool just lists all other tools. - Dieses Tool listet nur alle anderen Tools auf - - - Show configuration - Konfigurationsübersicht - - - Display the local configuration of in2publish from LocalConfiguration.yaml - Zeige die aktuelle Konfiguration von in2publish von der LocalConfiguration.yaml Datei - - - Show logs - Logs anzeigen - - - Show all logs of the Content Publisher with plenty filter options - Zeigt die Logeinträge des Content Publishers mit vielen Filteroptionen an - - - Tests - Tests + Datensatz Status - - Run some function test of in2publish - Teste in2publish auf Funktionsfähigkeit - - - Inspect TCA - TCA inspection - - - See which parts of the TCA are processed, and the reason why others are not. - Zeige die Teile des TCA die verwendet werden und alle anderen mit der Begründung warum sie nicht verwendet werden. - - - Remove superfluous envelopes - Überflüssige Envelopes löschen - - - Deletes all envelopes (remote procedure call database entries) which were already used (envelopes are one-way records). - Löscht alle Envelopes ("Remote Procedure Call"-Datenbankeinträge) die bereits genutzt wurden (Diese sind Einweg-Einträge) - - - Remove all Registry entries - Registereinträge löschen - - - Removes all registry entries which store information about the local and foreign environment. You must run the test after flushing these. - Löscht alle Registereinträge, welche Informationen über das lokale und entfernte System vorhalten. Danach müssen die Tests erneut ausgeführt werden. - - - System information - Systeminformationen - - - Use this module to collect information which will be required or useful to solve a bug or problem. - Anzeige von Informationen welche erforderlich oder nützlich sind Probleme zu identifizieren und sie zu beheben. - - - Compare Databases - Datenbankvergleich - - - The compare tool scans the whole database for differences. This tool is ought to help administrators clean up UID conflicts. - Zeigt unterschiede zwischen den Datenbanken an. Dieses Tools soll Administratoren dabei helfen UID-Konflikte zu lösen. - - - Inspect a record tree - RecordTree Untersuchen - - - You can query for a record tree by classification and identifier and inspect it with all of its properties and children. - Hier kann ein RecordTree mittels Klassifikation und Identifikation aufgebaut und untersucht werden.. - - - - Show the system info - Systeminformationen anzeigen - - - Download the system info - Systeminformationen herunterladen - - - Decode the system info JSON - JSON dekodieren - - - JSON string - JSON Text - - - Decode! - Dekodieren - - - Error during json decoding - Fehler während des dekodierens - - - JSON decode error #%d: %s - JSON dekodierfehler #%d: %s - - - SysInfo JSON file - Systeminformationen JSON Datei - - - Upload! - Hochladen! - - - + Filter by: Filtern nach: - - + Filename Dateiname - - + Status Status - - + Record unchanged - Unverändert + Unverändert - + Record changed - Datensatz geändert + Datensatz geändert - + New record - Neuer Datensatz + Neuer Datensatz - + Record deleted - Gelöschter Datensatz + Gelöschter Datensatz - + Record moved - Datensatz verschoben + Datensatz verschoben - - - + Unchanged - Unverändert + Unverändert - + Changed - Verändert + Verändert - + New - Neu + Neu - + Deleted - Gelöscht + Gelöscht - + Moved - Verschoben + Verschoben - + Moved/Changed - Verschoben/Verändert - - - - Configuration - Konfiguration - - - Global Configuration - Globale Konfiguration - - - Personal Config - Persönliche Konfiguration - - - Config Container Dump - Config Container Konfiguration - - - Enter Page ID for page specific configuration - Seiten-ID für Seitenspezifische konfiguration angeben - - - Overview - Übersicht - - - Filter - Filter - - - TCA inspection - TCA Inspizieren - - - Compatible TCA - Kompatibles TCA - - - Incompatible TCA - Inkompatibles TCA - - - Controls - Controls - - - Navigate to incompatible TCA - Zu inkompatiblem TCA springen - - - Navigate to compatible TCA - Zu kompatiblem TCA springen - - - Navigate to controls - Zu controls springen - - - Flushed all related registry entries. - Alle zugehörigen Registereinträge wurden gelöscht. - - - Flushed all superfluous envelopes. - Alle überflüssigen Envelopes wurden gelöscht. - - - Flush superfluous envelopes. - Überflüssigen Envelopes löschen. - - - There are no envelopes to flush. - Es gibt keine Envelopes zum löschen. - - - Clear registry entries. - Registry-Einträge löschen. - - - The Content Publisher stores various information in the registry, like the outcome of the last test run. You can force the removal of these entries here. - Der Content Publisher speichert Informationen wie das Ergebnis des letzten Testlaufs in der Registry. Sie können die Löschung dieser Einträge hier erzwingen. + Verschoben/Verändert - + In2publish might not work as expected. - In2publish könnte vom erwarteten Verhalten abweichen. + In2publish könnte vom erwarteten Verhalten abweichen. - + Unknown test state. Please execute the tests in the Publish Tools Module. - Unbekannter Teststatus. Bitte führen sie die Tests im Publish Tools Modul aus. + Unbekannter Teststatus. Bitte führen sie die Tests im Publish Tools Modul aus. - + Some In2publish tests are failing. - Einige Tests von in2publish schlagen fehl. + Einige Tests von in2publish schlagen fehl. - + Your TYPO3 installation changed. - Ihre TYPO3 Installation hat sich verändert. + Ihre TYPO3 Installation hat sich verändert. - + The In2publish configuration has changed. - Die In2publish Konfiguration wurde verändert. + Die In2publish Konfiguration wurde verändert. - - + Vertical - Vertikal + Vertikal - + Horizontal - Horizontal + Horizontal - - - Successful published. - Erfolgreich publiziert. + + Successfully published. + Erfolgreich publiziert. - + Something went wrong. - Ein Fehler ist aufgetreten. + Ein Fehler ist aufgetreten. - + The selected folder %s has been published to the foreign system. - Das Verzeichnis %s wurde erfolgreich publiziert. + Das Verzeichnis %s wurde erfolgreich publiziert. - + The selected folder %s could not be published. - Das Verzeichnis %s konnte nicht publiziert werden. + Das Verzeichnis %s konnte nicht publiziert werden. - + The selected file %s has been published to the foreign system. - Die Datei %s wurde erfolgreich publiziert. + Die Datei %s wurde erfolgreich publiziert. - + The selected file %s could not be fully published. - Die Datei %s konnte nicht vollständig publiziert werden. + Die Datei %s konnte nicht vollständig publiziert werden. - - + One or more additional tasks which run after the successful publishing process failed. Caches and other non critical aspects might not behave as expected. The logs might contain more information. - Eine oder mehrere Aufgaben, die nach dem erfolgreichen publizieren ausgeführt werden sind fehlgeschlagen. Caches und andere nicht kritische Aspekte könnten sich nicht wie erwartet verhalten. Die Logs können weitere Informationen enthalten. + Eine oder mehrere Aufgaben, die nach dem erfolgreichen publizieren ausgeführt werden sind fehlgeschlagen. Caches und andere nicht kritische Aspekte könnten sich nicht wie erwartet verhalten. Die Logs können weitere Informationen enthalten. - - + Secure, authenticated and encrypted command execution - Sichere, authentifizierte und verschlüsselte Befehlsausführung + Sichere, authentifizierte und verschlüsselte Befehlsausführung - + Secure, reliable and encrypted asset transmission - Sichere, zuverlässige und Verschlüsselte Dateiübertragung + Sichere, zuverlässige und Verschlüsselte Dateiübertragung - - - https://typo3.slack.com/archives/C2ULY79MZ]]> - https://typo3.slack.com/archives/C2ULY79MZ]]> - - - https://github.com/in2code-de/in2publish_core/tree/master/Documentation]]> - https://github.com/in2code-de/in2publish_core/tree/master/Documentation]]> - - - + You are not allowed to publish this page. - Sie dürfen diese Seite nicht veröffentlichen. + Sie dürfen diese Seite nicht veröffentlichen. - + No page parameter was transferred. - Es wurde keine Seiten-ID zum veröffentlichen übertragen. + Es wurde keine Seiten-ID zum veröffentlichen übertragen. - - The page "%s" has been published. - Die Seite "%s" wurde veröffentlicht. + + The page "%s" has been published. + Die Seite "%s" wurde veröffentlicht. - - Error during publishing of page "%s". Please check your logs. - Beim veröffentlichen der Seite "%s" ist ein Fehler aufgetreten. Weitere Informationen finden Sie in den Logs. + + Error during publishing of page "%s". Please check your logs. + Beim veröffentlichen der Seite "%s" ist ein Fehler aufgetreten. Weitere Informationen finden Sie in den Logs. - + This record is not yet publishable. - Diese Seite ist noch nicht veröffentlichbar. + Diese Seite ist noch nicht veröffentlichbar. - - + Associated page - Verknüpfte Seite + Verknüpfte Seite - + Associated foreign Site - Verknüpfte Site des entfernten Systems + Verknüpfte Site des entfernten Systems - + Use site from associated page - Site der verknüpften Seite benutzen + Site der verknüpften Seite benutzen - + Page/Site association - Seite/Site Verknüpfung + Seite/Site Verknüpfung - - + Save and publish - Speichern und veröffentlichen + Speichern und veröffentlichen - + Publish all selected - Gewählte veröffentlichen + Gewählte veröffentlichen - + Status icons explained - Status Legende + Status Legende - + The redirect requires an associated page or site to be published - Dieser Redirect benötigt eine verknüpfte Seite oder Site um veröffentlicht werden zu können + Dieser Redirect benötigt eine verknüpfte Seite oder Site um veröffentlicht werden zu können - + The associated page is not published - Die verknüpfte Seite ist nicht publiziert. + Die verknüpfte Seite ist nicht publiziert. - + This redirect has unpublished changes. Hover over the icon to get more information - Der Redirect hat unveröffentlichte Änderungen. Den Mauszeiger auf das Icon bewegen um mehr zu erfahren + Der Redirect hat unveröffentlichte Änderungen. Den Mauszeiger auf das Icon bewegen um mehr zu erfahren - + This redirect is published - Dieser Redirect ist publiziert + Dieser Redirect ist publiziert - + Action icons explained - Aktionen Legende + Aktionen Legende - + This redirect can be published. Click to publish. - Dieser Redorect kann publiziert werden. Klicken um zu publizieren. + Dieser Redorect kann publiziert werden. Klicken um zu publizieren. - + This redirect can be published with a manual site association. Click to assign a Site and publish. - Dieser Redirect kann mit einer manuellen Site-Verknüpfung veröffentlicht werden. Klicken um eine Site zu verknüpfen und zu publizieren. + Dieser Redirect kann mit einer manuellen Site-Verknüpfung veröffentlicht werden. Klicken um eine Site zu verknüpfen und zu publizieren. - - + Property - Eigenschaft + Eigenschaft - + Old value - Alt + Alt - + New value - Neu + Neu - + ID - ID + ID - + Domain - Domain + Domain - + Source - Quelle + Quelle - + Target - Ziel + Ziel - + Status - Status + Status - + Actions - Aktionen + Aktionen - - + Missing associated page or site - Fehlende Seite oder Seitenkonfiguration + Fehlende Seite oder Seitenkonfiguration - - The associated page [%d] "%s" is not published - Die verknüpfte Seite [%d] "%s" ist nicht publiziert + + The associated page [%d] "%s" is not published + Die verknüpfte Seite [%d] "%s" ist nicht publiziert - + This redirect is published - Dieser Redirect ist publiziert + Dieser Redirect ist publiziert - - + Ready for publishing - Bereit zur Veröffentlichung + Bereit zur Veröffentlichung - + Missing page or site - Fehlende Seite oder Seitenkonfiguration + Fehlende Seite oder Seitenkonfiguration - + Requires page publishing - Fehlende Veröffentlichung der Seite + Fehlende Veröffentlichung der Seite - + Published - Veröffentlicht + Veröffentlicht - - + Filter - Filtern + Filtern - + Association - Verknüpfung + Verknüpfung - + Association present - Verknüpft + Verknüpft - + Association missing - Verknüpfung fehlend + Verknüpfung fehlend - - + Refresh page - Seite neu laden + Seite neu laden - - - New - Neu - - - Deleted - Gelöscht - - - Differences - Unterschiede - - - Generally - Generell - - - The table is empty on Local - Die Tabelle is auf Local leer. - - - The table is empty on Foreign - Die Tabelle is auf Foreign leer. - - - Transfer these changes to the foreign system - Übertrage diese Änderungen auf das entfernte System - - - No differences were found in the data of the selected tables. - Es wurden keine Unterschiede in den Daten der gewählten Tabellen entdeckt. - - - Select the tables to compare - Tabellen für den Vergleich auswählen - - - Tables - Tabellen - - - Compare - Vergleichen - - - This is not publishing - Dies ist kein Publishing - - - A transfer is not a publishing. Only the exact record that is selected is transferred. No relations to other records are resolved and no MM-records are displayed nor transferred. You should always prefer normal publishing to this feature. Use this feature only in an emergency and only if you are aware of all the consequences. - Ein Transfer ist kein Publishing. Es wird dabei nur exakt der Datensatz übertragen, der ausgewählt wird. Es werden keine Verknüpfungen zu anderen Datensätzen aufgelöst und es werden auch keine MM-Datensätze angezeigt oder übertragen. Sie sollten ein normales Publishing immer diesem Feature vorziehen. Nutzen Sie dieses Feature nur im Notfall und nur, wenn Sie sich aller Konsequenzen bewusst sind. - - - Record label - Datensatztitle - - - Actions - Aktionen - - - Differences - Unterschiede - - - Error - Fehler - - - Success - Erfolg - - - The record which should be transferred does not exist anymore. - Der Datensatz der übertragen werden sollte existiert nicht mehr. - - - The record which should be transferred should not exists on Foreign but it does. It was probably published in the meantime. - Der Datensatz der übertragen werden sollte sollte nicht auf Foreign existieren, jedoch ist er vorhanden. Möglicherweise wurde er in der Zwischenzeit veröffentlicht. + + Admin tools + Administrationswerkzeuge - - The record which should be transferred should not exists on Local but it does. It was probably created in the meantime. - Der Datensatz der übertragen werden sollte sollte nicht auf Local existieren, jedoch ist er vorhanden. Möglicherweise wurde er in der Zwischenzeit erstellt. + + "%s" requires that the record "%s" (which is the default language version) is published first. + "%s" erfordert, dass der Datensatz "%s" (das ist die Standardsprachversion) zuerst veröffentlicht wird. - - The record which should be transferred should exists on both Local and Foreign but it does not. Please reload the comparison view and try again. - Der Datensatz der übertragen werden sollte sollte auf Local und Foreign existieren, er fehlt jedoch auf mindestens einem System. Bitte versuchen Sie es noch einmal, nachdem Sie die Vergleichsansicht neu geladen haben. + + The record "%s" cannot be published until all visibility settings (%s) of the default language "%s" have been published. + Der Datensatz "%s" kann nicht veröffentlicht werden, bevor nicht alle Sichtbarkeitseinstellung (%s) der Standardsprache "%s" veröffentlicht wurden. - - The record %s[%d] was deleted from foreign. - Der Datensatz %s[%d] wurde auf Foreign gelöscht. + + "%s" requires that the page "%s" is published first. + "%s" erfordert, dass die Seite "%s" zuerst veröffentlicht wird. - - The record %s[%d] was created on foreign. - Der Datensatz %s[%d] wurde auf Foreign erstellt. + + "%s" requires that the page "%s" has all of its properties published (%s) which determine if the record is visible. + "%s" setzt voraus, dass die Seite "%s" alle seine Eigenschaften veröffentlicht hat (%s), die bestimmen, ob der Datensatz sichtbar ist. - - The record %s[%d] was updated on foreign. - Der Datensatz %s[%d] wurde auf Foreign aktualisiert. + + The record with classification "%s" and properties "%s" does not exist. + Der Datensatz mit der klassifizierung "%s" und Eigenschaften "%s" existiert nicht. - - - Admin tools - Administrationswerkzeuge + + The record "%s" is a target of the shortcut record "%s". The target must be published before the shortcut record can be published. + Der Datensatz "%s" ist ein Ziel des Verknüpfungsdatensatzes "%s". Das Ziel muss veröffentlicht werden, bevor der Verknüpfungsdatensatz veröffentlicht werden kann. - - - "%s" requires that the record "%s" (which is the default language version) is published first. - "%s" erfordert, dass der Datensatz "%s" (das ist die Standardsprachversion) zuerst veröffentlicht wird. + + This page has unmet dependencies. Since you cannot access the dependencies, it is possible to publish this page anyway. However, there is no guarantee that the page will look exactly the same as on Local. + You can see the dependencies in the details of the page. + Diese Seite hat unerfüllte Abhängigkeiten. Da Sie auf die Abhängigkeiten nicht zugreifen können ist es möglich, diese Seite trotzdem zu veröffentlichen. Allerdings ohne Garantie, dass die Seite exakt so aussieht wie auf Local. + Sie können die Abhängigkeiten in den Details der Seite einsehen. - - The record "%s" cannot be published until all visibility settings (%s) of the default language "%s" have been published. - Der Datensatz "%s" kann nicht veröffentlicht werden, bevor nicht alle Sichtbarkeitseinstellung (%s) der Standardsprache "%s" veröffentlicht wurden. + + Publish + Veröffentlichen - - "%s" requires that the page "%s" is published first. - "%s" erfordert, dass die Seite "%s" zuerst veröffentlicht wird. + + Abort + Abbrechen - - "%s" requires that the page "%s" has all of its properties published (%s) which determine if the record is visible. - "%s" setzt voraus, dass die Seite "%s" alle seine Eigenschaften veröffentlicht hat (%s), die bestimmen, ob der Datensatz sichtbar ist. + + Confirm publish + Veröffentlichung bestätigen - - The record with classification "%s" and properties "%s" does not exist. - Der Datensatz mit der klassifizierung "%s" und Eigenschaften "%s" existiert nicht. + + Are you sure you want to publish the file %s? This cannot be reversed. + Möchten Sie die Datei %s wirklich veröffentlichen? Dies kann nicht rückgängig gemacht werden. - - The record "%s" is a target of the shortcut record "%s". The target must be published before the shortcut record can be published. - Der Datensatz "%s" ist ein Ziel des Verknüpfungsdatensatzes "%s". Das Ziel muss veröffentlicht werden, bevor der Verknüpfungsdatensatz veröffentlicht werden kann. + + Are you sure you want to publish the folder %s? This cannot be reversed. + Möchten Sie den Ordner %s wirklich veröffentlichen? Dies kann nicht rückgängig gemacht werden. diff --git a/Resources/Private/Language/de.locallang_js.xlf b/Resources/Private/Language/de.locallang_js.xlf index 6a846ba8f..154ce3ff9 100755 --- a/Resources/Private/Language/de.locallang_js.xlf +++ b/Resources/Private/Language/de.locallang_js.xlf @@ -11,24 +11,6 @@ Abort Abbrechen - - Confirm publish - Veröffentlichung bestätigen - - - Are you sure you want to publish the file $name$? This cannot be reversed. - Möchten Sie die Datei $1 wirklich veröffentlichen? Dies kann nicht rückgängig gemacht werden. - - - Are you sure you want to publish the folder $name$? This cannot be reversed. - Möchten Sie den Ordner $name$ wirklich veröffentlichen? Dies kann nicht rückgängig gemacht werden. - - - This page has unmet dependencies. Since you cannot access the dependencies, it is possible to publish this page anyway. However, there is no guarantee that the page will look exactly the same as on Local. - You can see the dependencies in the details of the page. - Diese Seite hat unerfüllte Abhängigkeiten. Da Sie auf die Abhängigkeiten nicht zugreifen können ist es möglich, diese Seite trotzdem zu veröffentlichen. Allerdings ohne Garantie, dass die Seite exakt so aussieht wie auf Local. - Sie können die Abhängigkeiten in den Details der Seite einsehen. - diff --git a/Resources/Private/Language/de.locallang_mod4.xlf b/Resources/Private/Language/de.locallang_mod4.xlf index f63133db9..839a9fef2 100755 --- a/Resources/Private/Language/de.locallang_mod4.xlf +++ b/Resources/Private/Language/de.locallang_mod4.xlf @@ -1,19 +1,315 @@ - - - + + +
- - Publish Tools - Publisher Tools + + Publisher Tools + Publisher Tools - + This Module offers some admin tools to the TYPO3 Content Publisher. - Dieses Modul bietet Administratoren einige Zusatzfunktionen zum TYPO3 Content Publisher. + Dieses Modul bietet Administratoren einige Zusatzfunktionen zum TYPO3 Content Publisher. - + TYPO3 Content Publisher - TYPO3 Content Publisher + TYPO3 Content Publisher + + + Show the system info + Systeminformationen anzeigen + + + Download the system info + Systeminformationen herunterladen + + + Decode the system info JSON + JSON dekodieren + + + JSON string + JSON Text + + + Decode! + Dekodieren + + + JSON decode error #%d: %s + JSON dekodierfehler #%d: %s + + + Systeminformation JSON file + Systeminformationen JSON Datei + + + Upload! + Hochladen! + + + Select classification and identifier for inspection + Bitte Klassifizierung und Identität auswählen + + + Toggle all relations + Alle Relationen umschalten + + + New + Neu + + + Deleted + Gelöscht + + + Differences + Unterschiede + + + Generally + Generell + + + The table is empty on Local + Die Tabelle is auf Local leer. + + + The table is empty on foreign + Die Tabelle is auf Foreign leer. + + + Transfer these changes to the foreign system + Übertrage diese Änderungen auf das entfernte System + + + No differences were found in the data of the selected tables. + Es wurden keine Unterschiede in den Daten der gewählten Tabellen entdeckt. + + + Select the tables to compare + Tabellen für den Vergleich auswählen + + + Tables + Tabellen + + + Compare + Vergleichen + + + This is not publishing + Dies ist kein Publishing + + + A transfer is not a publishing. Only the exact record that is selected is transferred. No relations to other records are resolved and no MM-records are displayed nor transferred. You should always prefer normal publishing to this feature. Use this feature only in an emergency and only if you are aware of all the consequences. + Ein Transfer ist kein Publishing. Es wird dabei nur exakt der Datensatz übertragen, der ausgewählt wird. Es werden keine Verknüpfungen zu anderen Datensätzen aufgelöst und es werden auch keine MM-Datensätze angezeigt oder übertragen. Sie sollten ein normales Publishing immer diesem Feature vorziehen. Nutzen Sie dieses Feature nur im Notfall und nur, wenn Sie sich aller Konsequenzen bewusst sind. + + + Record label + Datensatztitle + + + Actions + Aktionen + + + Error + Fehler + + + Success + Erfolg + + + The record which should be transferred does not exist anymore. + Der Datensatz der übertragen werden sollte existiert nicht mehr. + + + The record which should be transferred should not exists on Foreign but it does. It was probably published in the meantime. + Der Datensatz der übertragen werden sollte sollte nicht auf Foreign existieren, jedoch ist er vorhanden. Möglicherweise wurde er in der Zwischenzeit veröffentlicht. + + + The record which should be transferred should not exists on Local but it does. It was probably created in the meantime. + Der Datensatz der übertragen werden sollte sollte nicht auf Local existieren, jedoch ist er vorhanden. Möglicherweise wurde er in der Zwischenzeit erstellt. + + + The record which should be transferred should exists on both Local and Foreign but it does not. Please reload the comparison view and try again. + Der Datensatz der übertragen werden sollte sollte auf Local und Foreign existieren, er fehlt jedoch auf mindestens einem System. Bitte versuchen Sie es noch einmal, nachdem Sie die Vergleichsansicht neu geladen haben. + + + The record %s[%d] was deleted from foreign. + Der Datensatz %s[%d] wurde auf Foreign gelöscht. + + + The record %s[%d] was created on foreign. + Der Datensatz %s[%d] wurde auf Foreign erstellt. + + + The record %s[%d] was updated on foreign. + Der Datensatz %s[%d] wurde auf Foreign aktualisiert. + + + Overview + Übersicht + + + Getting help + Hilfe + + + https://typo3.slack.com/archives/C2ULY79MZ]]> + https://typo3.slack.com/archives/C2ULY79MZ]]> + + + https://github.com/in2code-de/in2publish_core/tree/master/Documentation]]> + https://github.com/in2code-de/in2publish_core/tree/master/Documentation]]> + + + Navigate to incompatible TCA + Zu inkompatiblem TCA springen + + + Navigate to compatible TCA + Zu kompatiblem TCA springen + + + Incompatible TCA + Inkompatibles TCA + + + Compatible TCA + Kompatibles TCA + + + Navigate to personal configuration + zur persönlichen Konfiguration springen + + + Navigate to global configuration + zur globalen Konfiguration springen + + + Navigate to config container dump + zum Dump des Config Containers springen + + + Enter page ID for page specific configuration + Seiten-ID für Seitenspezifische konfiguration angeben + + + Config container dump + Config Container Konfiguration + + + Global configuration + Globale Konfiguration + + + Personal configuration + Persönliche Konfiguration + + + Flushed all related registry entries. + Alle zugehörigen Registereinträge wurden gelöscht. + + + The Content Publisher stores various information in the registry, like the outcome of the last test run. You can force the removal of these entries here. + Der Content Publisher speichert Informationen wie das Ergebnis des letzten Testlaufs in der Registry. Sie können die Löschung dieser Einträge hier erzwingen. + + + Remove registry entries + Entferne Registrierungseinträge + + + Flushed all superfluous envelopes. + Alle überflüssigen Envelopes wurden gelöscht. + + + There are superfluous envelopes in the foreign database. + In der Foreign Datenbank sind überflüssige Envelopes vorhanden. + + + There are no envelopes to flush. + Es gibt keine Envelopes zum löschen. + + + Remove superfluous envelopes + Entferne überflüssige Envelopes + + + Introduction + Übersicht + + + This tool just lists all other tools. + Dieses Tool listet nur alle anderen Tools auf + + + Show configuration + Konfigurationsübersicht + + + Display the local configuration of in2publish from LocalConfiguration.yaml + Zeige die aktuelle Konfiguration von in2publish von der LocalConfiguration.yaml Datei + + + Tests + Tests + + + Run some function test of in2publish + Teste in2publish auf Funktionsfähigkeit + + + Inspect TCA + TCA inspection + + + See which parts of the TCA are processed, and the reason why others are not. + Zeige die Teile des TCA die verwendet werden und alle anderen mit der Begründung warum sie nicht verwendet werden. + + + Remove superfluous envelopes + Überflüssige Envelopes löschen + + + Deletes all envelopes (remote procedure call database entries) which were already used (envelopes are one-way records). + Löscht alle Envelopes ("Remote Procedure Call"-Datenbankeinträge) die bereits genutzt wurden (Diese sind Einweg-Einträge) + + + Remove all registry entries + Registereinträge löschen + + + Removes all registry entries which store information about the local and foreign environment. You must run the test after flushing these. + Löscht alle Registereinträge, welche Informationen über das lokale und entfernte System vorhalten. Danach müssen die Tests erneut ausgeführt werden. + + + Systeminformation + Systeminformationen + + + Use this module to collect information which will be required or useful to solve a bug or problem. + Anzeige von Informationen welche erforderlich oder nützlich sind Probleme zu identifizieren und sie zu beheben. + + + Compare databases + Datenbankvergleich + + + The compare tool scans the whole database for differences. This tool is ought to help administrators clean up UID conflicts. + Zeigt unterschiede zwischen den Datenbanken an. Dieses Tools soll Administratoren dabei helfen UID-Konflikte zu lösen. + + + Inspect a record tree + RecordTree Untersuchen + + + You can query for a record tree by classification and identifier and inspect it with all of its properties and children. + Hier kann ein RecordTree mittels Klassifikation und Identifikation aufgebaut und untersucht werden.. diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf index d107911cf..cb21e09aa 100755 --- a/Resources/Private/Language/locallang.xlf +++ b/Resources/Private/Language/locallang.xlf @@ -1,617 +1,378 @@ - - - + + +
- + Publish Overview - - + TYPO3 Content Publisher - + publish pages and records overview - + Publish files - - administration tools - - + Publish redirects - - + Depth - + %d level - + %d levels - - + Stage System - + Production System - + The selected record has been published successfully. (Duration: %s) - + One or more errors occurred during publishing. Please check the logs. (Duration: %s) - + One or more errors occurred during relation resolving. Please check the logs. - + The selected record has been published successfully. - + The selected folder has been created successfully. - + The selected folder could not be created. - + The selected folder has been removed successfully. - + The selected folder could not be removed. - - in2publish failed to retrieve information about the folder %s or any of it's sub folders. Please verify that is in2publish is working properly by running the tests. + + in2publish failed to retrieve information about the folder %s or any of it's sub folders. Please verify that is in2publish is working properly by running the tests. - - Publish page "%s" and its subpages. + + Publish page "%s" and its subpages. - - Do you really want to publish all pages? - - + Do you really want to publish this page? - + Publish - + + Not publishable + + Publish this page - + This page is currently being published - + This file is currently being published - - Publish all files within folder "%s". + + Publish all files within folder "%s". - + Do you really want to publish all files? - + Do you really want to publish this file? - + Preview - + Compare - + History - + File preview - + Edit - + %s properties - + Page properties - + Record history - + Edit record - + Related Records - + Record State - - - Introduction - - - This tool just lists all other tools. - - - Show configuration - - - Display the local configuration of in2publish from LocalConfiguration.yaml - - - Show logs - - - Show all logs of the Content Publisher with plenty filter options - - - Tests - - - Run some function test of in2publish - - - Inspect TCA - - - See which parts of the TCA are processed, and the reason why others are not. - - - Remove superfluous envelopes - - - Deletes all envelopes (remote procedure call database entries) which were already used (envelopes are one-way records). - - - Remove all Registry entries - - - Removes all registry entries which store information about the local and foreign environment. You must run the test after flushing these. - - - System information - - - Use this module to collect information which will be required or useful to solve a bug or problem. - - - Compare Databases - - - The compare tool scans the whole database for differences. This tool is ought to help administrators clean up UID conflicts. - - - Inspect a record tree - - - You can query for a record tree by classification and identifier and inspect it with all of its properties and children. - - - - Show the system info - - - Download the system info - - - Decode the system info JSON - - - JSON string - - - Decode! - - - Error during json decoding - - - JSON decode error #%d: %s - - - SysInfo JSON file - - - Upload! - - - + Filter by: - - + Filename - - + Status - - + Record unchanged - + Record changed - + New record - + Record deleted - + Record moved - - + Unchanged - + Changed - + New - + Deleted - + Moved - + Moved/Changed - - Configuration - - - Global Configuration - - - Personal Config - - - Config Container Dump - - - Enter Page ID for page specific configuration - - - Overview - - - Filter - - - TCA inspection - - - Compatible TCA - - - Incompatible TCA - - - Controls - - - Navigate to incompatible TCA - - - Navigate to compatible TCA - - - Navigate to controls - - - Flushed all related registry entries. - - - Flushed all superfluous envelopes. - - - Flush superfluous envelopes. - - - There are no envelopes to flush. - - - Clear registry entries. - - - The Content Publisher stores various information in the registry, like the outcome of the last test run. You can force the removal of these entries here. - - - + In2publish might not work as expected. - + Unknown test state. Please execute the tests in the Publish Tools Module. - + Some In2publish tests are failing. - + Your TYPO3 installation changed. - + The In2publish configuration has changed. - - + Vertical - + Horizontal - - + Successfully published. - + Something went wrong. - + The selected folder %s has been published to the foreign system. - + The selected folder %s could not be published. - + The selected file %s has been published to the foreign system. - + The selected file %s could not be fully published. - - + One or more additional tasks which run after the successful publishing process failed. Caches and other non critical aspects might not behave as expected. The logs might contain more information. - - + Secure, authenticated and encrypted command execution - + Secure, reliable and encrypted asset transmission - - - https://typo3.slack.com/archives/C2ULY79MZ]]> - - - https://github.com/in2code-de/in2publish_core/tree/master/Documentation]]> - - - + You are not allowed to publish this page. - + No page parameter was transferred. - - The page "%s" has been published. + + The page "%s" has been published. - - Error during publishing of page "%s". Please check your logs. + + Error during publishing of page "%s". Please check your logs. - + This record is not yet publishable. - - + Associated page - + Associated foreign Site - + Use site from associated page - + Page/Site association - - + Save and publish - + Publish all selected - + Status icons explained - + The redirect requires an associated page or site to be published - + The associated page is not published - + This redirect has unpublished changes. Hover over the icon to get more information - + This redirect is published - + Action icons explained - + This redirect can be published. Click to publish. - + This redirect can be published with a manual site association. Click to assign a Site and publish. - - + Property - + Old value - + New value - + ID - + Domain - + Source - + Target - + Status - + Actions - - + Missing associated page or site - - The associated page [%d] "%s" is not published + + The associated page [%d] "%s" is not published - + This redirect is published - - + Ready for publishing - + Missing page or site - + Requires page publishing - + Published - - + Filter - + Association - + Association present - + Association missing - - + Refresh page - - - New - - - Deleted - - - Differences - - - Generally - - - The table is empty on Local - - - The table is empty on Foreign - - - Transfer these changes to the foreign system - - - No differences were found in the data of the selected tables. - - - Select the tables to compare - - - Tables - - - Compare - - - This is not publishing - - - A transfer is not a publishing. Only the exact record that is selected is transferred. No relations to other records are resolved and no MM-records are displayed nor transferred. You should always prefer normal publishing to this feature. Use this feature only in an emergency and only if you are aware of all the consequences. - - - Record label - - - Actions - - - Differences - - - Error - - - Success - - - The record which should be transferred does not exist anymore. - - - The record which should be transferred should not exists on Foreign but it does. It was probably published in the meantime. + + Admin tools - - The record which should be transferred should not exists on Local but it does. It was probably created in the meantime. + + "%s" requires that the record "%s" (which is the default language version) is published first. - - The record which should be transferred should exists on both Local and Foreign but it does not. Please reload the comparison view and try again. + + The record "%s" cannot be published until all visibility settings (%s) of the default language "%s" have been published. - - The record %s[%d] was deleted from foreign. + + "%s" requires that the page "%s" is published first. - - The record %s[%d] was created on foreign. + + "%s" requires that the page "%s" has all of its properties published (%s) which determine if the record is visible. - - The record %s[%d] was updated on foreign. + + The record with classification "%s" and properties "%s" does not exist. - - - Admin tools + + The record "%s" is a target of the shortcut record "%s". The target must be published before the shortcut record can be published. - - - "%s" requires that the record "%s" (which is the default language version) is published first. + + This page has unmet dependencies. Since you cannot access the dependencies, it is possible to publish this page anyway. However, there is no guarantee that the page will look exactly the same as on Local. + You can see the dependencies in the details of the page. - - The record "%s" cannot be published until all visibility settings (%s) of the default language "%s" have been published. + + Publish - - "%s" requires that the page "%s" is published first. + + Abort - - "%s" requires that the page "%s" has all of its properties published (%s) which determine if the record is visible. + + Confirm publish - - The record with classification "%s" and properties "%s" does not exist. + + Are you sure you want to publish the file %s? This cannot be reversed. - - The record "%s" is a target of the shortcut record "%s". The target must be published before the shortcut record can be published. + + Are you sure you want to publish the folder %s? This cannot be reversed. diff --git a/Resources/Private/Language/locallang_js.xlf b/Resources/Private/Language/locallang_js.xlf index 2a3d884e5..88fc4c8d4 100755 --- a/Resources/Private/Language/locallang_js.xlf +++ b/Resources/Private/Language/locallang_js.xlf @@ -9,19 +9,6 @@ Abort - - Confirm publish - - - Are you sure you want to publish the file $name$? This cannot be reversed. - - - Are you sure you want to publish the folder $name$? This cannot be reversed. - - - This page has unmet dependencies. Since you cannot access the dependencies, it is possible to publish this page anyway. However, there is no guarantee that the page will look exactly the same as on Local. - You can see the dependencies in the details of the page. - diff --git a/Resources/Private/Language/locallang_mod4.xlf b/Resources/Private/Language/locallang_mod4.xlf index c7f2c1156..9fa701713 100755 --- a/Resources/Private/Language/locallang_mod4.xlf +++ b/Resources/Private/Language/locallang_mod4.xlf @@ -1,17 +1,239 @@ - - - + + +
- - Publish Tools + + Publisher Tools - + This Module offers some admin tools to the TYPO3 Content Publisher. - + TYPO3 Content Publisher + + Show the system info + + + Download the system info + + + Decode the system info JSON + + + JSON string + + + Decode! + + + JSON decode error #%d: %s + + + Systeminformation JSON file + + + Upload! + + + Select classification and identifier for inspection + + + Toggle all relations + + + New + + + Deleted + + + Differences + + + Generally + + + The table is empty on Local + + + The table is empty on foreign + + + Transfer these changes to the foreign system + + + No differences were found in the data of the selected tables. + + + Select the tables to compare + + + Tables + + + Compare + + + This is not publishing + + + A transfer is not a publishing. Only the exact record that is selected is transferred. No relations to other records are resolved and no MM-records are displayed nor transferred. You should always prefer normal publishing to this feature. Use this feature only in an emergency and only if you are aware of all the consequences. + + + Record label + + + Actions + + + Error + + + Success + + + The record which should be transferred does not exist anymore. + + + The record which should be transferred should not exists on Foreign but it does. It was probably published in the meantime. + + + The record which should be transferred should not exists on Local but it does. It was probably created in the meantime. + + + The record which should be transferred should exists on both Local and Foreign but it does not. Please reload the comparison view and try again. + + + The record %s[%d] was deleted from foreign. + + + The record %s[%d] was created on foreign. + + + The record %s[%d] was updated on foreign. + + + Overview + + + Getting help + + + https://typo3.slack.com/archives/C2ULY79MZ]]> + + + https://github.com/in2code-de/in2publish_core/tree/master/Documentation]]> + + + Navigate to incompatible TCA + + + Navigate to compatible TCA + + + Incompatible TCA + + + Compatible TCA + + + Navigate to personal configuration + + + Navigate to global configuration + + + Navigate to config container dump + + + Enter page ID for page specific configuration + + + Config container dump + + + Global configuration + + + Personal configuration + + + Flushed all related registry entries. + + + The Content Publisher stores various information in the registry, like the outcome of the last test run. You can force the removal of these entries here. + + + Remove registry entries + + + Flushed all superfluous envelopes. + + + There are superfluous envelopes in the foreign database. + + + There are no envelopes to flush. + + + Remove superfluous envelopes + + + Introduction + + + This tool just lists all other tools. + + + Show configuration + + + Display the local configuration of in2publish from LocalConfiguration.yaml + + + Tests + + + Run some function test of in2publish + + + Inspect TCA + + + See which parts of the TCA are processed, and the reason why others are not. + + + Remove superfluous envelopes + + + Deletes all envelopes (remote procedure call database entries) which were already used (envelopes are one-way records). + + + Remove all registry entries + + + Removes all registry entries which store information about the local and foreign environment. You must run the test after flushing these. + + + Systeminformation + + + Use this module to collect information which will be required or useful to solve a bug or problem. + + + Compare databases + + + The compare tool scans the whole database for differences. This tool is ought to help administrators clean up UID conflicts. + + + Inspect a record tree + + + You can query for a record tree by classification and identifier and inspect it with all of its properties and children. + diff --git a/Resources/Private/Layouts/File.html b/Resources/Private/Layouts/File.html index 4ca88107b..2e3e15cbf 100755 --- a/Resources/Private/Layouts/File.html +++ b/Resources/Private/Layouts/File.html @@ -1,8 +1,4 @@ -
- - - diff --git a/Resources/Private/Layouts/Record.html b/Resources/Private/Layouts/Record.html index 841376007..5a9b9c9aa 100755 --- a/Resources/Private/Layouts/Record.html +++ b/Resources/Private/Layouts/Record.html @@ -1,12 +1,11 @@ -
-
-
+
- - - + +
+ +
diff --git a/Resources/Private/Layouts/Submodule.html b/Resources/Private/Layouts/Submodule.html new file mode 100644 index 000000000..e0d5bfcd5 --- /dev/null +++ b/Resources/Private/Layouts/Submodule.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/Resources/Private/Libraries/Spyc/Spyc.php b/Resources/Private/Libraries/Spyc/Spyc.php deleted file mode 100755 index c30a4dca5..000000000 --- a/Resources/Private/Libraries/Spyc/Spyc.php +++ /dev/null @@ -1,1155 +0,0 @@ - - * @author Chris Wanstrath - * @link https://github.com/mustangostang/spyc/ - * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen - * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @package Spyc - */ - -if (!function_exists('spyc_load')) { - /** - * Parses YAML to array. - * @param string $string YAML string. - * @return array - */ - function spyc_load ($string) { - return Spyc::YAMLLoadString($string); - } -} - -if (!function_exists('spyc_load_file')) { - /** - * Parses YAML to array. - * @param string $file Path to YAML file. - * @return array - */ - function spyc_load_file ($file) { - return Spyc::YAMLLoad($file); - } -} - -if (!function_exists('spyc_dump')) { - /** - * Dumps array to YAML. - * @param array $data Array. - * @return string - */ - function spyc_dump ($data) { - return Spyc::YAMLDump($data, false, false, true); - } -} - -/** - * The Simple PHP YAML Class. - * - * This class can be used to read a YAML file and convert its contents - * into a PHP array. It currently supports a very limited subsection of - * the YAML spec. - * - * Usage: - * - * $Spyc = new Spyc; - * $array = $Spyc->load($file); - * - * or: - * - * $array = Spyc::YAMLLoad($file); - * - * or: - * - * $array = spyc_load_file($file); - * - * @package Spyc - */ -class Spyc { - - // SETTINGS - - const REMPTY = "\0\0\0\0\0"; - - /** - * Setting this to true will force YAMLDump to enclose any string value in - * quotes. False by default. - * - * @var bool - */ - public $setting_dump_force_quotes = false; - - /** - * Setting this to true will forse YAMLLoad to use syck_load function when - * possible. False by default. - * @var bool - */ - public $setting_use_syck_is_possible = false; - - - - /**#@+ - * @access private - * @var mixed - */ - private $_dumpIndent; - private $_dumpWordWrap; - private $_containsGroupAnchor = false; - private $_containsGroupAlias = false; - private $path; - private $result; - private $LiteralPlaceHolder = '___YAML_Literal_Block___'; - private $SavedGroups = array(); - private $indent; - /** - * Path modifier that should be applied after adding current element. - * @var array - */ - private $delayedPath = array(); - - /**#@+ - * @access public - * @var mixed - */ - public $_nodeId; - -/** - * Load a valid YAML string to Spyc. - * @param string $input - * @return array - */ - public function load ($input) { - return $this->__loadString($input); - } - - /** - * Load a valid YAML file to Spyc. - * @param string $file - * @return array - */ - public function loadFile ($file) { - return $this->__load($file); - } - - /** - * Load YAML into a PHP array statically - * - * The load method, when supplied with a YAML stream (string or file), - * will do its best to convert YAML in a file into a PHP array. Pretty - * simple. - * Usage: - * - * $array = Spyc::YAMLLoad('lucky.yaml'); - * print_r($array); - * - * @access public - * @return array - * @param string $input Path of YAML file or string containing YAML - */ - public static function YAMLLoad($input) { - $Spyc = new Spyc; - return $Spyc->__load($input); - } - - /** - * Load a string of YAML into a PHP array statically - * - * The load method, when supplied with a YAML string, will do its best - * to convert YAML in a string into a PHP array. Pretty simple. - * - * Note: use this function if you don't want files from the file system - * loaded and processed as YAML. This is of interest to people concerned - * about security whose input is from a string. - * - * Usage: - * - * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); - * print_r($array); - * - * @access public - * @return array - * @param string $input String containing YAML - */ - public static function YAMLLoadString($input) { - $Spyc = new Spyc; - return $Spyc->__loadString($input); - } - - /** - * Dump YAML from PHP array statically - * - * The dump method, when supplied with an array, will do its best - * to convert the array into friendly YAML. Pretty simple. Feel free to - * save the returned string as nothing.yaml and pass it around. - * - * Oh, and you can decide how big the indent is and what the wordwrap - * for folding is. Pretty cool -- just pass in 'false' for either if - * you want to use the default. - * - * Indent's default is 2 spaces, wordwrap's default is 40 characters. And - * you can turn off wordwrap by passing in 0. - * - * @access public - * @return string - * @param array $array PHP array - * @param int $indent Pass in false to use the default, which is 2 - * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) - * @param int $no_opening_dashes Do not start YAML file with "---\n" - */ - public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { - $spyc = new Spyc; - return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); - } - - - /** - * Dump PHP array to YAML - * - * The dump method, when supplied with an array, will do its best - * to convert the array into friendly YAML. Pretty simple. Feel free to - * save the returned string as tasteful.yaml and pass it around. - * - * Oh, and you can decide how big the indent is and what the wordwrap - * for folding is. Pretty cool -- just pass in 'false' for either if - * you want to use the default. - * - * Indent's default is 2 spaces, wordwrap's default is 40 characters. And - * you can turn off wordwrap by passing in 0. - * - * @access public - * @return string - * @param array $array PHP array - * @param int $indent Pass in false to use the default, which is 2 - * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) - */ - public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { - // Dumps to some very clean YAML. We'll have to add some more features - // and options soon. And better support for folding. - - // New features and options. - if ($indent === false or !is_numeric($indent)) { - $this->_dumpIndent = 2; - } else { - $this->_dumpIndent = $indent; - } - - if ($wordwrap === false or !is_numeric($wordwrap)) { - $this->_dumpWordWrap = 40; - } else { - $this->_dumpWordWrap = $wordwrap; - } - - // New YAML document - $string = ""; - if (!$no_opening_dashes) $string = "---\n"; - - // Start at the base of the array and move through it. - if ($array) { - $array = (array)$array; - $previous_key = -1; - foreach ($array as $key => $value) { - if (!isset($first_key)) $first_key = $key; - $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); - $previous_key = $key; - } - } - return $string; - } - - /** - * Attempts to convert a key / value array item to YAML - * @access private - * @return string - * @param $key The name of the key - * @param $value The value of the item - * @param $indent The indent of the current node - */ - private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { - if (is_array($value)) { - if (empty ($value)) - return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); - // It has children. What to do? - // Make it the right kind of item - $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array); - // Add the indent - $indent += $this->_dumpIndent; - // Yamlize the array - $string .= $this->_yamlizeArray($value,$indent); - } elseif (!is_array($value)) { - // It doesn't have children. Yip. - $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); - } - return $string; - } - - /** - * Attempts to convert an array to YAML - * @access private - * @return string - * @param $array The array you want to convert - * @param $indent The indent of the current level - */ - private function _yamlizeArray($array,$indent) { - if (is_array($array)) { - $string = ''; - $previous_key = -1; - foreach ($array as $key => $value) { - if (!isset($first_key)) $first_key = $key; - $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); - $previous_key = $key; - } - return $string; - } else { - return false; - } - } - - /** - * Returns YAML from a key and a value - * @access private - * @return string - * @param $key The name of the key - * @param $value The value of the item - * @param $indent The indent of the current node - */ - private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { - // do some folding here, for blocks - if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || - strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, ' ') !== false || - strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || - substr ($value, -1, 1) == ':') - ) { - $value = $this->_doLiteralBlock($value,$indent); - } else { - $value = $this->_doFolding($value,$indent); - } - - if ($value === array()) $value = '[ ]'; - if ($value === "") $value = '""'; - if (self::isTranslationWord($value)) { - $value = $this->_doLiteralBlock($value, $indent); - } - if (trim ($value) != $value) - $value = $this->_doLiteralBlock($value,$indent); - - if (is_bool($value)) { - $value = $value ? "true" : "false"; - } - - if ($value === null) $value = 'null'; - if ($value === "'" . self::REMPTY . "'") $value = null; - - $spaces = str_repeat(' ',$indent); - - //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { - if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { - // It's a sequence - $string = $spaces.'- '.$value."\n"; - } else { - // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"'); - // It's mapped - if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } - $string = rtrim ($spaces.$key.': '.$value)."\n"; - } - return $string; - } - - /** - * Creates a literal block for dumping - * @access private - * @return string - * @param $value - * @param $indent int The value of the indent - */ - private function _doLiteralBlock($value,$indent) { - if ($value === "\n") return '\n'; - if (strpos($value, "\n") === false && strpos($value, "'") === false) { - return sprintf ("'%s'", $value); - } - if (strpos($value, "\n") === false && strpos($value, '"') === false) { - return sprintf ('"%s"', $value); - } - $exploded = explode("\n",$value); - $newValue = '|'; - $indent += $this->_dumpIndent; - $spaces = str_repeat(' ',$indent); - foreach ($exploded as $line) { - $newValue .= "\n" . $spaces . ($line); - } - return $newValue; - } - - /** - * Folds a string of text, if necessary - * @access private - * @return string - * @param $value The string you wish to fold - */ - private function _doFolding($value,$indent) { - // Don't do anything if wordwrap is set to 0 - - if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { - $indent += $this->_dumpIndent; - $indent = str_repeat(' ',$indent); - $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); - $value = ">\n".$indent.$wrapped; - } else { - if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY) - $value = '"' . $value . '"'; - if (is_numeric($value) && is_string($value)) - $value = '"' . $value . '"'; - } - - - return $value; - } - - private function isTrueWord($value) { - $words = self::getTranslations(array('true', 'on', 'yes', 'y')); - return in_array($value, $words, true); - } - - private function isFalseWord($value) { - $words = self::getTranslations(array('false', 'off', 'no', 'n')); - return in_array($value, $words, true); - } - - private function isNullWord($value) { - $words = self::getTranslations(array('null', '~')); - return in_array($value, $words, true); - } - - private function isTranslationWord($value) { - return ( - self::isTrueWord($value) || - self::isFalseWord($value) || - self::isNullWord($value) - ); - } - - /** - * Coerce a string into a native type - * Reference: http://yaml.org/type/bool.html - * TODO: Use only words from the YAML spec. - * @access private - * @param $value The value to coerce - */ - private function coerceValue(&$value) { - if (self::isTrueWord($value)) { - $value = true; - } else if (self::isFalseWord($value)) { - $value = false; - } else if (self::isNullWord($value)) { - $value = null; - } - } - - /** - * Given a set of words, perform the appropriate translations on them to - * match the YAML 1.1 specification for type coercing. - * @param $words The words to translate - * @access private - */ - private static function getTranslations(array $words) { - $result = array(); - foreach ($words as $i) { - $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i))); - } - return $result; - } - -// LOADING FUNCTIONS - - private function __load($input) { - $Source = $this->loadFromSource($input); - return $this->loadWithSource($Source); - } - - private function __loadString($input) { - $Source = $this->loadFromString($input); - return $this->loadWithSource($Source); - } - - private function loadWithSource($Source) { - if (empty ($Source)) return array(); - if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { - $array = syck_load (implode ("\n", $Source)); - return is_array($array) ? $array : array(); - } - - $this->path = array(); - $this->result = array(); - - $cnt = count($Source); - for ($i = 0; $i < $cnt; $i++) { - $line = $Source[$i]; - - $this->indent = strlen($line) - strlen(ltrim($line)); - $tempPath = $this->getParentPathByIndent($this->indent); - $line = self::stripIndent($line, $this->indent); - if (self::isComment($line)) continue; - if (self::isEmpty($line)) continue; - $this->path = $tempPath; - - $literalBlockStyle = self::startsLiteralBlock($line); - if ($literalBlockStyle) { - $line = rtrim ($line, $literalBlockStyle . " \n"); - $literalBlock = ''; - $line .= ' '.$this->LiteralPlaceHolder; - $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); - while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { - $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); - } - $i--; - } - - // Strip out comments - if (strpos ($line, '#')) { - $line = preg_replace('/\s*#([^"\']+)$/','',$line); - } - - while (++$i < $cnt && self::greedilyNeedNextLine($line)) { - $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); - } - $i--; - - $lineArray = $this->_parseLine($line); - - if ($literalBlockStyle) - $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); - - $this->addArray($lineArray, $this->indent); - - foreach ($this->delayedPath as $indent => $delayedPath) - $this->path[$indent] = $delayedPath; - - $this->delayedPath = array(); - - } - return $this->result; - } - - private function loadFromSource ($input) { - if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) - $input = file_get_contents($input); - - return $this->loadFromString($input); - } - - private function loadFromString ($input) { - $lines = explode("\n",$input); - foreach ($lines as $k => $_) { - $lines[$k] = rtrim ($_, "\r"); - } - return $lines; - } - - /** - * Parses YAML code and returns an array for a node - * @access private - * @return array - * @param string $line A line from the YAML file - */ - private function _parseLine($line) { - if (!$line) return array(); - $line = trim($line); - if (!$line) return array(); - - $array = array(); - - $group = $this->nodeContainsGroup($line); - if ($group) { - $this->addGroup($line, $group); - $line = $this->stripGroup ($line, $group); - } - - if ($this->startsMappedSequence($line)) - return $this->returnMappedSequence($line); - - if ($this->startsMappedValue($line)) - return $this->returnMappedValue($line); - - if ($this->isArrayElement($line)) - return $this->returnArrayElement($line); - - if ($this->isPlainArray($line)) - return $this->returnPlainArray($line); - - - return $this->returnKeyValuePair($line); - - } - - /** - * Finds the type of the passed value, returns the value as the new type. - * @access private - * @param string $value - * @return mixed - */ - private function _toType($value) { - if ($value === '') return ""; - $first_character = $value[0]; - $last_character = substr($value, -1, 1); - - $is_quoted = false; - do { - if (!$value) break; - if ($first_character != '"' && $first_character != "'") break; - if ($last_character != '"' && $last_character != "'") break; - $is_quoted = true; - } while (0); - - if ($is_quoted) { - $value = str_replace('\n', "\n", $value); - return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\'')); - } else { - if ((string)(int)$value === (string)$value) { - return(int) $value; - } - } - - if (strpos($value, ' #') !== false && !$is_quoted) - $value = preg_replace('/\s+#(.+)$/','',$value); - - if ($first_character == '[' && $last_character == ']') { - // Take out strings sequences and mappings - $innerValue = trim(substr ($value, 1, -1)); - if ($innerValue === '') return array(); - $explode = $this->_inlineEscape($innerValue); - // Propagate value array - $value = array(); - foreach ($explode as $v) { - $value[] = $this->_toType($v); - } - return $value; - } - - if (strpos($value,': ')!==false && $first_character != '{') { - $array = explode(': ',$value); - $key = trim($array[0]); - array_shift($array); - $value = trim(implode(': ',$array)); - $value = $this->_toType($value); - return array($key => $value); - } - - if ($first_character == '{' && $last_character == '}') { - $innerValue = trim(substr ($value, 1, -1)); - if ($innerValue === '') return array(); - // Inline Mapping - // Take out strings sequences and mappings - $explode = $this->_inlineEscape($innerValue); - // Propagate value array - $array = array(); - foreach ($explode as $v) { - $SubArr = $this->_toType($v); - if (empty($SubArr)) continue; - if (is_array ($SubArr)) { - $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; - } - $array[] = $SubArr; - } - return $array; - } - - if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { - return null; - } - - if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ - $intvalue = (int)$value; - if ($intvalue != PHP_INT_MAX) - $value = $intvalue; - return $value; - } - - if (is_numeric($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) { - // Hexadecimal value. - return hexdec($value); - } - - $this->coerceValue($value); - - if (is_numeric($value)) { - if ($value === '0') return 0; - if (rtrim ($value, 0) === $value) - $value = (float)$value; - return $value; - } - - return $value; - } - - /** - * Used in inlines to check for more inlines or quoted strings - * @access private - * @return array - */ - private function _inlineEscape($inline) { - // There's gotta be a cleaner way to do this... - // While pure sequences seem to be nesting just fine, - // pure mappings and mappings with sequences inside can't go very - // deep. This needs to be fixed. - - $seqs = array(); - $maps = array(); - $saved_strings = array(); - $saved_empties = array(); - - // Check for empty strings - $regex = '/("")|(\'\')/'; - if (preg_match_all($regex,$inline,$strings)) { - $saved_empties = $strings[0]; - $inline = preg_replace($regex,'YAMLEmpty',$inline); - } - unset($regex); - - // Check for strings - $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; - if (preg_match_all($regex,$inline,$strings)) { - $saved_strings = $strings[0]; - $inline = preg_replace($regex,'YAMLString',$inline); - } - unset($regex); - - // echo $inline; - - $i = 0; - do { - - // Check for sequences - while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { - $seqs[] = $matchseqs[0]; - $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); - } - - // Check for mappings - while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { - $maps[] = $matchmaps[0]; - $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); - } - - if ($i++ >= 10) break; - - } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); - - $explode = explode(',',$inline); - $explode = array_map('trim', $explode); - $stringi = 0; $i = 0; - - while (1) { - - // Re-add the sequences - if (!empty($seqs)) { - foreach ($explode as $key => $value) { - if (strpos($value,'YAMLSeq') !== false) { - foreach ($seqs as $seqk => $seq) { - $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); - $value = $explode[$key]; - } - } - } - } - - // Re-add the mappings - if (!empty($maps)) { - foreach ($explode as $key => $value) { - if (strpos($value,'YAMLMap') !== false) { - foreach ($maps as $mapk => $map) { - $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); - $value = $explode[$key]; - } - } - } - } - - - // Re-add the strings - if (!empty($saved_strings)) { - foreach ($explode as $key => $value) { - while (strpos($value,'YAMLString') !== false) { - $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); - unset($saved_strings[$stringi]); - ++$stringi; - $value = $explode[$key]; - } - } - } - - - // Re-add the empties - if (!empty($saved_empties)) { - foreach ($explode as $key => $value) { - while (strpos($value,'YAMLEmpty') !== false) { - $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); - $value = $explode[$key]; - } - } - } - - $finished = true; - foreach ($explode as $key => $value) { - if (strpos($value,'YAMLSeq') !== false) { - $finished = false; break; - } - if (strpos($value,'YAMLMap') !== false) { - $finished = false; break; - } - if (strpos($value,'YAMLString') !== false) { - $finished = false; break; - } - if (strpos($value,'YAMLEmpty') !== false) { - $finished = false; break; - } - } - if ($finished) break; - - $i++; - if ($i > 10) - break; // Prevent infinite loops. - } - - - return $explode; - } - - private function literalBlockContinues ($line, $lineIndent) { - if (!trim($line)) return true; - if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; - return false; - } - - private function referenceContentsByAlias ($alias) { - do { - if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } - $groupPath = $this->SavedGroups[$alias]; - $value = $this->result; - foreach ($groupPath as $k) { - $value = $value[$k]; - } - } while (false); - return $value; - } - - private function addArrayInline ($array, $indent) { - $CommonGroupPath = $this->path; - if (empty ($array)) return false; - - foreach ($array as $k => $_) { - $this->addArray(array($k => $_), $indent); - $this->path = $CommonGroupPath; - } - return true; - } - - private function addArray ($incoming_data, $incoming_indent) { - - // print_r ($incoming_data); - - if (count ($incoming_data) > 1) - return $this->addArrayInline ($incoming_data, $incoming_indent); - - $key = key ($incoming_data); - $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; - if ($key === '__!YAMLZero') $key = '0'; - - if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. - if ($key || $key === '' || $key === '0') { - $this->result[$key] = $value; - } else { - $this->result[] = $value; end ($this->result); $key = key ($this->result); - } - $this->path[$incoming_indent] = $key; - return; - } - - - - $history = array(); - // Unfolding inner array tree. - $history[] = $_arr = $this->result; - foreach ($this->path as $k) { - $history[] = $_arr = $_arr[$k]; - } - - if ($this->_containsGroupAlias) { - $value = $this->referenceContentsByAlias($this->_containsGroupAlias); - $this->_containsGroupAlias = false; - } - - - // Adding string or numeric key to the innermost level or $this->arr. - if (is_string($key) && $key == '<<') { - if (!is_array ($_arr)) { $_arr = array (); } - - $_arr = array_merge ($_arr, $value); - } else if ($key || $key === '' || $key === '0') { - if (!is_array ($_arr)) - $_arr = array ($key=>$value); - else - $_arr[$key] = $value; - } else { - if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } - else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } - } - - $reverse_path = array_reverse($this->path); - $reverse_history = array_reverse ($history); - $reverse_history[0] = $_arr; - $cnt = count($reverse_history) - 1; - for ($i = 0; $i < $cnt; $i++) { - $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; - } - $this->result = $reverse_history[$cnt]; - - $this->path[$incoming_indent] = $key; - - if ($this->_containsGroupAnchor) { - $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; - if (is_array ($value)) { - $k = key ($value); - if (!is_int ($k)) { - $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; - } - } - $this->_containsGroupAnchor = false; - } - - } - - private static function startsLiteralBlock ($line) { - $lastChar = substr (trim($line), -1); - if ($lastChar != '>' && $lastChar != '|') return false; - if ($lastChar == '|') return $lastChar; - // HTML tags should not be counted as literal blocks. - if (preg_match ('#<.*?>$#', $line)) return false; - return $lastChar; - } - - private static function greedilyNeedNextLine($line) { - $line = trim ($line); - if (!strlen($line)) return false; - if (substr ($line, -1, 1) == ']') return false; - if ($line[0] == '[') return true; - if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; - return false; - } - - private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { - $line = self::stripIndent($line, $indent); - if ($literalBlockStyle !== '|') { - $line = self::stripIndent($line); - } - $line = rtrim ($line, "\r\n\t ") . "\n"; - if ($literalBlockStyle == '|') { - return $literalBlock . $line; - } - if (strlen($line) == 0) - return rtrim($literalBlock, ' ') . "\n"; - if ($line == "\n" && $literalBlockStyle == '>') { - return rtrim ($literalBlock, " \t") . "\n"; - } - if ($line != "\n") - $line = trim ($line, "\r\n ") . " "; - return $literalBlock . $line; - } - - function revertLiteralPlaceHolder ($lineArray, $literalBlock) { - foreach ($lineArray as $k => $_) { - if (is_array($_)) - $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); - else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) - $lineArray[$k] = rtrim ($literalBlock, " \r\n"); - } - return $lineArray; - } - - private static function stripIndent ($line, $indent = -1) { - if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); - return substr ($line, $indent); - } - - private function getParentPathByIndent ($indent) { - if ($indent == 0) return array(); - $linePath = $this->path; - do { - end($linePath); $lastIndentInParentPath = key($linePath); - if ($indent <= $lastIndentInParentPath) array_pop ($linePath); - } while ($indent <= $lastIndentInParentPath); - return $linePath; - } - - - private function clearBiggerPathValues ($indent) { - - - if ($indent == 0) $this->path = array(); - if (empty ($this->path)) return true; - - foreach ($this->path as $k => $_) { - if ($k > $indent) unset ($this->path[$k]); - } - - return true; - } - - - private static function isComment ($line) { - if (!$line) return false; - if ($line[0] == '#') return true; - if (trim($line, " \r\n\t") == '---') return true; - return false; - } - - private static function isEmpty ($line) { - return (trim ($line) === ''); - } - - - private function isArrayElement ($line) { - if (!$line || !is_scalar($line)) return false; - if (substr($line, 0, 2) != '- ') return false; - if (strlen ($line) > 3) - if (substr($line,0,3) == '---') return false; - - return true; - } - - private function isHashElement ($line) { - return strpos($line, ':'); - } - - private function isLiteral ($line) { - if ($this->isArrayElement($line)) return false; - if ($this->isHashElement($line)) return false; - return true; - } - - - private static function unquote ($value) { - if (!$value) return $value; - if (!is_string($value)) return $value; - if ($value[0] == '\'') return trim ($value, '\''); - if ($value[0] == '"') return trim ($value, '"'); - return $value; - } - - private function startsMappedSequence ($line) { - return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':'); - } - - private function returnMappedSequence ($line) { - $array = array(); - $key = self::unquote(trim(substr($line,1,-1))); - $array[$key] = array(); - $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); - return array($array); - } - - private function checkKeysInValue($value) { - if (strchr('[{"\'', $value[0]) === false) { - if (strchr($value, ': ') !== false) { - throw new Exception('Too many keys: '.$value); - } - } - } - - private function returnMappedValue ($line) { - $this->checkKeysInValue($line); - $array = array(); - $key = self::unquote (trim(substr($line,0,-1))); - $array[$key] = ''; - return $array; - } - - private function startsMappedValue ($line) { - return (substr ($line, -1, 1) == ':'); - } - - private function isPlainArray ($line) { - return ($line[0] == '[' && substr ($line, -1, 1) == ']'); - } - - private function returnPlainArray ($line) { - return $this->_toType($line); - } - - private function returnKeyValuePair ($line) { - $array = array(); - $key = ''; - if (strpos ($line, ': ')) { - // It's a key/value pair most likely - // If the key is in double quotes pull it out - if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { - $value = trim(str_replace($matches[1],'',$line)); - $key = $matches[2]; - } else { - // Do some guesswork as to the key and the value - $explode = explode(': ', $line); - $key = trim(array_shift($explode)); - $value = trim(implode(': ', $explode)); - $this->checkKeysInValue($value); - } - // Set the type of the value. Int, string, etc - $value = $this->_toType($value); - if ($key === '0') $key = '__!YAMLZero'; - $array[$key] = $value; - } else { - if ((string)(int)$line === (string)$line) { - $array = array((int)$line); - } else { - $array = array ($line); - } - } - return $array; - - } - - - private function returnArrayElement ($line) { - if (strlen($line) <= 1) return array(array()); // Weird %) - $array = array(); - $value = trim(substr($line,1)); - $value = $this->_toType($value); - if ($this->isArrayElement($value)) { - $value = $this->returnArrayElement($value); - } - $array[] = $value; - return $array; - } - - - private function nodeContainsGroup ($line) { - $symbolsForReference = 'A-z0-9_\-'; - if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) - if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; - if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; - if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; - if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; - if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; - return false; - - } - - private function addGroup ($line, $group) { - if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); - if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); - //print_r ($this->path); - } - - private function stripGroup ($line, $group) { - $line = trim(str_replace($group, '', $line)); - return $line; - } -} - -// Enable use of Spyc from command line -// The syntax is the following: php Spyc.php spyc.yaml - -do { - if (PHP_SAPI != 'cli') break; - if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; - if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; - $file = $argv[1]; - echo json_encode (spyc_load_file ($file)); -} while (0); diff --git a/Resources/Private/Partials/File/DirtyPropertiesList.html b/Resources/Private/Partials/File/DirtyPropertiesList.html index 51cd73cd7..33a9130da 100755 --- a/Resources/Private/Partials/File/DirtyPropertiesList.html +++ b/Resources/Private/Partials/File/DirtyPropertiesList.html @@ -2,15 +2,74 @@ xmlns:publish="http://typo3.org/ns/In2code/In2publishCore/ViewHelpers" data-namespace-typo3-fluid="true" > - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
  • {reason}
  • +
    +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + {stagingLevel}Props diff --git a/Resources/Private/Partials/File/FileList.html b/Resources/Private/Partials/File/FileList.html index c876049bf..4fa18de54 100755 --- a/Resources/Private/Partials/File/FileList.html +++ b/Resources/Private/Partials/File/FileList.html @@ -52,7 +52,7 @@ class="btn btn-default" data-bs-toggle="tooltip" title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:view')}" - href="{publish:File.BuildResourcePath(stagingLevel: 'local', publicUrl: record.localProps.publicUrl)}" + href="{record.localProps.publicUrl}" > @@ -110,15 +110,34 @@ - - - - + + + + + + + + + + + + + + + + @@ -152,7 +171,7 @@ class="btn btn-default" data-bs-toggle="tooltip" title="{f:translate(key: 'LLL:EXT:core/Resources/Private/Language/locallang_common.xlf:view')}" - href="{publish:File.BuildResourcePath(stagingLevel: 'foreign', publicUrl: record.foreignProps.publicUrl)}" + href="{record.foreignProps.publicUrl}" > diff --git a/Resources/Private/Partials/File/FolderList.html b/Resources/Private/Partials/File/FolderList.html index f42f3c395..83520e43c 100755 --- a/Resources/Private/Partials/File/FolderList.html +++ b/Resources/Private/Partials/File/FolderList.html @@ -16,28 +16,35 @@ Left - + - + + + + - + Right - + diff --git a/Resources/Private/Partials/File/FunctionBar.html b/Resources/Private/Partials/File/FunctionBar.html index a90c0db0b..d5a892ddf 100755 --- a/Resources/Private/Partials/File/FunctionBar.html +++ b/Resources/Private/Partials/File/FunctionBar.html @@ -3,84 +3,86 @@ xmlns:publish="http://typo3.org/ns/In2code/In2publishCore/ViewHelpers" data-namespace-typo3-fluid="true" > -
-
- - - -
- + +
+
+ + + +
+ +
+ +

+ +

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
- -

- -

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-
+ diff --git a/Resources/Private/Partials/Footer.html b/Resources/Private/Partials/Footer.html index 22d43d957..278a7ba9a 100755 --- a/Resources/Private/Partials/Footer.html +++ b/Resources/Private/Partials/Footer.html @@ -1,6 +1,9 @@ - + diff --git a/Resources/Private/Partials/Header.html b/Resources/Private/Partials/Header.html new file mode 100644 index 000000000..0a848d631 --- /dev/null +++ b/Resources/Private/Partials/Header.html @@ -0,0 +1,8 @@ + +

+ + - {moduleName} +

+ diff --git a/Resources/Private/Partials/LoadingOverlay.html b/Resources/Private/Partials/LoadingOverlay.html deleted file mode 100755 index d36e0eef2..000000000 --- a/Resources/Private/Partials/LoadingOverlay.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
-
-
-
-
-
-
-
diff --git a/Resources/Private/Partials/Record/FunctionBar.html b/Resources/Private/Partials/Record/FunctionBar.html index a6240ee49..3ef3eec8e 100755 --- a/Resources/Private/Partials/Record/FunctionBar.html +++ b/Resources/Private/Partials/Record/FunctionBar.html @@ -4,41 +4,43 @@ > ns is removed on reformat code.]]> -
- - Filter buttons - - -
- - - - - - - - - - - - -
-
-
+ +
+ + Filter buttons + + +
+ + + + + + + + + + + + +
+
+
+
diff --git a/Resources/Private/Partials/Record/Publishing/PublishButton.html b/Resources/Private/Partials/Record/Publishing/PublishButton.html index 68dcdd520..189c0dc33 100644 --- a/Resources/Private/Partials/Record/Publishing/PublishButton.html +++ b/Resources/Private/Partials/Record/Publishing/PublishButton.html @@ -1,8 +1,9 @@ diff --git a/Resources/Private/Partials/Record/Publishing/PublishButtonWithWarning.html b/Resources/Private/Partials/Record/Publishing/PublishButtonWithWarning.html index bda50d2f7..d7412a6b7 100644 --- a/Resources/Private/Partials/Record/Publishing/PublishButtonWithWarning.html +++ b/Resources/Private/Partials/Record/Publishing/PublishButtonWithWarning.html @@ -1,10 +1,13 @@ + + + diff --git a/Resources/Private/Partials/Redirect/Filter.html b/Resources/Private/Partials/Redirect/Filter.html index 3c48c4ee9..d5f4231ea 100644 --- a/Resources/Private/Partials/Redirect/Filter.html +++ b/Resources/Private/Partials/Redirect/Filter.html @@ -3,73 +3,75 @@ data-namespace-typo3-fluid="true" > -
- - -
-
- - -
-
- - -
-
- - -
-
- - +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + -
-
- + class="form-select" + prependOptionLabel="{f:translate(key: 'LLL:EXT:redirects/Resources/Private/Language/locallang_module_redirect.xlf:filter.source_host.showAll')}" + prependOptionValue="" + /> +
+
+ +
diff --git a/Resources/Private/Partials/Tools/CompareDatabaseTool/Details.html b/Resources/Private/Partials/Tools/CompareDatabaseTool/Details.html index ecc999e2a..f25a1cb2b 100644 --- a/Resources/Private/Partials/Tools/CompareDatabaseTool/Details.html +++ b/Resources/Private/Partials/Tools/CompareDatabaseTool/Details.html @@ -13,12 +13,12 @@

- +

- +

diff --git a/Resources/Private/Partials/Tools/CompareDatabaseTool/TableDetails.html b/Resources/Private/Partials/Tools/CompareDatabaseTool/TableDetails.html index b84df19be..558b7b23c 100644 --- a/Resources/Private/Partials/Tools/CompareDatabaseTool/TableDetails.html +++ b/Resources/Private/Partials/Tools/CompareDatabaseTool/TableDetails.html @@ -3,78 +3,83 @@ xmlns:publish="http://typo3.org/ns/In2code/In2publishCore/ViewHelpers" data-namespace-typo3-fluid="true" > - - - - - - - +
+ +
UIDPID - -
+ + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - -
UIDPID - + - -
{value.local.uid}{value.local.pid} - - - - - - - - - -
{field} - -
-
- - - -
{value.uid}{value.pid} - - - - - -
+ + + + + + + + + + + + + + + + {value.local.uid} + {value.local.pid} + + + + + + + + + + + +
{field}
+ + + + + + + +
+ + + {value.uid} + {value.pid} + + + + + + + + + + +
+
+ + + +
diff --git a/Resources/Private/Partials/Tools/CompareDatabaseTool/TransferNotice.html b/Resources/Private/Partials/Tools/CompareDatabaseTool/TransferNotice.html index 7327ecb53..ee71ebe42 100644 --- a/Resources/Private/Partials/Tools/CompareDatabaseTool/TransferNotice.html +++ b/Resources/Private/Partials/Tools/CompareDatabaseTool/TransferNotice.html @@ -1,20 +1,10 @@ -
-
-
- - - - -
-
-

- -

-
- -
-
-
-
+ + + diff --git a/Resources/Private/Partials/Tools/GetHelp.html b/Resources/Private/Partials/Tools/GetHelp.html new file mode 100644 index 000000000..5b1036a00 --- /dev/null +++ b/Resources/Private/Partials/Tools/GetHelp.html @@ -0,0 +1,22 @@ + +
+
+
+

+ +

+
+
+
+
    + +
  • + {support} +
  • +
    +
+
+
+ diff --git a/Resources/Private/Partials/Tools/IntroductionMenu.html b/Resources/Private/Partials/Tools/IntroductionMenu.html index 5a25a0f41..3b6772485 100644 --- a/Resources/Private/Partials/Tools/IntroductionMenu.html +++ b/Resources/Private/Partials/Tools/IntroductionMenu.html @@ -1,12 +1,23 @@ -
    - -
  • - - {entry.name} - :{entry.description} - -
  • -
    -
+
+
+
+

+ +

+
+
+
+
    + +
  • + + + : + +
  • +
    +
+
+
diff --git a/Resources/Private/Partials/Tools/SysInfoDecode.html b/Resources/Private/Partials/Tools/SysInfoDecode.html deleted file mode 100644 index 47bd47f15..000000000 --- a/Resources/Private/Partials/Tools/SysInfoDecode.html +++ /dev/null @@ -1,9 +0,0 @@ - - -
- - -
- -
- diff --git a/Resources/Private/Sass/LoadingOverlay.scss b/Resources/Private/Sass/LoadingOverlay.scss new file mode 100644 index 000000000..f83ffa7b4 --- /dev/null +++ b/Resources/Private/Sass/LoadingOverlay.scss @@ -0,0 +1,64 @@ +@import "compass"; +@import "Settings"; + +.in2publish-loading-overlay { + background-color: $scorpion; + height: 100%; + position: fixed; + z-index: 100000; + text-align: center; + opacity: 0.3; + top: 0; + left: 0; + right: 0; + bottom: 0; + filter: blur(0); +} + +.in2publish-loading-overlay--hidden { + display: none; +} + +.in2publish-loading-overlay--spinner { + position: relative; + top: 30%; + margin: 100px auto; + width: 50px; + height: 30px; + text-align: center; + font-size: 10px; + + > div { + background-color: $white; + height: 100%; + width: 6px; + display: inline-block; + + -webkit-animation: stretchdelay 1.2s infinite ease-in-out; + animation: stretchdelay 1.2s infinite ease-in-out; + } + + .rect2 { + -webkit-animation-delay: -1.1s; + animation-delay: -1.1s; + } + + .rect3 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; + } + + .rect4 { + -webkit-animation-delay: -0.9s; + animation-delay: -0.9s; + } + + .rect5 { + -webkit-animation-delay: -0.8s; + animation-delay: -0.8s; + } +} + +body:has(.in2publish-loading-overlay--active) > *:not(.in2publish-loading-overlay) { + filter: blur(3px); +} diff --git a/Resources/Private/Sass/_ModulesGeneral.scss b/Resources/Private/Sass/_ModulesGeneral.scss index 79764e145..b7a416435 100644 --- a/Resources/Private/Sass/_ModulesGeneral.scss +++ b/Resources/Private/Sass/_ModulesGeneral.scss @@ -53,6 +53,7 @@ div.typo3-fullDoc { .in2publish-backend { font-size: 15px; color: $white; + padding-top: 1em; h1, h2, @@ -465,63 +466,6 @@ a.in2publish-notextdecoration { } } -.in2publish-preloader { - background-color: $scorpion; - height: 100%; - position: fixed; - z-index: 100000; - text-align: center; - opacity: 0.9; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -.in2publish-preloader--hidden { - display: none; -} - -.in2publish-preloader--spinner { - position: relative; - top: 30%; - margin: 100px auto; - width: 50px; - height: 30px; - text-align: center; - font-size: 10px; - - > div { - background-color: $white; - height: 100%; - width: 6px; - display: inline-block; - - -webkit-animation: stretchdelay 1.2s infinite ease-in-out; - animation: stretchdelay 1.2s infinite ease-in-out; - } - - .rect2 { - -webkit-animation-delay: -1.1s; - animation-delay: -1.1s; - } - - .rect3 { - -webkit-animation-delay: -1.0s; - animation-delay: -1.0s; - } - - .rect4 { - -webkit-animation-delay: -0.9s; - animation-delay: -0.9s; - } - - .rect5 { - -webkit-animation-delay: -0.8s; - animation-delay: -0.8s; - } -} - div#typo3-docbody.stopScrolling { transform: none; } @@ -661,13 +605,25 @@ div#typo3-docbody.stopScrolling { border-bottom-right-radius: 2px } -// Fix buttons in admin tools module - -.tx_in2publishcore_admintools .module-docheader-bar-buttons .btn-toolbar .btn-group { - flex-wrap: wrap; +.tx_in2publishcore_admintools { + .card-body { + .extbase-debugger { + margin: 0; + } + } + // Fix buttons in admin tools module + .module-docheader-bar-buttons { + .btn-toolbar { + .btn-group { + flex-wrap: wrap; + } + } + } } + + .in2publish-state-icon { &:before { font-size: 6px; @@ -705,26 +661,31 @@ div#typo3-docbody.stopScrolling { } } -.in2publish-badge { +.badge.in2publish-badge { &--unchanged { - color: #000!important; - background-color: $nobel!important; + color: #000; + background-color: $nobel; + color: white; } &--changed { background-color: $goldentainoi; + color: black; } &--moved-and-changed { background-color: $goldentainoi; + color: black; } &--added { background-color: $apple; + color: white; } &--deleted { background-color: $appleblossom; + color: white; } &--moved { @@ -768,3 +729,14 @@ div#typo3-docbody.stopScrolling { width: 50%; } } + +.in2publish_core_m1 { + .module-docheader-bar-column-left { + .form-group { + display: flex; + .form-select { + margin-left: 1em; + } + } + } +} diff --git a/Resources/Private/Templates/CompareDatabaseTool/Compare.html b/Resources/Private/Templates/CompareDatabaseTool/Compare.html index 83b9e7a60..a44197f6b 100644 --- a/Resources/Private/Templates/CompareDatabaseTool/Compare.html +++ b/Resources/Private/Templates/CompareDatabaseTool/Compare.html @@ -1,5 +1,9 @@ - + + + + + @@ -8,7 +12,7 @@ -

+

diff --git a/Resources/Private/Templates/CompareDatabaseTool/Index.html b/Resources/Private/Templates/CompareDatabaseTool/Index.html index 4e68901b6..f16fedc08 100644 --- a/Resources/Private/Templates/CompareDatabaseTool/Index.html +++ b/Resources/Private/Templates/CompareDatabaseTool/Index.html @@ -1,27 +1,41 @@ - + + + + + - -
-

-
-
-
- - -
+ + +
+
+
+

+ +

-
-
-
- +
+
+
+ + +
+
+
-
+
diff --git a/Resources/Private/Templates/Letterbox/Index.html b/Resources/Private/Templates/Letterbox/Index.html index ae72c17bf..7a2844a23 100644 --- a/Resources/Private/Templates/Letterbox/Index.html +++ b/Resources/Private/Templates/Letterbox/Index.html @@ -1,16 +1,33 @@ - + + + + + - - - - - - - -

-
-
+
+
+
+ + +

+ +

+
+ +

+
+
+
+ +
+
diff --git a/Resources/Private/Templates/Log/Filter.html b/Resources/Private/Templates/Log/Filter.html deleted file mode 100644 index 9d901fa49..000000000 --- a/Resources/Private/Templates/Log/Filter.html +++ /dev/null @@ -1,13 +0,0 @@ - - - Changes: - * Render our Layout "Default" - - - - - - - - - diff --git a/Resources/Private/Templates/RecordInspector/Index.html b/Resources/Private/Templates/RecordInspector/Index.html index 3a1c9d903..c4afa0aa7 100644 --- a/Resources/Private/Templates/RecordInspector/Index.html +++ b/Resources/Private/Templates/RecordInspector/Index.html @@ -1,33 +1,45 @@ - + + + + + - -
-
-
-
- - -
+
+
+
+

+ +

-
-
- +
+
+
+
+ + +
+
+
-
-
-
-
-
+
-
+
diff --git a/Resources/Private/Templates/RecordInspector/Inspect.html b/Resources/Private/Templates/RecordInspector/Inspect.html index 2b5143fed..d1461824d 100644 --- a/Resources/Private/Templates/RecordInspector/Inspect.html +++ b/Resources/Private/Templates/RecordInspector/Inspect.html @@ -1,11 +1,25 @@ - + + + + + - - {recordTree} + });"> +
+ +
+ {recordTree} +
+ + + diff --git a/Resources/Private/Templates/Redirect/List.html b/Resources/Private/Templates/Redirect/List.html index 4a0263164..5bee90836 100644 --- a/Resources/Private/Templates/Redirect/List.html +++ b/Resources/Private/Templates/Redirect/List.html @@ -1,23 +1,24 @@ -

TYPO3 Content Publisher - publish redirects

- - - +
+ +
- - +
+
+ @@ -26,8 +27,8 @@

- - + + @@ -91,9 +92,9 @@

- - - + + + @@ -102,9 +103,10 @@

- - -
{redirect.id}
+ + + +
diff --git a/Resources/Private/Templates/Registry/Index.html b/Resources/Private/Templates/Registry/Index.html index 724320d47..424eae0f6 100644 --- a/Resources/Private/Templates/Registry/Index.html +++ b/Resources/Private/Templates/Registry/Index.html @@ -1,13 +1,24 @@ - + - -

- -

+ + + - - - + +
+
+
+

+ +

+
+ +
+
diff --git a/Resources/Private/Templates/ShowConfiguration/Index.html b/Resources/Private/Templates/ShowConfiguration/Index.html index e315c576a..786543a18 100755 --- a/Resources/Private/Templates/ShowConfiguration/Index.html +++ b/Resources/Private/Templates/ShowConfiguration/Index.html @@ -1,26 +1,93 @@ - + - -

Publish Content

- -

global config

- - {globalConfig} - -

personal config

+ + + - {fullConfig} + + + +
+
+
+

+ +

+
+
+
+ {globalConfig} +
+
-

module.m4.container_dump

+ + +
+
+
+

+ +

+
+
+
+ {fullConfig} +
+
- -
- - + + +
+
+
+

+ +

+
+
+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+
+ {containerDump} +
+
- +
+ - {containerDump} + + diff --git a/Resources/Private/Templates/SystemInformationExport/SysInfoDecode.html b/Resources/Private/Templates/SystemInformationExport/SysInfoDecode.html index f0f936717..aec0174e4 100644 --- a/Resources/Private/Templates/SystemInformationExport/SysInfoDecode.html +++ b/Resources/Private/Templates/SystemInformationExport/SysInfoDecode.html @@ -1,10 +1,32 @@ - + + + + + - - - {info} - +
+
+ +
+
+ + +
+
+
+
+ +
+
+
+ + {info} + +
+
diff --git a/Resources/Private/Templates/SystemInformationExport/SysInfoIndex.html b/Resources/Private/Templates/SystemInformationExport/SysInfoIndex.html index 61cb9f130..e85ebd171 100644 --- a/Resources/Private/Templates/SystemInformationExport/SysInfoIndex.html +++ b/Resources/Private/Templates/SystemInformationExport/SysInfoIndex.html @@ -1,25 +1,55 @@ - - + + + + + + - - system_info.show - - - system_info.download - - - system_info.decode - - -
- - +
+
+
+

+ + + + +

+

+ + + + +

+

+ + + + +

+
+
+
+
+ +
+
+ + +
+
+ +
+
+
+
- - +
diff --git a/Resources/Private/Templates/SystemInformationExport/SysInfoShow.html b/Resources/Private/Templates/SystemInformationExport/SysInfoShow.html index 56ad7a286..bcb253b77 100644 --- a/Resources/Private/Templates/SystemInformationExport/SysInfoShow.html +++ b/Resources/Private/Templates/SystemInformationExport/SysInfoShow.html @@ -1,11 +1,27 @@ - + + + + + -
- - +
+
+
+

+ JSON +

+
+
+
+ +
+
+
+
+ {info} +
- {info} diff --git a/Resources/Private/Templates/Tca/Index.html b/Resources/Private/Templates/Tca/Index.html index 049c984ae..9a7a64a7b 100755 --- a/Resources/Private/Templates/Tca/Index.html +++ b/Resources/Private/Templates/Tca/Index.html @@ -1,39 +1,54 @@ - + - -

- Publish Content - - - TCA -

+ + + + -

- Compatible TCA -

- - {compatibleTca} +
+
+
+

+ +

+
+
+
+ {compatibleTca} +
+
-

- Inompatible TCA -

- {incompatibleTca} +
+
+
+

+ +

+
+
+
+ {incompatibleTca} +
+
- - + diff --git a/Resources/Private/Templates/Test/Index.html b/Resources/Private/Templates/Test/Index.html index 07415d1fc..cb76218a5 100755 --- a/Resources/Private/Templates/Test/Index.html +++ b/Resources/Private/Templates/Test/Index.html @@ -1,43 +1,21 @@ - + - -

- Publish Content - - - Tools -

- -
- -
-
-
- - - - - - - - - - - + + + - -
-
-
{testResult.translatedLabel}
-
-

- {testResult.translatedMessages} -

-
-
-
+ +
+
+
+ + + {testResult.translatedMessages} + +
- +
diff --git a/Resources/Private/Templates/Tools/Index.html b/Resources/Private/Templates/Tools/Index.html index f487c4fc5..cc55fa5ce 100755 --- a/Resources/Private/Templates/Tools/Index.html +++ b/Resources/Private/Templates/Tools/Index.html @@ -1,30 +1,16 @@ - + - - -

Getting Help

- -
    - -
  • - {support} -
  • -
    -
-
- - -

- Publish Content - - - Tools -

- -

- admin tools -

+ + + - + +
+ + + + +
diff --git a/Resources/Public/Css/LoadingOverlay.css b/Resources/Public/Css/LoadingOverlay.css new file mode 100644 index 000000000..e79366992 --- /dev/null +++ b/Resources/Public/Css/LoadingOverlay.css @@ -0,0 +1 @@ +.in2publish-loading-overlay{background-color:#585858;height:100%;position:fixed;z-index:100000;text-align:center;opacity:0.3;top:0;left:0;right:0;bottom:0;filter:blur(0)}.in2publish-loading-overlay--hidden{display:none}.in2publish-loading-overlay--spinner{position:relative;top:30%;margin:100px auto;width:50px;height:30px;text-align:center;font-size:10px}.in2publish-loading-overlay--spinner>div{background-color:#fff;height:100%;width:6px;display:inline-block;-webkit-animation:stretchdelay 1.2s infinite ease-in-out;animation:stretchdelay 1.2s infinite ease-in-out}.in2publish-loading-overlay--spinner .rect2{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.in2publish-loading-overlay--spinner .rect3{-webkit-animation-delay:-1.0s;animation-delay:-1.0s}.in2publish-loading-overlay--spinner .rect4{-webkit-animation-delay:-0.9s;animation-delay:-0.9s}.in2publish-loading-overlay--spinner .rect5{-webkit-animation-delay:-0.8s;animation-delay:-0.8s}body:has(.in2publish-loading-overlay--active)>*:not(.in2publish-loading-overlay){filter:blur(3px)} diff --git a/Resources/Public/Css/Modules.css b/Resources/Public/Css/Modules.css index b9757b534..f873f1f07 100644 --- a/Resources/Public/Css/Modules.css +++ b/Resources/Public/Css/Modules.css @@ -1 +1 @@ -.in2publish-icon-info{position:absolute;top:1px;right:25px}.in2publish-icon-loop-alt4:before{font-size:90px !important;color:#5D9A46;position:absolute;top:25px;left:-45px}.in2publish-icon-loop-alt4.in2publish-warning:before{color:#FFCA4B}.in2publish-icon-loop-alt4.in2publish-connection-error:before{color:#A94442}.in2publish-icon-folder{margin:0 15px !important;position:relative;top:3px}.in2publish-icon-file{margin:0 15px;position:relative;top:3px}.in2publish-link-publish{position:absolute;right:0;top:1px}.in2publish-link-publish:hover{text-decoration:none}.in2publish-stagelisting__item__publish--blocked .in2publish-link-publish .in2publish-icon-publish{right:1px;cursor:not-allowed}.in2publish-stagelisting__item__publish--blocked .in2publish-link-publish .in2publish-icon-publish:before{content:"\e60a"}.in2publish-stagelisting__item--removed .in2publish-link-publish .in2publish-icon-publish:hover{color:#5D9A46}@font-face{font-family:'in2publish';src:url("../Fonts/in2publish.eot?abcdef");src:url("../Fonts/in2publish.eot?#iefixabcdef") format("embedded-opentype"),url("../Fonts/in2publish.woff?abcdef") format("woff"),url("../Fonts/in2publish.ttf?abcdef") format("truetype"),url("../Fonts/in2publish.svg?abcdef#in2publish") format("svg");font-weight:normal;font-style:normal}[class^="in2publish-icon-"]:before,[class*=" in2publish-icon-"]:before,.in2publish-messages--removeable>.typo3-message:before{font-family:'in2publish';speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;font-size:20px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-circle-o:before,.in2publish-icon-circle-o:before{content:"\f10c"}.icon-circle:before,.in2publish-icon-circle:before{content:"\f111"}.icon-blocked:before,.in2publish-icon-blocked:before{content:"\e60a"}.icon-x-altx-alt:before,.in2publish-icon-x-altx-alt:before{content:"\e609"}.icon-folder-download:before,.in2publish-icon-folder-download:before,.in2publish-stagelisting__item--moved .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e608"}.icon-history:before,.in2publish-icon-history:before{content:"\e603"}.icon-info:before,.in2publish-icon-info:before{content:"\e601"}.icon-loop-alt4:before,.in2publish-icon-loop-alt4:before{content:"\e600"}.icon-folder:before,.in2publish-icon-folder:before{content:"\e92f"}.icon-folder-open:before,.in2publish-icon-folder-open:before,.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column .in2publish-icon-folder:before,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e930"}.icon-folder-plus:before,.in2publish-icon-folder-plus:before,.in2publish-stagelisting__item--added .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e931"}.icon-folder-minus:before,.in2publish-icon-folder-minus:before,.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e932"}.icon-eye:before,.in2publish-icon-eye:before{content:"\e9ce"}.icon-arrow-right:before,.in2publish-link-publish .in2publish-icon-publish:before{content:"\ea34"}.icon-file-broken:before,.in2publish-icon-file-delete:before,.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item__column .in2publish-icon-file:before,.in2publish-stagelisting__item--removed .in2publish-stagelisting__item__column .in2publish-icon-folder:before,.in2publish-stagelisting__item--removed .in2publish-stagelisting__item__column .in2publish-icon-file:before{content:"\e604"}.icon-file-add:before,.in2publish-icon-file-add:before,.in2publish-stagelisting__item--added .in2publish-stagelisting__item__column .in2publish-icon-file:before{content:"\e605"}.icon-file-settings:before,.in2publish-icon-file-changed:before,.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column .in2publish-icon-file:before,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column .in2publish-icon-file:before{content:"\e606"}.icon-align-justify:before,.in2publish-icon-file:before{content:"\e607"}.icon-clock2:before{content:"\e60b"}.icon-edit:before,.in2publish-icon-edit:before{content:"\e602"}.nav-pills-container{background:white;padding:10px}div.typo3-fullDoc{height:auto}.in2publish-hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.in2publish-clearfix{zoom:1}.in2publish-clearfix:after{clear:both}.in2publish-clearfix:before,.in2publish-clearfix:after{content:"";display:table}.in2publish-icon-small:before,.in2publish-icon-history:before,.in2publish-icon-info:before,.in2publish-icon-eye:before,.in2publish-icon-edit:before,.in2publish-link-publish .in2publish-icon-publish:before{font-size:16px}.in2publish-unstyledlist{margin:0;padding:0;list-style-type:none}[data-action]{cursor:pointer}.in2publish-inline-block{display:inline-block}.in2publish-backend{font-size:15px;color:#fff}.in2publish-backend h1,.in2publish-backend h2,.in2publish-backend h3,.in2publish-backend h4,.in2publish-backend legend{color:#fff}.in2publish-backend select{color:#444}#typo3-docheader .in2publish-backend-select.module-docheader-bar{overflow:initial}.in2publish-backend *,.in2publish-backend *:before,.in2publish-backend *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.in2publish-backend ul{margin:0;padding:0}.in2publish-module>section{margin:15px 0}.in2publish-module>section>h3{margin-bottom:10px}.in2publish-module{background:#585858;margin:-48px -24px;padding-top:24px;min-height:calc(100vh - 41px)}legend{display:block;width:100%;padding:0;margin-bottom:18px;font-size:inherit;line-height:inherit;color:currentColor;border-style:none}label{font-weight:normal}a.in2publish-notextdecoration{text-decoration:none}.in2publish-main{margin-top:40px;overflow:hidden}.in2publish-container{overflow:hidden;margin:0 0 30px}.in2publish-container-50{position:relative;width:50%;float:left;text-align:center}.in2publish-container-50 img{width:90px;height:auto}.in2publish-stagelisting__item__column{background-color:#666;width:50%;float:left;height:30px;line-height:30px;position:relative;top:0;left:0}.in2publish-stagelisting__item__column a{color:#fff}.in2publish-stagelisting__item--added .in2publish-stagelisting__item__column{background-color:#3B7826}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item__column{background-color:#A94442}.in2publish-stagelisting__item--removed .in2publish-stagelisting__item__column{background-color:#000}.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column{background-color:#FFCA4B;color:#585858}.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column a,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column a{color:#585858}.in2publish-stagelisting__item--moved .in2publish-stagelisting__item__column{background-color:#425ea1}.in2publish-list-level--1 .in2publish-icon-info{right:37px}.in2publish-list-level--1 .in2publish-link-publish{right:12px}.in2publish-list-level--1 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:27px}.in2publish-list-level--2 .in2publish-icon-info{right:49px}.in2publish-list-level--2 .in2publish-link-publish{right:24px}.in2publish-list-level--2 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:39px}.in2publish-list-level--3 .in2publish-icon-info{right:61px}.in2publish-list-level--3 .in2publish-link-publish{right:36px}.in2publish-list-level--3 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:51px}.in2publish-list-level--4 .in2publish-icon-info{right:73px}.in2publish-list-level--4 .in2publish-link-publish{right:48px}.in2publish-list-level--4 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:63px}.in2publish-list-level--5 .in2publish-icon-info{right:85px}.in2publish-list-level--5 .in2publish-link-publish{right:60px}.in2publish-list-level--5 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:75px}.in2publish-backend .in2publish-list-level{margin:0 0 0 25px}.in2publish-backend .in2publish-list-level>li{border-top:1px solid #585858}.in2publish-stagelisting__dropdown{overflow:hidden;background-color:#DBDBDB;color:#585858;border:10px solid #666}.in2publish-stagelisting__dropdown--open{margin-bottom:10px}.in2publish-stagelisting__dropdown--close{display:none}.in2publish-stagelisting__dropdown__item{width:50%;float:left;line-height:30px;padding:0}.in2publish-stagelisting__dropdown__item--full{width:100%}.in2publish-stagelisting__dropdown__item h3{line-height:30px}.in2publish-stagelisting__dropdown a{color:currentColor}.in2publish-stagelisting__dropdown h3{background-color:#585858;font-weight:normal;font-size:1em;padding:0 15px;margin-top:0}.in2publish-stagelisting__dropdown h4{background-color:#666;font-style:italic;padding:3px 15px}.in2publish-stagelisting__dropdown li{list-style-type:none;padding:0 0 0 15px;word-break:break-all}.in2publish-stagelisting__dropdown span.in2publish-link-publish,.in2publish-stagelisting__dropdown [class^="in2publish-icon-"],.in2publish-stagelisting__dropdown [class*=" in2publish-icon-"]{padding:0 10px 0 0}.in2publish-stagelisting__item--added .in2publish-stagelisting__dropdown{border-color:#3B7826}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__dropdown{border-color:#A94442}.in2publish-stagelisting__item--changed .in2publish-stagelisting__dropdown{border-color:#FFCA4B}.in2publish-stagelisting__item--moved .in2publish-stagelisting__dropdown{border-color:#425ea1}.in2publish-backend .in2publish-stagelisting__dropdown__item__list>li{border-style:none;position:relative}.in2publish-stagelisting__dropdown__actions{padding:0 15px;background-color:#A0A0A0;color:#fff}.in2publish-stagelisting__dropdown__actions>a{margin-right:20px}.in2publish-stagelisting__dropdown__actions>a:last-child{margin-right:0}.in2publish-stagelisting__item--added .in2publish-stagelisting__dropdown__actions{background-color:#5D9A46}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__dropdown__actions{background-color:#C15955}.in2publish-stagelisting__item--changed .in2publish-stagelisting__dropdown__actions{background-color:#FFDB88;color:currentColor}.in2publish-stagelisting__item--moved .in2publish-stagelisting__dropdown__actions{background-color:#557AD1;color:#fff}.in2publish-stagelisting__dropdown__page{background-color:#fff}.in2publish-backend .in2publish-related__title{font-style:normal;line-height:30px;padding:0 15px;margin:0;background-color:#FFDB88;color:#444}.in2publish-backend .in2publish-related__list{margin:12px 0 15px}.in2publish-backend .in2publish-related__list li{font-size:14px}.in2publish-backend .in2publish-stagelisting__dropdown__item__list{margin-bottom:12px}.in2publish-footer{margin:20px 0;color:#A0A0A0;font-size:80%}.in2publish-footer .in2publish-logo{float:right;width:160px;height:48px}.in2publish-functions-bar{background-color:#666;margin:20px 0;text-align:center;position:relative;padding:15px;min-height:73px}.in2publish-functions-bar__filter{position:absolute;right:20px;top:22px}.in2publish-functions-bar__filter i:before{font-size:30px}.in2publish-functions-bar__filter .in2publish-functions-bar__filter__link{display:inline-block;margin:0 0 0 10px;color:#fff}.in2publish-functions-bar__filter .in2publish-functions-bar__filter__link:hover{text-decoration:none}.in2publish-functions-bar__filter .in2publish-functions-bar--active{border-bottom:2px solid #fff}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-changed{border-color:#FFCA4B}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-added{border-color:#5D9A46}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-deleted{border-color:#A94442}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-moved{border-color:#557AD1}.in2publish-preloader{background-color:#585858;height:100%;position:fixed;z-index:100000;text-align:center;opacity:0.9;top:0;left:0;right:0;bottom:0}.in2publish-preloader--hidden{display:none}.in2publish-preloader--spinner{position:relative;top:30%;margin:100px auto;width:50px;height:30px;text-align:center;font-size:10px}.in2publish-preloader--spinner>div{background-color:#fff;height:100%;width:6px;display:inline-block;-webkit-animation:stretchdelay 1.2s infinite ease-in-out;animation:stretchdelay 1.2s infinite ease-in-out}.in2publish-preloader--spinner .rect2{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.in2publish-preloader--spinner .rect3{-webkit-animation-delay:-1.0s;animation-delay:-1.0s}.in2publish-preloader--spinner .rect4{-webkit-animation-delay:-0.9s;animation-delay:-0.9s}.in2publish-preloader--spinner .rect5{-webkit-animation-delay:-0.8s;animation-delay:-0.8s}div#typo3-docbody.stopScrolling{transform:none}@-webkit-keyframes stretchdelay{0%, 40%, 100%{-webkit-transform:scaleY(0.4)}20%{-webkit-transform:scaleY(1)}}@keyframes stretchdelay{0%, 40%, 100%{transform:scaleY(0.4);-webkit-transform:scaleY(0.4)}20%{transform:scaleY(1);-webkit-transform:scaleY(1)}}.in2publish-removevalue:before{right:4px;bottom:4px}.pagination .t3-icon{margin:0}.pagination .paginator-input{display:inline-block;width:auto;margin:-6px 0;height:26px;padding:4px 4px;font-size:11px;line-height:1.5;border-radius:2px}.pagination-block{display:block}.pagination{display:inline-block;padding-left:0;margin:18px 0;border-radius:2px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 6px;margin-left:-1px;line-height:1.5;color:#212424;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#000;background-color:#f5f5f5;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#0078e6;border-color:#0078e6}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#d7d7d7;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:12px 12px;font-size:15px;line-height:1.33333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 4px;font-size:11px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.tx_in2publishcore_admintools .module-docheader-bar-buttons .btn-toolbar .btn-group{flex-wrap:wrap}.in2publish-state-icon:before{font-size:6px;margin-bottom:2px;display:inline-block;vertical-align:middle;height:12px;line-height:12px}.in2publish-state--unchanged{color:#9C9C9C}.in2publish-state--changed{color:#FFCA4B}.in2publish-state--moved-and-changed{color:#FFCA4B}.in2publish-state--added{color:#5D9A46}.in2publish-state--deleted{color:#A94442}.in2publish-state--moved{color:#557AD1}.in2publish-badge--unchanged{color:#000 !important;background-color:#9C9C9C !important}.in2publish-badge--changed{background-color:#FFCA4B}.in2publish-badge--moved-and-changed{background-color:#FFCA4B}.in2publish-badge--added{background-color:#5D9A46}.in2publish-badge--deleted{background-color:#A94442}.in2publish-badge--moved{background-color:#557AD1}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item-filename{color:#A94442}.in2publish-icon-toggle__on{display:none}.in2publish-icon-toggle__off{display:initial}.in2publish-icon-toggle--active .in2publish-icon-toggle__on{display:initial}.in2publish-icon-toggle--active .in2publish-icon-toggle__off{display:none}.in2publish-table .col-filename{width:50%} +.in2publish-icon-info{position:absolute;top:1px;right:25px}.in2publish-icon-loop-alt4:before{font-size:90px !important;color:#5D9A46;position:absolute;top:25px;left:-45px}.in2publish-icon-loop-alt4.in2publish-warning:before{color:#FFCA4B}.in2publish-icon-loop-alt4.in2publish-connection-error:before{color:#A94442}.in2publish-icon-folder{margin:0 15px !important;position:relative;top:3px}.in2publish-icon-file{margin:0 15px;position:relative;top:3px}.in2publish-link-publish{position:absolute;right:0;top:1px}.in2publish-link-publish:hover{text-decoration:none}.in2publish-stagelisting__item__publish--blocked .in2publish-link-publish .in2publish-icon-publish{right:1px;cursor:not-allowed}.in2publish-stagelisting__item__publish--blocked .in2publish-link-publish .in2publish-icon-publish:before{content:"\e60a"}.in2publish-stagelisting__item--removed .in2publish-link-publish .in2publish-icon-publish:hover{color:#5D9A46}@font-face{font-family:'in2publish';src:url("../Fonts/in2publish.eot?abcdef");src:url("../Fonts/in2publish.eot?#iefixabcdef") format("embedded-opentype"),url("../Fonts/in2publish.woff?abcdef") format("woff"),url("../Fonts/in2publish.ttf?abcdef") format("truetype"),url("../Fonts/in2publish.svg?abcdef#in2publish") format("svg");font-weight:normal;font-style:normal}[class^="in2publish-icon-"]:before,[class*=" in2publish-icon-"]:before,.in2publish-messages--removeable>.typo3-message:before{font-family:'in2publish';speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;font-size:20px;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-circle-o:before,.in2publish-icon-circle-o:before{content:"\f10c"}.icon-circle:before,.in2publish-icon-circle:before{content:"\f111"}.icon-blocked:before,.in2publish-icon-blocked:before{content:"\e60a"}.icon-x-altx-alt:before,.in2publish-icon-x-altx-alt:before{content:"\e609"}.icon-folder-download:before,.in2publish-icon-folder-download:before,.in2publish-stagelisting__item--moved .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e608"}.icon-history:before,.in2publish-icon-history:before{content:"\e603"}.icon-info:before,.in2publish-icon-info:before{content:"\e601"}.icon-loop-alt4:before,.in2publish-icon-loop-alt4:before{content:"\e600"}.icon-folder:before,.in2publish-icon-folder:before{content:"\e92f"}.icon-folder-open:before,.in2publish-icon-folder-open:before,.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column .in2publish-icon-folder:before,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e930"}.icon-folder-plus:before,.in2publish-icon-folder-plus:before,.in2publish-stagelisting__item--added .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e931"}.icon-folder-minus:before,.in2publish-icon-folder-minus:before,.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item__column .in2publish-icon-folder:before{content:"\e932"}.icon-eye:before,.in2publish-icon-eye:before{content:"\e9ce"}.icon-arrow-right:before,.in2publish-link-publish .in2publish-icon-publish:before{content:"\ea34"}.icon-file-broken:before,.in2publish-icon-file-delete:before,.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item__column .in2publish-icon-file:before,.in2publish-stagelisting__item--removed .in2publish-stagelisting__item__column .in2publish-icon-folder:before,.in2publish-stagelisting__item--removed .in2publish-stagelisting__item__column .in2publish-icon-file:before{content:"\e604"}.icon-file-add:before,.in2publish-icon-file-add:before,.in2publish-stagelisting__item--added .in2publish-stagelisting__item__column .in2publish-icon-file:before{content:"\e605"}.icon-file-settings:before,.in2publish-icon-file-changed:before,.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column .in2publish-icon-file:before,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column .in2publish-icon-file:before{content:"\e606"}.icon-align-justify:before,.in2publish-icon-file:before{content:"\e607"}.icon-clock2:before{content:"\e60b"}.icon-edit:before,.in2publish-icon-edit:before{content:"\e602"}.nav-pills-container{background:white;padding:10px}div.typo3-fullDoc{height:auto}.in2publish-hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.in2publish-clearfix{zoom:1}.in2publish-clearfix:after{clear:both}.in2publish-clearfix:before,.in2publish-clearfix:after{content:"";display:table}.in2publish-icon-small:before,.in2publish-icon-history:before,.in2publish-icon-info:before,.in2publish-icon-eye:before,.in2publish-icon-edit:before,.in2publish-link-publish .in2publish-icon-publish:before{font-size:16px}.in2publish-unstyledlist{margin:0;padding:0;list-style-type:none}[data-action]{cursor:pointer}.in2publish-inline-block{display:inline-block}.in2publish-backend{font-size:15px;color:#fff;padding-top:1em}.in2publish-backend h1,.in2publish-backend h2,.in2publish-backend h3,.in2publish-backend h4,.in2publish-backend legend{color:#fff}.in2publish-backend select{color:#444}#typo3-docheader .in2publish-backend-select.module-docheader-bar{overflow:initial}.in2publish-backend *,.in2publish-backend *:before,.in2publish-backend *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.in2publish-backend ul{margin:0;padding:0}.in2publish-module>section{margin:15px 0}.in2publish-module>section>h3{margin-bottom:10px}.in2publish-module{background:#585858;margin:-48px -24px;padding-top:24px;min-height:calc(100vh - 41px)}legend{display:block;width:100%;padding:0;margin-bottom:18px;font-size:inherit;line-height:inherit;color:currentColor;border-style:none}label{font-weight:normal}a.in2publish-notextdecoration{text-decoration:none}.in2publish-main{margin-top:40px;overflow:hidden}.in2publish-container{overflow:hidden;margin:0 0 30px}.in2publish-container-50{position:relative;width:50%;float:left;text-align:center}.in2publish-container-50 img{width:90px;height:auto}.in2publish-stagelisting__item__column{background-color:#666;width:50%;float:left;height:30px;line-height:30px;position:relative;top:0;left:0}.in2publish-stagelisting__item__column a{color:#fff}.in2publish-stagelisting__item--added .in2publish-stagelisting__item__column{background-color:#3B7826}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item__column{background-color:#A94442}.in2publish-stagelisting__item--removed .in2publish-stagelisting__item__column{background-color:#000}.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column{background-color:#FFCA4B;color:#585858}.in2publish-stagelisting__item--moved-and-changed .in2publish-stagelisting__item__column a,.in2publish-stagelisting__item--changed .in2publish-stagelisting__item__column a{color:#585858}.in2publish-stagelisting__item--moved .in2publish-stagelisting__item__column{background-color:#425ea1}.in2publish-list-level--1 .in2publish-icon-info{right:37px}.in2publish-list-level--1 .in2publish-link-publish{right:12px}.in2publish-list-level--1 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:27px}.in2publish-list-level--2 .in2publish-icon-info{right:49px}.in2publish-list-level--2 .in2publish-link-publish{right:24px}.in2publish-list-level--2 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:39px}.in2publish-list-level--3 .in2publish-icon-info{right:61px}.in2publish-list-level--3 .in2publish-link-publish{right:36px}.in2publish-list-level--3 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:51px}.in2publish-list-level--4 .in2publish-icon-info{right:73px}.in2publish-list-level--4 .in2publish-link-publish{right:48px}.in2publish-list-level--4 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:63px}.in2publish-list-level--5 .in2publish-icon-info{right:85px}.in2publish-list-level--5 .in2publish-link-publish{right:60px}.in2publish-list-level--5 .in2publish-stagelisting__item__column--right .in2publish-icon-folder{margin-left:75px}.in2publish-backend .in2publish-list-level{margin:0 0 0 25px}.in2publish-backend .in2publish-list-level>li{border-top:1px solid #585858}.in2publish-stagelisting__dropdown{overflow:hidden;background-color:#DBDBDB;color:#585858;border:10px solid #666}.in2publish-stagelisting__dropdown--open{margin-bottom:10px}.in2publish-stagelisting__dropdown--close{display:none}.in2publish-stagelisting__dropdown__item{width:50%;float:left;line-height:30px;padding:0}.in2publish-stagelisting__dropdown__item--full{width:100%}.in2publish-stagelisting__dropdown__item h3{line-height:30px}.in2publish-stagelisting__dropdown a{color:currentColor}.in2publish-stagelisting__dropdown h3{background-color:#585858;font-weight:normal;font-size:1em;padding:0 15px;margin-top:0}.in2publish-stagelisting__dropdown h4{background-color:#666;font-style:italic;padding:3px 15px}.in2publish-stagelisting__dropdown li{list-style-type:none;padding:0 0 0 15px;word-break:break-all}.in2publish-stagelisting__dropdown span.in2publish-link-publish,.in2publish-stagelisting__dropdown [class^="in2publish-icon-"],.in2publish-stagelisting__dropdown [class*=" in2publish-icon-"]{padding:0 10px 0 0}.in2publish-stagelisting__item--added .in2publish-stagelisting__dropdown{border-color:#3B7826}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__dropdown{border-color:#A94442}.in2publish-stagelisting__item--changed .in2publish-stagelisting__dropdown{border-color:#FFCA4B}.in2publish-stagelisting__item--moved .in2publish-stagelisting__dropdown{border-color:#425ea1}.in2publish-backend .in2publish-stagelisting__dropdown__item__list>li{border-style:none;position:relative}.in2publish-stagelisting__dropdown__actions{padding:0 15px;background-color:#A0A0A0;color:#fff}.in2publish-stagelisting__dropdown__actions>a{margin-right:20px}.in2publish-stagelisting__dropdown__actions>a:last-child{margin-right:0}.in2publish-stagelisting__item--added .in2publish-stagelisting__dropdown__actions{background-color:#5D9A46}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__dropdown__actions{background-color:#C15955}.in2publish-stagelisting__item--changed .in2publish-stagelisting__dropdown__actions{background-color:#FFDB88;color:currentColor}.in2publish-stagelisting__item--moved .in2publish-stagelisting__dropdown__actions{background-color:#557AD1;color:#fff}.in2publish-stagelisting__dropdown__page{background-color:#fff}.in2publish-backend .in2publish-related__title{font-style:normal;line-height:30px;padding:0 15px;margin:0;background-color:#FFDB88;color:#444}.in2publish-backend .in2publish-related__list{margin:12px 0 15px}.in2publish-backend .in2publish-related__list li{font-size:14px}.in2publish-backend .in2publish-stagelisting__dropdown__item__list{margin-bottom:12px}.in2publish-footer{margin:20px 0;color:#A0A0A0;font-size:80%}.in2publish-footer .in2publish-logo{float:right;width:160px;height:48px}.in2publish-functions-bar{background-color:#666;margin:20px 0;text-align:center;position:relative;padding:15px;min-height:73px}.in2publish-functions-bar__filter{position:absolute;right:20px;top:22px}.in2publish-functions-bar__filter i:before{font-size:30px}.in2publish-functions-bar__filter .in2publish-functions-bar__filter__link{display:inline-block;margin:0 0 0 10px;color:#fff}.in2publish-functions-bar__filter .in2publish-functions-bar__filter__link:hover{text-decoration:none}.in2publish-functions-bar__filter .in2publish-functions-bar--active{border-bottom:2px solid #fff}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-changed{border-color:#FFCA4B}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-added{border-color:#5D9A46}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-deleted{border-color:#A94442}.in2publish-functions-bar__filter .in2publish-functions-bar--active.in2publish-icon-status-moved{border-color:#557AD1}div#typo3-docbody.stopScrolling{transform:none}@-webkit-keyframes stretchdelay{0%, 40%, 100%{-webkit-transform:scaleY(0.4)}20%{-webkit-transform:scaleY(1)}}@keyframes stretchdelay{0%, 40%, 100%{transform:scaleY(0.4);-webkit-transform:scaleY(0.4)}20%{transform:scaleY(1);-webkit-transform:scaleY(1)}}.in2publish-removevalue:before{right:4px;bottom:4px}.pagination .t3-icon{margin:0}.pagination .paginator-input{display:inline-block;width:auto;margin:-6px 0;height:26px;padding:4px 4px;font-size:11px;line-height:1.5;border-radius:2px}.pagination-block{display:block}.pagination{display:inline-block;padding-left:0;margin:18px 0;border-radius:2px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 6px;margin-left:-1px;line-height:1.5;color:#212424;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#000;background-color:#f5f5f5;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#0078e6;border-color:#0078e6}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#d7d7d7;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:12px 12px;font-size:15px;line-height:1.33333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.pagination-sm>li>a,.pagination-sm>li>span{padding:4px 4px;font-size:11px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:2px;border-bottom-left-radius:2px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:2px;border-bottom-right-radius:2px}.tx_in2publishcore_admintools .card-body .extbase-debugger{margin:0}.tx_in2publishcore_admintools .module-docheader-bar-buttons .btn-toolbar .btn-group{flex-wrap:wrap}.in2publish-state-icon:before{font-size:6px;margin-bottom:2px;display:inline-block;vertical-align:middle;height:12px;line-height:12px}.in2publish-state--unchanged{color:#9C9C9C}.in2publish-state--changed{color:#FFCA4B}.in2publish-state--moved-and-changed{color:#FFCA4B}.in2publish-state--added{color:#5D9A46}.in2publish-state--deleted{color:#A94442}.in2publish-state--moved{color:#557AD1}.badge.in2publish-badge--unchanged{color:#000;background-color:#9C9C9C;color:white}.badge.in2publish-badge--changed{background-color:#FFCA4B;color:black}.badge.in2publish-badge--moved-and-changed{background-color:#FFCA4B;color:black}.badge.in2publish-badge--added{background-color:#5D9A46;color:white}.badge.in2publish-badge--deleted{background-color:#A94442;color:white}.badge.in2publish-badge--moved{background-color:#557AD1}.in2publish-stagelisting__item--deleted .in2publish-stagelisting__item-filename{color:#A94442}.in2publish-icon-toggle__on{display:none}.in2publish-icon-toggle__off{display:initial}.in2publish-icon-toggle--active .in2publish-icon-toggle__on{display:initial}.in2publish-icon-toggle--active .in2publish-icon-toggle__off{display:none}.in2publish-table .col-filename{width:50%}.in2publish_core_m1 .module-docheader-bar-column-left .form-group{display:flex}.in2publish_core_m1 .module-docheader-bar-column-left .form-group .form-select{margin-left:1em} diff --git a/Resources/Public/JavaScript/BackendModule.js b/Resources/Public/JavaScript/BackendModule.js index 05756c655..778cf45b8 100644 --- a/Resources/Public/JavaScript/BackendModule.js +++ b/Resources/Public/JavaScript/BackendModule.js @@ -1,8 +1,13 @@ 'use strict'; define([ - 'jquery', 'TYPO3/CMS/Core/Event/DebounceEvent', 'TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/Backend/Input/Clearable' -], function ($, DebounceEvent, Modal) { + 'jquery', + 'TYPO3/CMS/Core/Event/DebounceEvent', + 'TYPO3/CMS/Backend/Modal', + 'TYPO3/CMS/Backend/Input/Clearable', + 'TYPO3/CMS/In2publishCore/LoadingOverlay', + 'TYPO3/CMS/In2publishCore/ConfirmationModal', +], function ($, DebounceEvent, Modal, LoadingOverlay, ConfirmationModal) { var In2publishCoreModule = { isPublishFilesModule: (document.querySelector('.module[data-module-name="file_In2publishCoreM3"]') !== null), unchangedFilter: false, @@ -10,12 +15,6 @@ define([ addedFilter: false, deletedFilter: false, movedFilter: false, - publisherBag: [], - objects: { - body: undefined, - preLoader: undefined, - typo3DocBody: undefined - }, }; In2publishCoreModule.initialize = function () { @@ -24,14 +23,10 @@ define([ In2publishCoreModule.filterItemsByStatus(); In2publishCoreModule.setupFilterListeners(); In2publishCoreModule.setupClearableInputs(); - In2publishCoreModule.setupPublishListeners(); } else { In2publishCoreModule.setFilterForPageView(); In2publishCoreModule.filterButtonsListener(); - In2publishCoreModule.setupPublishListeners(); } - In2publishCoreModule.overlayListener(); - In2publishCoreModule.ajaxUriListener(); }; In2publishCoreModule.filterItemsByStatus = function () { @@ -147,86 +142,6 @@ define([ }); }; - In2publishCoreModule.overlayListener = function () { - document.querySelectorAll('[data-in2publish-confirm]').forEach(element => { - element.addEventListener('click', In2publishCoreModule.overlayHandler, true) - }) - }; - - /** - * @param {Event} event - */ - In2publishCoreModule.overlayHandler = function (event) { - /** - * @type {HTMLAnchorElement} - */ - const target = event.currentTarget - if ( - target.dataset['in2publishConfirm'] - && ( - target.classList.contains('in2publish-stagelisting__item__publish--blocked') - || !confirm(target.dataset['in2publishConfirm']) - ) - ) { - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); - return; - } - if ('TRUE' === target.dataset['in2publishOverlay']) { - In2publishCoreModule.showPreloader(); - } - } - - In2publishCoreModule.showPreloader = function () { - In2publishCoreModule.objects.preLoader.removeClass('in2publish-preloader--hidden'); - In2publishCoreModule.objects.typo3DocBody.addClass('stopScrolling'); - }; - - In2publishCoreModule.ajaxUriListener = function () { - $('*[data-action-ajax-uri]').click(function (e) { - var $this = $(this); - var uri = $this.data('action-ajax-uri'); - if ('href' === uri) { - uri = $this.prop('href'); - e.preventDefault(); - } - var once = true === $this.data('action-ajax-once'); - var container = $this.data('action-ajax-result'); - var filled = false; - if (undefined !== container) { - var $container = $(container); - filled = true === $container.data('container-filled'); - } - - if (!once || !filled) { - $.ajax({ - url: uri, - beforeSend: function () { - In2publishCoreModule.showPreloader(); - }, - complete: function () { - In2publishCoreModule.hidePreLoader(); - }, - success: function (data) { - if (data && undefined !== container) { - $container.html(data); - $container.data('container-filled', true); - } - In2publishCoreModule.openOrCloseStageListingDropdownContainer( - $container.find('.in2publish-stagelisting__dropdown') - ); - } - }); - } - }); - }; - - In2publishCoreModule.hidePreLoader = function () { - In2publishCoreModule.objects.preLoader.addClass('in2publish-preloader--hidden'); - In2publishCoreModule.objects.typo3DocBody.removeClass('stopScrolling'); - }; - In2publishCoreModule.setupFilterListeners = function () { const filters = document.querySelectorAll('.js-in2publish-filter'); @@ -284,97 +199,6 @@ define([ } } - In2publishCoreModule.setupPublishListeners = function () { - document.querySelectorAll('.js-publish-trigger').forEach( - element => element.addEventListener( - 'click', - In2publishCoreModule.publishFileEventListener, - {capture: true} - ) - ) - }; - - In2publishCoreModule.publishFileEventListener = function (event) { - event.preventDefault(); - const target = event.currentTarget; - const type = target.dataset.type; - const severity = parseInt(target.dataset.severity || '0'); - let actionButtonClass = 'btn-default'; - let modalTitle = ''; - let modalContent = ''; - let modalButtonAbort = ''; - let modalButtonPublish = ''; - switch(severity) { - /* - * TYPO3.Severity.error = 2 - * TYPO3.Severity.warning = 1 - * TYPO3.Severity.ok = 0 - * TYPO3.Severity.info = -1 - * TYPO3.Severity.notice = -2 - */ - case -2: - case -1: - actionButtonClass = 'btn-info'; - break; - case 0: - actionButtonClass = 'btn-success'; - break; - case 1: - actionButtonClass = 'btn-warning'; - break; - case 2: - actionButtonClass = 'btn-danger'; - break; - } - if (TYPO3.lang['tx_in2publishcore.modal.publish.' + type + '.text'] !== undefined) { - modalTitle = TYPO3.lang['tx_in2publishcore.modal.publish.title']; - modalContent = TYPO3.lang['tx_in2publishcore.modal.publish.' + type + '.text'].replace('$name$', target.dataset.name); - modalButtonAbort = TYPO3.lang['tx_in2publishcore.action.abort']; - modalButtonPublish = TYPO3.lang['tx_in2publishcore.actions.publish']; - } else { - modalTitle = target.dataset.modalTitle; - modalContent = target.dataset.modalText + '\n' + target.dataset.modalReasons; - modalButtonAbort = target.dataset.modalButtonAbortCaption; - modalButtonPublish = target.dataset.modalButtonPublishCaption; - } - const configuration = { - title: modalTitle, - content: modalContent, - severity: severity, - buttons: [ - { - text: modalButtonAbort, - btnClass: 'btn btn-default', - name: 'abort', - active: true, - trigger: function () { - Modal.currentModal.trigger('modal-dismiss'); - In2publishCoreModule.hidePreLoader(); - } - }, - { - text: modalButtonPublish, - btnClass: 'btn ' + actionButtonClass, - name: 'publish', - trigger: () => { - Modal.currentModal.trigger('modal-dismiss'); - if (target.classList.contains('js-publish-overlay')) { - In2publishCoreModule.showPreloader(); - } - target.removeEventListener( - 'click', - In2publishCoreModule.publishFileEventListener, - {capture: true} - ); - target.dispatchEvent(event); - window.location = target.href; - } - } - ] - }; - Modal.advanced(configuration); - } - In2publishCoreModule.setupClearableInputs = function () { (Array.from(document.querySelectorAll('.t3js-clearable'))).forEach(function (input) { input.clearable(); @@ -382,9 +206,6 @@ define([ }; $(function () { - In2publishCoreModule.objects.body = $('body'); - In2publishCoreModule.objects.preLoader = $('.in2publish-preloader'); - In2publishCoreModule.objects.typo3DocBody = $('#typo3-docbody'); In2publishCoreModule.initialize(); }); diff --git a/Resources/Public/JavaScript/ConfirmationModal.js b/Resources/Public/JavaScript/ConfirmationModal.js new file mode 100644 index 000000000..458768370 --- /dev/null +++ b/Resources/Public/JavaScript/ConfirmationModal.js @@ -0,0 +1,149 @@ +'use strict' + +define(['TYPO3/CMS/Backend/Modal', 'TYPO3/CMS/In2publishCore/LoadingOverlay'], function (Modal, LoadingOverlay) { + 'use strict' + + class ConfirmationModal { + + easyModalMethod = this.handleClickEasyModal.bind(this) + advancedModalMethod = this.handleClickAdvancedModal.bind(this) + + constructor() { + document.querySelectorAll('.js-in2publish-confirmation-modal').forEach(element => this.registerClickHandler(element)) + } + + /** + * Register a click handler which will show the loading-overlay when the element is clicked. + * Use only for elements that will reload the page or disable the loading-overlay yourself. + * + * If you want to register an element automatically on page load, give the element the class js-loading-overlay. + * This method can be used to register the handler for elements loaded via fetch. + * + * @param {HTMLElement} node + */ + registerClickHandler(node) { + if (node.dataset.easyModalTitle) { + node.addEventListener('click', this.easyModalMethod, {capture: true}) + } else if (node.dataset.modalConfiguration) { + node.addEventListener('click', this.advancedModalMethod, {capture: true}) + } + } + + /** + * @param {Event} event + */ + handleClickEasyModal(event) { + event.preventDefault() + const target = event.currentTarget + const severity = parseInt(target.dataset.easyModalSeverity ?? 0) + + Modal.confirm( + target.dataset.easyModalTitle, + target.dataset.easyModalContent ?? '', + severity, + this.defaultEasyModalButtons(event, target, severity), + ) + } + + defaultEasyModalButtons(event, target, severity) { + let actionButtonClass = '' + switch (severity) { + /* + * TYPO3.Severity.error = 2 + * TYPO3.Severity.warning = 1 + * TYPO3.Severity.ok = 0 + * TYPO3.Severity.info = -1 + * TYPO3.Severity.notice = -2 + */ + case -2: + case -1: + actionButtonClass = 'btn-info' + break + case 0: + actionButtonClass = 'btn-success' + break + case 1: + actionButtonClass = 'btn-warning' + break + case 2: + actionButtonClass = 'btn-danger' + break + } + return [ + { + text: TYPO3.lang['tx_in2publishcore.action.abort'], + btnClass: 'btn-default', + name: 'abort', + active: true, + trigger: this.abortClick.bind(this) + }, + { + text: TYPO3.lang['tx_in2publishcore.actions.publish'], + btnClass: actionButtonClass, + name: 'publish', + trigger: () => this.confirmClick(event, target, this.easyModalMethod) + } + ] + } + + + /** + * @param {Event} event + */ + handleClickAdvancedModal(event) { + event.preventDefault() + + const target = event.currentTarget + const modalConfiguration = JSON.parse(target.dataset.modalConfiguration) + + let finalModalConfiguration = modalConfiguration.settings + finalModalConfiguration.buttons = [] + if (modalConfiguration.buttons.abort) { + let abortButton = modalConfiguration.buttons.abort + abortButton.trigger = this.abortClick.bind(this) + finalModalConfiguration.buttons.push(abortButton) + } + if (modalConfiguration.buttons.confirm) { + let confirmButton = modalConfiguration.buttons.confirm + confirmButton.trigger = () => this.confirmClick(event, target, this.advancedModalMethod) + finalModalConfiguration.buttons.push(confirmButton) + } + + Modal.advanced(finalModalConfiguration) + } + + abortClick() { + this.closeModal() + LoadingOverlay.hideOverlay() + } + + confirmClick(event, target, previousHandler) { + target.removeEventListener('click', previousHandler, {capture: true}) + LoadingOverlay.showOverlay() + this.closeModal() + if (target instanceof HTMLAnchorElement) { + target.dispatchEvent(event) + window.location = target.href + } else if (target instanceof HTMLButtonElement) { + if (target.getAttribute("type") === "submit") { + if (target.hasAttribute("form")) { + const targetForm = target.getAttribute("form") + document.getElementById(targetForm).submit() + } else { + target.dispatchEvent(event) + } + } + } + } + + closeModal() { + if (typeof Modal.currentModal.hideModal === "function") { + Modal.currentModal.hideModal() + } else { + Modal.currentModal.trigger('modal-dismiss') + } + } + } + + return new ConfirmationModal() +}) diff --git a/Resources/Public/JavaScript/LoadingOverlay.js b/Resources/Public/JavaScript/LoadingOverlay.js new file mode 100644 index 000000000..4e9fe2d0f --- /dev/null +++ b/Resources/Public/JavaScript/LoadingOverlay.js @@ -0,0 +1,62 @@ +'use strict' + +define([], function () { + 'use strict' + + class LoadingOverlay { + + eventListenerMethod = this.handleClick.bind(this) + /** + * @var {NodeListOf} + */ + overlays + + constructor() { + this.registerOverlays() + document.querySelectorAll('.js-in2publish-loading-overlay').forEach(element => this.registerClickHandler(element)) + } + + /** + * @param {NodeListOf} nodes + */ + registerOverlays(nodes = null) { + this.overlays = nodes ?? document.querySelectorAll('.in2publish-loading-overlay') + } + + /** + * Register a click handler which will show the loading-overlay + * when the element is clicked. Use only for elements that will + * reload the page or disable the loading-overlay yourself. + * + * If you want to register an element automatically on page load, + * give the element the class js-in2publish-loading-overlay. This + * method can be used to register the handler for elements loaded + * via fetch. + * + * @param {HTMLElement} node + */ + registerClickHandler(node) { + node.addEventListener('click', this.eventListenerMethod) + } + + handleClick() { + this.showOverlay() + } + + showOverlay() { + this.overlays.forEach(element => { + element.classList.remove('in2publish-loading-overlay--hidden') + element.classList.add('in2publish-loading-overlay--active') + }) + } + + hideOverlay() { + this.overlays.forEach(element => { + element.classList.add('in2publish-loading-overlay--hidden') + element.classList.remove('in2publish-loading-overlay--active') + }) + } + } + + return new LoadingOverlay() +}) diff --git a/Resources/Public/JavaScript/context-menu-actions.js b/Resources/Public/JavaScript/context-menu-actions.js new file mode 100644 index 000000000..00bd64f36 --- /dev/null +++ b/Resources/Public/JavaScript/context-menu-actions.js @@ -0,0 +1,35 @@ +import Notification from "@typo3/backend/notification.js"; + +class ContextMenuActions { + static publishRecord(table, uid, element) { + Notification.info("Page " + uid + " is published in the background") + if ("pages" !== table) { + Notification.warning("Can not publish non-page via context menu entry") + return; + } + const publishUrl = element["publishUrl"] + if (!publishUrl) { + Notification.error("Publish URL is not set for this page") + return + } + fetch(publishUrl) + .then(response => { + if (!response.ok) { + throw new Error("Something went wrong"); + } + return response.json() + }) + .then(body => { + console.log(body) + if (body.error) { + Notification.error(body.message) + return + } + Notification.success(body.message) + top.document.dispatchEvent(new CustomEvent("typo3:pagetree:refresh")) + }) + .catch(error => Notification.error(error.message)) + } +} + +export default ContextMenuActions; diff --git a/Tests/Functional/Features/PreventParallelPublishing/Service/RunningRequestServiceTest.php b/Tests/Functional/Features/PreventParallelPublishing/Service/RunningRequestServiceTest.php index c9e3260c2..9561a3f65 100644 --- a/Tests/Functional/Features/PreventParallelPublishing/Service/RunningRequestServiceTest.php +++ b/Tests/Functional/Features/PreventParallelPublishing/Service/RunningRequestServiceTest.php @@ -52,18 +52,18 @@ public function testRecordWithMmRecordCanBeMarkedAsPublishing(): void $query->select('*') ->from('tx_in2publishcore_running_request') ->where( - $query->expr()->orX( - $query->expr()->andX( + $query->expr()->or( + $query->expr()->and( $query->expr()->eq('record_id', 1), $query->expr()->eq('table_name', $query->createNamedParameter('foo')), ), - $query->expr()->andX( + $query->expr()->and( $query->expr()->eq('record_id', $query->createNamedParameter($mmId)), $query->expr()->eq('table_name', $query->createNamedParameter('foo_bar_mm')), ), ), ); - $result = $query->execute(); + $result = $query->executeQuery(); $rows = $result->fetchAllAssociative(); $this->assertCount(2, $rows); } diff --git a/Tests/Unit/Command/Foreign/Status/DbConfigTestCommandTest.php b/Tests/Unit/Command/Foreign/Status/DbConfigTestCommandTest.php index 65def8aa0..dce0266e7 100644 --- a/Tests/Unit/Command/Foreign/Status/DbConfigTestCommandTest.php +++ b/Tests/Unit/Command/Foreign/Status/DbConfigTestCommandTest.php @@ -36,7 +36,7 @@ public function testCommandCanBeExecuted(): void ]); $queryBuilder = $this->createMock(QueryBuilder::class); - $queryBuilder->method('execute')->willReturn($query); + $queryBuilder->method('executeQuery')->willReturn($query); $connection = $this->createMock(Connection::class); $connection->method('createQueryBuilder')->willReturn($queryBuilder); diff --git a/Tests/Unit/Component/Core/Demand/DemandServiceTest.php b/Tests/Unit/Component/Core/Demand/DemandServiceTest.php index abe732535..afdd95157 100644 --- a/Tests/Unit/Component/Core/Demand/DemandServiceTest.php +++ b/Tests/Unit/Component/Core/Demand/DemandServiceTest.php @@ -47,7 +47,7 @@ public function resolve(Demands $demands, Record $record): void ]; $resolverService = $this->createMock(ResolverService::class); - $resolverService->method('getResolversForTable')->willReturn($resolversForTable); + $resolverService->method('getResolversForClassification')->willReturn($resolversForTable); $demandBuilder->injectResolverService($resolverService); $demand = $demandBuilder->buildDemandForRecords(new RecordCollection([$record])); @@ -97,7 +97,7 @@ public function resolve(Demands $demands, Record $record): void ]; $resolverService = $this->createMock(ResolverService::class); - $resolverService->method('getResolversForTable')->willReturnOnConsecutiveCalls( + $resolverService->method('getResolversForClassification')->willReturnOnConsecutiveCalls( $resolversForTableFoo, $resolversForTableBar, ); diff --git a/Tests/Unit/Component/Core/FileHandling/FileDemandResolverTest.php b/Tests/Unit/Component/Core/FileHandling/FileDemandResolverTest.php index 3293123d4..08bab061a 100644 --- a/Tests/Unit/Component/Core/FileHandling/FileDemandResolverTest.php +++ b/Tests/Unit/Component/Core/FileHandling/FileDemandResolverTest.php @@ -6,18 +6,23 @@ use In2code\In2publishCore\Component\Core\Demand\DemandsCollection; use In2code\In2publishCore\Component\Core\Demand\Type\FileDemand; -use In2code\In2publishCore\Component\Core\FileHandling\FileDemandResolver; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FileSystemInfoService; -use In2code\In2publishCore\Component\Core\FileHandling\Service\ForeignFileSystemInfoService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\FileDemandResolver; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FileInfo; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FilesystemInformationCollection; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\ForeignFileInfoService; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Service\LocalFileInfoService; use In2code\In2publishCore\Component\Core\Record\Factory\RecordFactory; use In2code\In2publishCore\Component\Core\Record\Model\FileRecord; use In2code\In2publishCore\Component\Core\RecordCollection; use In2code\In2publishCore\Tests\UnitTestCase; +use function bin2hex; use function hash; +use function random_bytes; +use function sha1; /** - * @coversDefaultClass \In2code\In2publishCore\Component\Core\FileHandling\FileDemandResolver + * @coversDefaultClass \In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\FileDemandResolver */ class FileDemandResolverTest extends UnitTestCase { @@ -27,67 +32,70 @@ class FileDemandResolverTest extends UnitTestCase public function testResolveDemand(): void { $fileDemandResolver = new FileDemandResolver(); - $file1 = new FileRecord(['identifier' => 'file1', 'storage' => 42], []); - $file2 = new FileRecord(['identifier' => 'file2', 'storage' => 42], []); - $filesArray = [ - 42 => [ - 'foo/bar' => [$file1], - '/file.txt' => [$file2], - ], - ]; - $fileInfoArray = $filesArray; - $fileInfoArray[42]['foo/bar'] = [ - 'size' => 123, - 'mimetype' => 'some_mimetype', - 'name' => 'some_name', - 'extension' => 'some_extension', - 'folder_hash' => 'some_folder_hash', - ]; - $fileInfoArray[42]['/file.txt'] = [ - 'size' => 123, - 'mimetype' => 'some_mimetype', - 'name' => 'some_name', - 'extension' => 'some_extension', - 'folder_hash' => 'some_folder_hash', - ]; + $childFile1Info = new FileInfo( + 42, + 'foo/bar', + 'some_name', + sha1(bin2hex(random_bytes(15))), + null, + 123, + 'some_mimetype', + 'some_extension', + 'some_folder_hash', + sha1('foo/bar') + ); + $childFile2Info = new FileInfo( + 42, + '/file.txt', + 'some_name', + sha1(bin2hex(random_bytes(15))), + null, + 123, + 'some_mimetype', + 'some_extension', + 'some_folder_hash', + sha1('foo/bar') + ); + $fileInfoArray = new FilesystemInformationCollection(); + $fileInfoArray->addFilesystemInfo($childFile1Info); + $fileInfoArray->addFilesystemInfo($childFile2Info); - $fileSystemInfoService = $this->createMock(FileSystemInfoService::class); + $fileSystemInfoService = $this->createMock(LocalFileInfoService::class); $fileSystemInfoService->expects($this->once())->method('getFileInfo')->willReturn($fileInfoArray); - $foreignFileSystemInfoService = $this->createMock(ForeignFileSystemInfoService::class); + $foreignFileInfoService = $this->createMock(ForeignFileInfoService::class); + $fileRecordChild1 = new FileRecord($childFile1Info->toArray(), []); + $fileRecordChild2 = new FileRecord($childFile2Info->toArray(), []); $recordFactory = $this->createMock(RecordFactory::class); - $localProps = $fileInfoArray[42]['foo/bar']; - $localProps['storage'] = 42; - $localProps['identifier'] = 'foo/bar'; - $localProps['identifier_hash'] = hash('sha1', 'foo/bar'); - $fileRecordChild1 = new FileRecord($localProps, []); - - $localProps = $fileInfoArray[42]['/file.txt']; - $localProps['storage'] = 42; - $localProps['identifier'] = '/file.txt'; - $localProps['identifier_hash'] = hash('sha1', '/file.txt'); - $fileRecordChild2 = new FileRecord($localProps, []); $recordFactory->method('createFileRecord')->willReturnOnConsecutiveCalls( $fileRecordChild1, $fileRecordChild2, ); - $fileDemandResolver->injectFileSystemInfoService($fileSystemInfoService); - $fileDemandResolver->injectForeignFileSystemInfoService($foreignFileSystemInfoService); + $fileDemandResolver->injectLocalFileInfoService($fileSystemInfoService); + $fileDemandResolver->injectForeignFileInfoService($foreignFileInfoService); $fileDemandResolver->injectRecordFactory($recordFactory); $demands = $this->createMock(DemandsCollection::class); - $demands->method('getDemandsByType')->with(FileDemand::class)->willReturn($filesArray); + $parentFile1 = new FileRecord(['identifier' => 'file1', 'storage' => 42], []); + $parentFile2 = new FileRecord(['identifier' => 'file2', 'storage' => 42], []); + $fileDemand = [ + 42 => [ + 'foo/bar' => [$parentFile1], + '/file.txt' => [$parentFile2], + ], + ]; + $demands->method('getDemandsByType')->with(FileDemand::class)->willReturn($fileDemand); $recordCollection = new RecordCollection(); $fileDemandResolver->resolveDemand($demands, $recordCollection); - $file1Children = $file1->getChildren(); + $file1Children = $parentFile1->getChildren(); $this->assertSame($fileRecordChild1, $file1Children['_file']['42:foo/bar']); - $file2Children = $file2->getChildren(); + $file2Children = $parentFile2->getChildren(); $this->assertSame($fileRecordChild2, $file2Children['_file']['42:/file.txt']); } } diff --git a/Tests/Unit/Component/Core/FileHandling/FileRecordListenerTest.php b/Tests/Unit/Component/Core/FileHandling/FileRecordListenerTest.php index 1140056c2..3f243eefd 100644 --- a/Tests/Unit/Component/Core/FileHandling/FileRecordListenerTest.php +++ b/Tests/Unit/Component/Core/FileHandling/FileRecordListenerTest.php @@ -6,16 +6,16 @@ use In2code\In2publishCore\Component\Core\Demand\DemandsCollection; use In2code\In2publishCore\Component\Core\Demand\DemandsFactory; -use In2code\In2publishCore\Component\Core\FileHandling\FileDemandResolver; -use In2code\In2publishCore\Component\Core\FileHandling\FileRecordListener; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\FileDemandResolver; use In2code\In2publishCore\Component\Core\Record\Model\DatabaseRecord; use In2code\In2publishCore\Component\Core\Record\Model\FileRecord; use In2code\In2publishCore\Event\RecordWasCreated; +use In2code\In2publishCore\Features\ResolveFilesForIndices\EventListener\FileRecordListener; use In2code\In2publishCore\Tests\UnitTestCase; use ReflectionProperty; /** - * @coversDefaultClass \In2code\In2publishCore\Component\Core\FileHandling\FileRecordListener + * @coversDefaultClass \In2code\In2publishCore\Features\ResolveFilesForIndices\EventListener\FileRecordListener */ class FileRecordListenerTest extends UnitTestCase { diff --git a/Tests/Unit/Component/Core/Publisher/Constraint/IsEqualIgnoringRequestToken.php b/Tests/Unit/Component/Core/Publisher/Constraint/IsEqualIgnoringRequestToken.php new file mode 100644 index 000000000..8c7420001 --- /dev/null +++ b/Tests/Unit/Component/Core/Publisher/Constraint/IsEqualIgnoringRequestToken.php @@ -0,0 +1,114 @@ +value = $value; + $this->delta = $delta; + $this->canonicalize = $canonicalize; + $this->ignoreCase = $ignoreCase; + } + + /** + * Evaluates the constraint for parameter $other. + * + * If $returnResult is set to false (the default), an exception is thrown + * in case of a failure. null is returned otherwise. + * + * If $returnResult is true, the result of the evaluation is returned as + * a boolean value instead: true in case of success, false in case of a + * failure. + * + * @throws ExpectationFailedException + */ + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool + { + unset($other[0]['request_token']); + // If $this->value and $other are identical, they are also equal. + // This is the most common path and will allow us to skip + // initialization of all the comparators. + if ($this->value === $other) { + return true; + } + + $comparatorFactory = ComparatorFactory::getInstance(); + + try { + $comparator = $comparatorFactory->getComparatorFor( + $this->value, + $other, + ); + + $comparator->assertEquals( + $this->value, + $other, + $this->delta, + $this->canonicalize, + $this->ignoreCase, + ); + } catch (ComparisonFailure $f) { + if ($returnResult) { + return false; + } + + throw new ExpectationFailedException( + trim($description . "\n" . $f->getMessage()), + $f, + ); + } + + return true; + } + + /** + * Returns a string representation of the constraint. + */ + public function toString(bool $exportObjects = false): string + { + $delta = ''; + + if (is_string($this->value)) { + if (str_contains($this->value, "\n")) { + return 'is equal to '; + } + + return sprintf( + "is equal to '%s'", + $this->value, + ); + } + + if ($this->delta != 0) { + $delta = sprintf( + ' with delta <%F>', + $this->delta, + ); + } + + return sprintf( + 'is equal to %s%s', + Exporter::export($this->value, $exportObjects), + $delta, + ); + } +} diff --git a/Tests/Unit/Component/Core/Publisher/FileRecordPublisherTest.php b/Tests/Unit/Component/Core/Publisher/FileRecordPublisherTest.php index e0b361fd5..305437956 100644 --- a/Tests/Unit/Component/Core/Publisher/FileRecordPublisherTest.php +++ b/Tests/Unit/Component/Core/Publisher/FileRecordPublisherTest.php @@ -4,18 +4,29 @@ namespace In2code\In2publishCore\Tests\Unit\Component\Core\Publisher; -use In2code\In2publishCore\Component\Core\FileHandling\Service\FalDriverService; use In2code\In2publishCore\Component\Core\Publisher\FileRecordPublisher; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\AddFileInstruction; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\DeleteFileInstruction; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\DeleteFolderInstruction; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\MoveFileInstruction; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\ReplaceAndRenameFileInstruction; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\ReplaceFileInstruction; use In2code\In2publishCore\Component\Core\Record\Model\DatabaseRecord; use In2code\In2publishCore\Component\Core\Record\Model\FileRecord; use In2code\In2publishCore\Component\Core\Record\Model\FolderRecord; -use In2code\In2publishCore\Component\Core\Record\Model\Record; +use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandDispatcher; +use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandResponse; use In2code\In2publishCore\Component\TemporaryAssetTransmission\AssetTransmitter; +use In2code\In2publishCore\Tests\Unit\Component\Core\Publisher\Constraint\IsEqualIgnoringRequestToken; use In2code\In2publishCore\Tests\UnitTestCase; -use org\bovigo\vfs\vfsStream; -use ReflectionProperty; use TYPO3\CMS\Core\Database\Connection; -use TYPO3\CMS\Core\Resource\Driver\DriverInterface; + +use function json_encode; +use function sha1; +use function str_replace; +use function strrpos; +use function substr; +use function trim; /** * @coversDefaultClass \In2code\In2publishCore\Component\Core\Publisher\FileRecordPublisher @@ -45,34 +56,32 @@ public function testCanPublishReturnsTrueForFileRecordsOnly() */ public function testPublishDeletesRemovedFile() { - $fileRecordPublisher = new FileRecordPublisher(); - $foreignDatabase = $this->createMock(Connection::class); + $fileRecordPublisher = $this->createFileRecordPublisher(); - $deletedFile = $this->createMock(FileRecord::class); - $deletedFile->method('getClassification')->willReturn('_file'); - $deletedFile->method('getState')->willReturn(Record::S_DELETED); - $deletedFile->method('getForeignProps')->willReturn( - ['storage' => 1, 'identifier' => 'bar', 'identifier_hash' => 'baz'], + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => DeleteFileInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'fileIdentifier' => '/foo/bar', + ]), + ], + ]), ); + $fileRecordPublisher->injectForeignDatabase($foreignDatabase); - $reflectionProperty = new ReflectionProperty($fileRecordPublisher, 'requestToken'); - $reflectionProperty->setAccessible(true); - - $foreignDatabase->expects($this->once())->method('insert')->with( - 'tx_in2publishcore_filepublisher_task', - [ - 'request_token' => $reflectionProperty->getValue($fileRecordPublisher), - 'crdate' => $GLOBALS['EXEC_TIME'], - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'storage_uid' => $deletedFile->getForeignProps()['storage'], - 'identifier' => $deletedFile->getForeignProps()['identifier'], - 'identifier_hash' => $deletedFile->getForeignProps()['identifier_hash'], - 'file_action' => $fileRecordPublisher::A_DELETE, - ], + $deletedFile = new FileRecord( + [], + $this->createFileInfo('bar', 1, '23149872364', '/foo'), ); - $fileRecordPublisher->injectForeignDatabase($foreignDatabase); $fileRecordPublisher->publish($deletedFile); + $fileRecordPublisher->finish(); } /** @@ -80,104 +89,70 @@ public function testPublishDeletesRemovedFile() */ public function testPublishTransmitsAddedRecord() { - $fileRecordPublisher = new FileRecordPublisher(); - $foreignDatabase = $this->createMock(Connection::class); - $falDriverService = $this->createMock(FalDriverService::class); - $mockDriver = $this->createMock(DriverInterface::class); + $fileRecordPublisher = $this->createFileRecordPublisher('/var/tmp/asdfasdf.tmp'); - $structure = [ - 'Api.php' => '', - ]; - $root = vfsStream::setup('root', null, $structure); - $file = $root->url() . '/Api.php'; - $mockDriver->method('getFileForLocalProcessing')->willReturn($file); - $falDriverService->method('getDriver')->willReturn($mockDriver); - $assetTransmitter = $this->createMock(AssetTransmitter::class); - - $addedFile = $this->createMock(FileRecord::class); - $addedFile->method('getId')->willReturn('1:/bar'); - $addedFile->method('getClassification')->willReturn('_file'); - $addedFile->method('getState')->willReturn(Record::S_ADDED); - $addedFile->method('getLocalProps')->willReturn( - ['storage' => 1, 'identifier' => 'bar', 'identifier_hash' => 'baz'], + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => AddFileInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'foreignTemporaryFileIdentifier' => '/var/tmp/asdfasdf.tmp', + 'foreignTargetFileIdentifier' => '/foo/bar', + ]), + ], + ]), ); + $fileRecordPublisher->injectForeignDatabase($foreignDatabase); - $reflectionProperty = new ReflectionProperty($fileRecordPublisher, 'requestToken'); - $reflectionProperty->setAccessible(true); - - $foreignDatabase->expects($this->once())->method('insert')->with( - 'tx_in2publishcore_filepublisher_task', - [ - 'request_token' => $reflectionProperty->getValue($fileRecordPublisher), - 'crdate' => $GLOBALS['EXEC_TIME'], - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'storage_uid' => $addedFile->getLocalProps()['storage'], - 'identifier' => $addedFile->getLocalProps()['identifier'], - 'identifier_hash' => $addedFile->getLocalProps()['identifier_hash'], - 'file_action' => $fileRecordPublisher::A_INSERT, - 'temp_identifier_hash' => '', - ], + $addedFile = new FileRecord( + $this->createFileInfo('bar', 1, '23450978', '/foo'), + [], ); - $fileRecordPublisher->injectForeignDatabase($foreignDatabase); - $fileRecordPublisher->injectFalDriverService($falDriverService); - $fileRecordPublisher->injectAssetTransmitter($assetTransmitter); - $fileRecordPublisher->publish($addedFile); + $fileRecordPublisher->finish(); } /** * @covers ::publish */ - public function testPublishTransmitsChangedRecord() + public function testPublishTransmitsRenamedRecord() { - $fileRecordPublisher = new FileRecordPublisher(); - $foreignDatabase = $this->createMock(Connection::class); - $falDriverService = $this->createMock(FalDriverService::class); - $mockDriver = $this->createMock(DriverInterface::class); + $fileRecordPublisher = $this->createFileRecordPublisher(); - $structure = [ - 'Api.php' => '', - ]; - $root = vfsStream::setup('root', null, $structure); - $file = $root->url() . '/Api.php'; - $mockDriver->method('getFileForLocalProcessing')->willReturn($file); - $falDriverService->method('getDriver')->willReturn($mockDriver); $assetTransmitter = $this->createMock(AssetTransmitter::class); + $fileRecordPublisher->injectAssetTransmitter($assetTransmitter); - $changedFile = $this->createMock(FileRecord::class); - $changedFile->method('getId')->willReturn('1:/bar'); - $changedFile->method('getClassification')->willReturn('_file'); - $changedFile->method('getState')->willReturn(Record::S_CHANGED); - $changedFile->method('getLocalProps')->willReturn( - ['storage' => 1, 'identifier' => 'bar', 'identifier_hash' => 'baz'], - ); - $changedFile->method('getForeignProps')->willReturn( - ['storage' => 2, 'identifier' => 'bar2', 'identifier_hash' => 'baz2'], + $changedFile = new FileRecord( + $this->createFileInfo('bar2', 1, '23149872364', '/foo'), + $this->createFileInfo('bar', 1, '23149872364', '/foo'), ); - $reflectionProperty = new ReflectionProperty($fileRecordPublisher, 'requestToken'); - $reflectionProperty->setAccessible(true); - - $foreignDatabase->expects($this->once())->method('insert')->with( - 'tx_in2publishcore_filepublisher_task', - [ - 'request_token' => $reflectionProperty->getValue($fileRecordPublisher), - 'crdate' => $GLOBALS['EXEC_TIME'], - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'storage_uid' => $changedFile->getLocalProps()['storage'], - 'identifier' => $changedFile->getLocalProps()['identifier'], - 'identifier_hash' => $changedFile->getLocalProps()['identifier_hash'], - 'file_action' => $fileRecordPublisher::A_UPDATE, - 'temp_identifier_hash' => '', - ], + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => MoveFileInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'oldFileIdentifier' => '/foo/bar', + 'newFileIdentifier' => '/foo/bar2', + ]), + ], + ]), ); - $fileRecordPublisher->injectForeignDatabase($foreignDatabase); - $fileRecordPublisher->injectFalDriverService($falDriverService); - $fileRecordPublisher->injectAssetTransmitter($assetTransmitter); $fileRecordPublisher->publish($changedFile); + $fileRecordPublisher->finish(); } /** @@ -185,37 +160,136 @@ public function testPublishTransmitsChangedRecord() */ public function testPublishTransmitsMovedRecord() { - $fileRecordPublisher = new FileRecordPublisher(); + $fileRecordPublisher = $this->createFileRecordPublisher(); + + $movedFile = new FileRecord( + $this->createFileInfo('foo', 1, '452093485', '/foo'), + $this->createFileInfo('foo', 1, '452093485', '/bar'), + ); + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => MoveFileInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'oldFileIdentifier' => '/bar/foo', + 'newFileIdentifier' => '/foo/foo', + ]), + ], + ]), + ); + $fileRecordPublisher->injectForeignDatabase($foreignDatabase); - $movedFile = $this->createMock(FileRecord::class); - $movedFile->method('getClassification')->willReturn('_file'); - $movedFile->method('getState')->willReturn(Record::S_MOVED); - $movedFile->method('getLocalProps')->willReturn( - ['storage' => 1, 'identifier' => 'bar', 'identifier_hash' => 'baz'], + $fileRecordPublisher->publish($movedFile); + $fileRecordPublisher->finish(); + } + + /** + * @covers ::publish + */ + public function testPublishTransmitsReplacedFileWithNewNameRecord() + { + $fileRecordPublisher = $this->createFileRecordPublisher('/var/tmp/transient/sadsdas.tmp'); + + $movedFile = new FileRecord( + $this->createFileInfo('foo', 1, '452093485', '/foo'), + $this->createFileInfo('bar', 1, '234587', '/bar'), ); - $movedFile->method('getForeignProps')->willReturn( - ['storage' => 1, 'identifier' => 'bar_foreign', 'identifier_hash' => 'baz_foreign'], + + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => ReplaceAndRenameFileInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'oldFileIdentifier' => '/bar/bar', + 'foreignTargetFileIdentifier' => '/foo/foo', + 'foreignTemporaryFileIdentifier' => '/var/tmp/transient/sadsdas.tmp', + ]), + ], + ]), + ); + $fileRecordPublisher->injectForeignDatabase($foreignDatabase); + + $fileRecordPublisher->publish($movedFile); + $fileRecordPublisher->finish(); + } + + /** + * @covers ::publish + */ + public function testPublishTransmitsReplacedFileWithSameNameRecord() + { + $fileRecordPublisher = $this->createFileRecordPublisher('/var/tmp/transient/sadsdas.tmp'); + + $movedFile = new FileRecord( + $this->createFileInfo('foo', 1, '452093485', '/foo'), + $this->createFileInfo('foo', 1, '234587', '/foo'), ); - $reflectionProperty = new ReflectionProperty($fileRecordPublisher, 'requestToken'); - $reflectionProperty->setAccessible(true); - - $foreignDatabase->expects($this->once())->method('insert')->with( - 'tx_in2publishcore_filepublisher_task', - [ - 'request_token' => $reflectionProperty->getValue($fileRecordPublisher), - 'crdate' => $GLOBALS['EXEC_TIME'], - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'storage_uid' => $movedFile->getLocalProps()['storage'], - 'identifier' => $movedFile->getLocalProps()['identifier'], - 'identifier_hash' => $movedFile->getLocalProps()['identifier_hash'], - 'previous_identifier' => $movedFile->getForeignProps()['identifier'], - 'file_action' => $fileRecordPublisher::A_RENAME, - ], + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => ReplaceFileInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'foreignTargetFileIdentifier' => '/foo/foo', + 'foreignTemporaryFileIdentifier' => '/var/tmp/transient/sadsdas.tmp', + ]), + ], + ]), ); $fileRecordPublisher->injectForeignDatabase($foreignDatabase); $fileRecordPublisher->publish($movedFile); + $fileRecordPublisher->finish(); + } + + protected function createFileInfo( + string $name, + int $storage, + string $hash, + string $folder, + string $mimeType = 'image/jpeg', + int $size = 12345 + ): array { + return [ + 'size' => $size, + 'mimetype' => $mimeType, + 'name' => $name, + 'extension' => strrpos($name, '.') ? substr($name, strrpos($name, '.')) : '', + 'folder_hash' => sha1($folder), + 'identifier' => str_replace('//', '/', '/' . trim($folder, '/') . '/') . $name, + 'storage' => $storage, + 'sha1' => $hash, + 'publicUrl' => 'fileadmin/' . $name, + ]; + } + + protected function createFileRecordPublisher(string $temporaryFileIdentifier = '_undefined_'): FileRecordPublisher + { + $fileRecordPublisher = $this->createPartialMock(FileRecordPublisher::class, ['transmitTemporaryFile']); + $remoteCommandResponse = $this->createMock(RemoteCommandResponse::class); + $remoteCommandResponse->method('isSuccessful')->willReturn(true); + $remoteCommandDispatcher = $this->createMock(RemoteCommandDispatcher::class); + $remoteCommandDispatcher->method('dispatch')->willReturn($remoteCommandResponse); + $fileRecordPublisher->injectRemoteCommandDispatcher($remoteCommandDispatcher); + + $fileRecordPublisher->method('transmitTemporaryFile')->willReturn($temporaryFileIdentifier); + + return $fileRecordPublisher; } } diff --git a/Tests/Unit/Component/Core/Publisher/FolderRecordPublisherTest.php b/Tests/Unit/Component/Core/Publisher/FolderRecordPublisherTest.php index a31a5e04a..490989713 100644 --- a/Tests/Unit/Component/Core/Publisher/FolderRecordPublisherTest.php +++ b/Tests/Unit/Component/Core/Publisher/FolderRecordPublisherTest.php @@ -4,15 +4,21 @@ namespace In2code\In2publishCore\Tests\Unit\Component\Core\Publisher; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FolderInfo; use In2code\In2publishCore\Component\Core\Publisher\FolderRecordPublisher; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\AddFolderInstruction; +use In2code\In2publishCore\Component\Core\Publisher\Instruction\DeleteFolderInstruction; use In2code\In2publishCore\Component\Core\Record\Model\DatabaseRecord; use In2code\In2publishCore\Component\Core\Record\Model\FileRecord; use In2code\In2publishCore\Component\Core\Record\Model\FolderRecord; -use In2code\In2publishCore\Component\Core\Record\Model\Record; +use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandDispatcher; +use In2code\In2publishCore\Component\RemoteCommandExecution\RemoteCommandResponse; +use In2code\In2publishCore\Tests\Unit\Component\Core\Publisher\Constraint\IsEqualIgnoringRequestToken; use In2code\In2publishCore\Tests\UnitTestCase; -use ReflectionProperty; use TYPO3\CMS\Core\Database\Connection; +use function json_encode; + /** * @coversDefaultClass \In2code\In2publishCore\Component\Core\Publisher\FolderRecordPublisher */ @@ -41,33 +47,32 @@ public function testCanPublishReturnsTrueForFileRecordsOnly() */ public function testPublishRemovesDeletedFolder() { - $folderRecordPublisher = new FolderRecordPublisher(); - $foreignDatabase = $this->createMock(Connection::class); - $deletedFolder = $this->createMock(FileRecord::class); - $deletedFolder->method('getClassification')->willReturn('_folder'); - $deletedFolder->method('getState')->willReturn(Record::S_DELETED); - $deletedFolder->method('getForeignProps')->willReturn( - ['storage' => 1, 'identifier' => 'bar', 'combinedIdentifier' => '1:bar'], + $folderRecordPublisher = $this->createFolderRecordPublisher(); + + $deletedFolder = new FolderRecord( + [], + (new FolderInfo(1, '/foo/bar', 'bar'))->toArray(), ); - $reflectionProperty = new ReflectionProperty($folderRecordPublisher, 'requestToken'); - $reflectionProperty->setAccessible(true); - - $foreignDatabase->expects($this->once())->method('insert')->with( - 'tx_in2publishcore_filepublisher_task', - [ - 'request_token' => $reflectionProperty->getValue($folderRecordPublisher), - 'crdate' => $GLOBALS['EXEC_TIME'], - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'storage_uid' => $deletedFolder->getForeignProps()['storage'], - 'identifier' => $deletedFolder->getForeignProps()['combinedIdentifier'], - 'identifier_hash' => '', - 'folder_action' => $folderRecordPublisher::A_DELETE, - ], + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => DeleteFolderInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'folderIdentifier' => '/foo/bar', + ]), + ], + ]), ); $folderRecordPublisher->injectForeignDatabase($foreignDatabase); $folderRecordPublisher->publish($deletedFolder); + $folderRecordPublisher->finish(); } /** @@ -75,33 +80,45 @@ public function testPublishRemovesDeletedFolder() */ public function testPublishAddsAddedFolder() { - $folderRecordPublisher = new FolderRecordPublisher(); - $foreignDatabase = $this->createMock(Connection::class); - $addedFolder = $this->createMock(FileRecord::class); - $addedFolder->method('getClassification')->willReturn('_folder'); - $addedFolder->method('getState')->willReturn(Record::S_ADDED); - $addedFolder->method('getLocalProps')->willReturn( - ['storage' => 1, 'identifier' => 'bar', 'combinedIdentifier' => '1:bar'], + $folderRecordPublisher = $this->createFolderRecordPublisher(); + + $addedFolder = new FolderRecord( + (new FolderInfo(1, '/foo/bar', 'bar'))->toArray(), + [], ); - $addedFolder->method('getForeignProps')->willReturn([]); - - $reflectionProperty = new ReflectionProperty($folderRecordPublisher, 'requestToken'); - $reflectionProperty->setAccessible(true); - - $foreignDatabase->expects($this->once())->method('insert')->with( - 'tx_in2publishcore_filepublisher_task', - [ - 'request_token' => $reflectionProperty->getValue($folderRecordPublisher), - 'crdate' => $GLOBALS['EXEC_TIME'], - 'tstamp' => $GLOBALS['EXEC_TIME'], - 'storage_uid' => $addedFolder->getLocalProps()['storage'], - 'identifier' => $addedFolder->getLocalProps()['combinedIdentifier'], - 'identifier_hash' => '', - 'folder_action' => $folderRecordPublisher::A_INSERT, - ], + + $foreignDatabase = $this->createMock(Connection::class); + $foreignDatabase->expects($this->once())->method('bulkInsert')->with( + 'tx_in2publishcore_filepublisher_instruction', + new IsEqualIgnoringRequestToken([ + [ + 'crdate' => $GLOBALS['EXEC_TIME'], + 'tstamp' => $GLOBALS['EXEC_TIME'], + 'instruction' => AddFolderInstruction::class, + 'configuration' => json_encode([ + 'storage' => 1, + 'folderIdentifier' => '/foo/bar', + ]), + ], + ]), ); $folderRecordPublisher->injectForeignDatabase($foreignDatabase); $folderRecordPublisher->publish($addedFolder); + $folderRecordPublisher->finish(); + } + + /** + * @return FolderRecordPublisher + */ + public function createFolderRecordPublisher(): FolderRecordPublisher + { + $folderRecordPublisher = new FolderRecordPublisher(); + $remoteCommandResponse = $this->createMock(RemoteCommandResponse::class); + $remoteCommandResponse->method('isSuccessful')->willReturn(true); + $remoteCommandDispatcher = $this->createMock(RemoteCommandDispatcher::class); + $remoteCommandDispatcher->method('dispatch')->willReturn($remoteCommandResponse); + $folderRecordPublisher->injectRemoteCommandDispatcher($remoteCommandDispatcher); + return $folderRecordPublisher; } } diff --git a/Tests/Unit/Component/Core/Publisher/PublisherCollectionTest.php b/Tests/Unit/Component/Core/Publisher/PublisherCollectionTest.php index 354cfd290..8b1b590f6 100644 --- a/Tests/Unit/Component/Core/Publisher/PublisherCollectionTest.php +++ b/Tests/Unit/Component/Core/Publisher/PublisherCollectionTest.php @@ -48,7 +48,7 @@ public function testAddPublisherAddsPublisher() /** * @covers ::addPublisher */ - public function testAddPublisherSortsPublishersCorrectly() + public function testAddPublisherSortsPublishersCorrectly(): void { $publisherCollection = new PublisherCollection(); $reflectionPropertyPublishers = new ReflectionProperty($publisherCollection, 'publishers'); @@ -56,7 +56,7 @@ public function testAddPublisherSortsPublishersCorrectly() $standardPublisher = $this->createMock(Publisher::class); $transactionalPublisher = $this->createMock(DatabaseRecordPublisher::class); - $finishablePublisher = $this->createMock(FileRecordPublisher::class); + $finishablePublisher = $this->createMock(FinishablePublisher::class); $publisherCollection->addPublisher($standardPublisher); $publisherCollection->addPublisher($transactionalPublisher); diff --git a/Tests/Unit/Component/Core/Publisher/PublisherServiceTest.php b/Tests/Unit/Component/Core/Publisher/PublisherServiceTest.php index 2b8211b11..052024091 100644 --- a/Tests/Unit/Component/Core/Publisher/PublisherServiceTest.php +++ b/Tests/Unit/Component/Core/Publisher/PublisherServiceTest.php @@ -9,6 +9,7 @@ use In2code\In2publishCore\Component\Core\Publisher\FileRecordPublisher; use In2code\In2publishCore\Component\Core\Publisher\Publisher; use In2code\In2publishCore\Component\Core\Publisher\PublisherService; +use In2code\In2publishCore\Component\Core\Publisher\PublishingContext; use In2code\In2publishCore\Component\Core\Publisher\ReversiblePublisher; use In2code\In2publishCore\Component\Core\Publisher\TransactionalPublisher; use In2code\In2publishCore\Component\Core\Record\Model\DatabaseRecord; @@ -25,6 +26,7 @@ class PublisherServiceTest extends UnitTestCase { /** + * @covers ::publish * @covers ::publishRecordTree * @covers ::publishRecord */ @@ -41,11 +43,13 @@ public function testPublishRecordTreePublishesAllUnchangedRecordsInTree(): void $publisherService->addPublisher($databaseRecordPublisher); $recordTree = $this->getRecordTree1(); + $publishingContext = new PublishingContext($recordTree); - $publisherService->publishRecordTree($recordTree); + $publisherService->publish($publishingContext); } /** + * @covers ::publish * @covers ::publishRecordTree * @covers ::publishRecord */ @@ -62,11 +66,13 @@ public function testPublishRecordTreePublishesChildRecordsWithoutPages(): void $publisherService->addPublisher($databaseRecordPublisher); $recordTree = $this->getRecordTree2(); + $publishingContext = new PublishingContext($recordTree); - $publisherService->publishRecordTree($recordTree); + $publisherService->publish($publishingContext); } /** + * @covers ::publish * @covers ::publishRecordTree */ public function testCancelIsCalledWhenExceptionIsThrownDuringPublishing(): void @@ -81,12 +87,14 @@ public function testCancelIsCalledWhenExceptionIsThrownDuringPublishing(): void $publisherService->addPublisher($databaseRecordPublisher); $recordTree = $this->getRecordTree1(); + $publishingContext = new PublishingContext($recordTree); $this->expectException(Exception::class); - $publisherService->publishRecordTree($recordTree); + $publisherService->publish($publishingContext); } /** + * @covers ::publish * @covers ::addPublisher * @covers ::publishRecordTree */ @@ -107,11 +115,13 @@ public function testCancelAndReverseAreCalledWhenExceptionIsThrownDuringFinish() $reversibleTransactionalPublisher = $this->getReversibleTransactionalPublisher(); $publisherService->addPublisher($reversibleTransactionalPublisher); + $publishingContext = new PublishingContext($recordTree); + $GLOBALS['number_of_calls_reverse'] = 0; $GLOBALS['number_of_calls_cancel'] = 0; try { $this->expectException(Exception::class); - $publisherService->publishRecordTree($recordTree); + $publisherService->publish($publishingContext); } finally { $this->assertEquals(1, $GLOBALS['number_of_calls_reverse']); $this->assertEquals(1, $GLOBALS['number_of_calls_cancel']); diff --git a/Tests/Unit/Component/Core/Record/Factory/RecordFactoryTest.php b/Tests/Unit/Component/Core/Record/Factory/RecordFactoryTest.php index f251f06fc..5ab774d36 100644 --- a/Tests/Unit/Component/Core/Record/Factory/RecordFactoryTest.php +++ b/Tests/Unit/Component/Core/Record/Factory/RecordFactoryTest.php @@ -168,6 +168,10 @@ public function testFileRecordIsCreatedAndAddedToRecordIndex(): void $eventDispatcher = $this->createMock(EventDispatcher::class); $eventDispatcher->expects($this->exactly(2))->method('dispatch'); + $ignoredFieldsService = $this->createMock(IgnoredFieldsService::class); + $ignoredFieldsService->method('getIgnoredFields')->willReturn([]); + + $recordFactory->injectIgnoredFieldsService($ignoredFieldsService); $recordFactory->injectEventDispatcher($eventDispatcher); $recordFactory->injectRecordIndex($recordIndex); @@ -195,7 +199,6 @@ public function testFolderRecordIsCreatedAndAddedToRecordIndex(): void $recordFactory->injectRecordIndex($recordIndex); $record = $recordFactory->createFolderRecord( - 'combined_identifier', ['field_foo' => 'value_foo'], [], ); diff --git a/Tests/Unit/Component/Core/Record/Model/FileRecordTest.php b/Tests/Unit/Component/Core/Record/Model/FileRecordTest.php index 00a022bfb..b243c1365 100644 --- a/Tests/Unit/Component/Core/Record/Model/FileRecordTest.php +++ b/Tests/Unit/Component/Core/Record/Model/FileRecordTest.php @@ -59,6 +59,18 @@ public function testCalculateState(): void ['identifier' => 'file1', 'storage' => 42], ['identifier' => 'file2', 'storage' => 42], ); - $this->assertSame(Record::S_MOVED, $changedFileRecord->getState()); + $this->assertSame(Record::S_CHANGED, $changedFileRecord->getState()); + + $movedFileRecord = new FileRecord( + ['identifier' => 'file1', 'folder_hash' => '/bar', 'storage' => 42], + ['identifier' => 'file1', 'folder_hash' => '/foo', 'storage' => 42], + ); + $this->assertSame(Record::S_MOVED, $movedFileRecord->getState()); + + $replacedFileRecord = new FileRecord( + ['identifier' => 'file1', 'folder_hash' => '/bar', 'storage' => 42, 'sha1' => '234572364958734'], + ['identifier' => 'file1', 'folder_hash' => '/foo', 'storage' => 42, 'sha1' => '238497569384765'], + ); + $this->assertSame(Record::S_CHANGED, $replacedFileRecord->getState()); } } diff --git a/Tests/Unit/Component/Core/Record/Model/FolderRecordTest.php b/Tests/Unit/Component/Core/Record/Model/FolderRecordTest.php index beaaefaad..0b4a33375 100644 --- a/Tests/Unit/Component/Core/Record/Model/FolderRecordTest.php +++ b/Tests/Unit/Component/Core/Record/Model/FolderRecordTest.php @@ -4,6 +4,7 @@ namespace In2code\In2publishCore\Tests\Unit\Component\Core\Record\Model; +use In2code\In2publishCore\Component\Core\DemandResolver\Filesystem\Model\FolderInfo; use In2code\In2publishCore\Component\Core\Record\Model\FolderRecord; use In2code\In2publishCore\Component\Core\Record\Model\Record; use In2code\In2publishCore\Tests\UnitTestCase; @@ -24,10 +25,13 @@ class FolderRecordTest extends UnitTestCase */ public function testConstructor(): void { - $folderRecord = new FolderRecord('42:folder_name', ['prop_1' => 'value_1'], []); + $folderRecord = new FolderRecord((new FolderInfo(42, 'folder_name', 'blala'))->toArray(), []); $this->assertInstanceOf(FolderRecord::class, $folderRecord); $this->assertSame('42:folder_name', $folderRecord->getId()); - $this->assertSame(['prop_1' => 'value_1'], $folderRecord->getLocalProps()); + $this->assertSame( + ['storage' => 42, 'identifier' => 'folder_name', 'name' => 'blala'], + $folderRecord->getLocalProps() + ); $this->assertSame([], $folderRecord->getForeignProps()); $this->assertSame(FolderRecord::CLASSIFICATION, $folderRecord->getClassification()); @@ -41,16 +45,18 @@ public function testConstructor(): void */ public function testCalculateState(): void { - $folderRecord = new FolderRecord('42:folder_name', ['prop_1' => 'value_1'], []); + $props = (new FolderInfo(42, 'folder_name', 'blala'))->toArray(); + $folderRecord = new FolderRecord($props, []); $this->assertSame(Record::S_ADDED, $folderRecord->getState()); - $folderRecord = new FolderRecord('42:folder_name', ['prop_1' => 'value_1'], ['prop_1' => 'value_1']); + $folderRecord = new FolderRecord($props, $props); $this->assertSame(Record::S_UNCHANGED, $folderRecord->getState()); - $folderRecord = new FolderRecord('42:folder_name', ['prop_1' => 'value_1'], ['prop_1' => 'value_2']); + $props2 = (new FolderInfo(42, 'other_folder_name', 'foooo'))->toArray(); + $folderRecord = new FolderRecord($props, $props2); $this->assertSame(Record::S_CHANGED, $folderRecord->getState()); - $folderRecord = new FolderRecord('42:folder_name', [], ['prop_1' => 'value_2']); + $folderRecord = new FolderRecord([], $props); $this->assertSame(Record::S_DELETED, $folderRecord->getState()); } } diff --git a/Tests/Unit/Component/Core/RecordTree/RecordTreeBuilderTest.php b/Tests/Unit/Component/Core/RecordTree/RecordTreeBuilderTest.php index fab4db378..d09081f36 100644 --- a/Tests/Unit/Component/Core/RecordTree/RecordTreeBuilderTest.php +++ b/Tests/Unit/Component/Core/RecordTree/RecordTreeBuilderTest.php @@ -17,6 +17,7 @@ use In2code\In2publishCore\Component\Core\RecordTree\RecordTreeBuilder; use In2code\In2publishCore\Component\Core\RecordTree\RecordTreeBuildRequest; use In2code\In2publishCore\Component\Core\Service\RelevantTablesService; +use In2code\In2publishCore\Service\Configuration\PageTypeService; use In2code\In2publishCore\Service\Configuration\TcaService; use In2code\In2publishCore\Tests\UnitTestCase; use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; @@ -83,14 +84,14 @@ function (Demands $demands, RecordCollection $recordCollection) use ($fooRecord) 1 => 'table_foo', ]); - $tcaService = $this->createMock(TcaService::class); - $tcaService->method('getTablesAllowedOnPage')->willReturn(['table_foo']); + $pageTypeService = $this->createMock(PageTypeService::class); + $pageTypeService->method('getTablesAllowedOnPage')->willReturn(['table_foo']); $recordTreeBuilder->injectDemandResolver($demandResolver); $recordTreeBuilder->injectRecordIndex($recordIndex); $recordTreeBuilder->injectDemandsFactory($demandsFactory); $recordTreeBuilder->injectRelevantTablesService($relevantTablesService); - $recordTreeBuilder->injectTcaService($tcaService); + $recordTreeBuilder->injectPageTypeService($pageTypeService); // act $recordsInCollection = $recordTreeBuilder->findAllRecordsOnPages(); diff --git a/Tests/Unit/Component/Core/Resolver/FlexResolverTest.php b/Tests/Unit/Component/Core/Resolver/FlexResolverTest.php index ba9a57339..b75449a88 100644 --- a/Tests/Unit/Component/Core/Resolver/FlexResolverTest.php +++ b/Tests/Unit/Component/Core/Resolver/FlexResolverTest.php @@ -93,7 +93,7 @@ public function testResolveDoesNotGetResolverOnEmptyDatabaseRecord(): void $flexFormTools->method('getDataStructureIdentifier')->willReturn($dataStructure); $flexFormService = $this->createMock(FlexFormService::class); $flexFormService->method('convertFlexFormContentToArray')->willReturn([]); - $resolversService->expects($this->never())->method('getResolversForTable'); + $resolversService->expects($this->never())->method('getResolversForClassification'); $flexResolver->injectFlexFormTools($flexFormTools); $flexResolver->injectFlexFormService($flexFormService); @@ -132,7 +132,7 @@ public function testResolveDelegatesResolveToItsResolvers(): void $selectResolver = $this->createMock(SelectResolver::class); $selectResolver->expects($this->once())->method('resolve'); $resolvers = ['column_foo' => $selectResolver,]; - $resolversService->expects($this->once())->method('getResolversForTable')->willReturn($resolvers); + $resolversService->expects($this->once())->method('getResolversForClassification')->willReturn($resolvers); $flexResolver->injectFlexFormTools($flexFormTools); $flexResolver->injectFlexFormService($flexFormService); diff --git a/Tests/Unit/Service/ReplaceMarkersServiceTest.php b/Tests/Unit/Service/ReplaceMarkersServiceTest.php index 8d6d0878b..ac75d26d4 100644 --- a/Tests/Unit/Service/ReplaceMarkersServiceTest.php +++ b/Tests/Unit/Service/ReplaceMarkersServiceTest.php @@ -54,7 +54,6 @@ public function testReplaceMarkerServiceSupportsPageTsConfigId(): void $record = $this->getRecordStub('tx_unit_test_table'); $flexFormTools = $this->createMock(FlexFormTools::class); - $tcaPreProcessingService = $this->createMock(TcaPreProcessingService::class); $siteFinder = $this->createMock(SiteFinder::class); $connection = $this->createMock(Connection::class); @@ -74,7 +73,6 @@ protected function getPagesTsConfig(int $pageIdentifier): array }; $replaceMarkerService->injectLocalDatabase($connection); $replaceMarkerService->injectFlexFormTools($flexFormTools); - $replaceMarkerService->injectTcaPreProcessingService($tcaPreProcessingService); $replaceMarkerService->injectSiteFinder($siteFinder); $replacement = $replaceMarkerService->replaceMarkers( @@ -95,7 +93,6 @@ public function testReplaceMarkerServiceSupportsPageTsConfigIdList(): void $record = $this->getRecordStub('tx_unit_test_table'); $flexFormTools = $this->createMock(FlexFormTools::class); - $tcaPreProcessingService = $this->createMock(TcaPreProcessingService::class); $siteFinder = $this->createMock(SiteFinder::class); $connection = $this->createMock(Connection::class); @@ -115,7 +112,6 @@ protected function getPagesTsConfig(int $pageIdentifier): array }; $replaceMarkerService->injectLocalDatabase($connection); $replaceMarkerService->injectFlexFormTools($flexFormTools); - $replaceMarkerService->injectTcaPreProcessingService($tcaPreProcessingService); $replaceMarkerService->injectSiteFinder($siteFinder); $replacement = $replaceMarkerService->replaceMarkers( @@ -135,7 +131,6 @@ public function testReplaceSiteMarker(): void $record = $this->getRecordStub('tx_unit_test_table'); $flexFormTools = $this->createMock(FlexFormTools::class); - $tcaProcessingService = $this->createMock(TcaPreProcessingService::class); $siteFinder = $this->createMock(SiteFinder::class); $siteFinder->method('getSiteByPageId')->willReturn( new Site('test', 1, [ @@ -161,7 +156,6 @@ public function testReplaceSiteMarker(): void $replaceMarkerService->injectLocalDatabase($connection); $replaceMarkerService->injectFlexFormTools($flexFormTools); $replaceMarkerService->injectSiteFinder($siteFinder); - $replaceMarkerService->injectTcaPreProcessingService($tcaProcessingService); $replacement = $replaceMarkerService->replaceMarkers( $record, diff --git a/Tests/Unit/Utility/BackendUtilityTest.php b/Tests/Unit/Utility/BackendUtilityTest.php index f2cc0d525..5b64a4508 100644 --- a/Tests/Unit/Utility/BackendUtilityTest.php +++ b/Tests/Unit/Utility/BackendUtilityTest.php @@ -29,8 +29,8 @@ * This copyright notice MUST APPEAR in all copies of the script! */ -use Doctrine\DBAL\Driver\Mysqli\MysqliStatement; -use Doctrine\DBAL\Schema\MySqlSchemaManager; +use Doctrine\DBAL\Result; +use Doctrine\DBAL\Schema\AbstractSchemaManager; use In2code\In2publishCore\Tests\UnitTestCase; use In2code\In2publishCore\Utility\BackendUtility; use ReflectionProperty; @@ -50,30 +50,30 @@ class BackendUtilityTest extends UnitTestCase protected function setUp(): void { parent::setUp(); - $connection = $this->createMock(Connection::class); - $schemaManager = $this->createMock(MySqlSchemaManager::class); + $schemaManager = $this->createMock(AbstractSchemaManager::class); $schemaManager->method('listTableNames')->willReturn( [ 'tt_content', ], ); - $connection->method('getSchemaManager')->willReturn($schemaManager); - $result = $this->createMock(MysqliStatement::class); + $result = $this->createMock(Result::class); $result->method('fetchAssociative')->willReturnCallback( function () { return $this->rows; }, ); - $restrictionContainer = $this->createMock(DefaultRestrictionContainer::class); $queryBuilder = $this->createMock(QueryBuilder::class); $queryBuilder->method('select')->willReturn($queryBuilder); $queryBuilder->method('from')->willReturn($queryBuilder); $queryBuilder->method('where')->willReturn($queryBuilder); $queryBuilder->method('setMaxResults')->willReturn($queryBuilder); - $queryBuilder->method('execute')->willReturn($result); - $queryBuilder->method('getRestrictions')->willReturn($restrictionContainer); + $queryBuilder->method('executeQuery')->willReturn($result); + $queryBuilder->method('getRestrictions')->willReturn($this->createMock(DefaultRestrictionContainer::class)); + + $connection = $this->createMock(Connection::class); + $connection->method('createSchemaManager')->willReturn($schemaManager); $connection->method('createQueryBuilder')->willReturn($queryBuilder); $reflection = new ReflectionProperty(ConnectionPool::class, 'connections'); diff --git a/UPGRADING.md b/UPGRADING.md index 23e1ea70d..47e134d9f 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,3 +1,19 @@ +# v12.2 to v12.3 + +## For admins + +### Change .yml configuration files +Up to version 12.2, the Content Publisher used the YAML parser [Spyc] for parsing the configuration files. Since TYPO3 +now ships with a YAML parser, we now use this parser to reduce the code we have to maintain. +This change has some impact on the configuration files. +Please read [60149-Change-ReplaceSpycWithSymfonyYaml.md] for details on how to adjust your configuration files. + + +## For developers + +The core events `PublishingOfOneRecordBegan` and `PublishingOfOneRecordEnded` were replaced. Please +read [54638-Deprecation-PublishingOfOneRecordBegan_Ended.md](Documentation/Developers/Changelog/54638-Deprecation-PublishingOfOneRecordBegan_Ended.md). + # v12.1 to v12.2 ## For developers diff --git a/composer.json b/composer.json index dc44b7321..2337b3ae5 100755 --- a/composer.json +++ b/composer.json @@ -41,15 +41,16 @@ "ext-json": "*", "ext-pdo": "*", "ext-zip": "*", - "typo3/cms-core": "^11.5" + "typo3/cms-core": "^11.5 || ^12.4", + "symfony/yaml": "^v5.4 || ^v6.3" }, "require-dev": { "symfony/polyfill-php80": "^1.24", "symfony/polyfill-php81": "^1.24", - "typo3/cms-extensionmanager": "^11.4", - "typo3/cms-filelist": "^11.5", - "typo3/cms-redirects": "^11.5", - "typo3/cms-reports": "^11.5", + "typo3/cms-extensionmanager": "^11.4 || ^12.4", + "typo3/cms-filelist": "^11.5 || ^12.4", + "typo3/cms-redirects": "^11.5 || ^12.4", + "typo3/cms-reports": "^11.5 || ^12.4", "typo3/testing-framework": "^6.12" }, "suggest": { @@ -58,7 +59,10 @@ "autoload": { "psr-4": { "In2code\\In2publishCore\\": "Classes" - } + }, + "files": [ + "constants.php" + ] }, "autoload-dev": { "psr-4": { @@ -81,8 +85,8 @@ }, "extra": { "branch-alias": { - "dev-develop-v12": "12.3.x-dev", - "dev-master-v12": "12.3.x-dev" + "dev-develop": "12.3.x-dev", + "dev-master": "12.3.x-dev" }, "typo3/cms": { "cms-package-dir": "{$vendor-dir}/typo3/cms", diff --git a/constants.php b/constants.php new file mode 100644 index 000000000..c7c9326ed --- /dev/null +++ b/constants.php @@ -0,0 +1,22 @@ +getMajorVersion(); + + if (!defined('In2code\In2publishCore\TYPO3_V11')) { + define('In2code\In2publishCore\TYPO3_V11', 11 === $majorVersion); + } + if (!(defined('In2code\In2publishCore\TYPO3_V12'))) { + define('In2code\In2publishCore\TYPO3_V12', 12 === $majorVersion); + } +})(); diff --git a/ext_emconf.php b/ext_emconf.php index e56bb7047..526650d4b 100755 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -9,7 +9,7 @@ 'title' => 'in2publish Core', 'description' => 'Content publishing extension to connect stage and production server', 'category' => 'plugin', - 'version' => '12.2.0', + 'version' => '12.3.0', 'state' => 'stable', 'clearCacheOnLoad' => true, 'author' => 'Alex Kellner, Oliver Eglseder, Thomas Scheibitz, Stefan Busemann', diff --git a/ext_localconf.php b/ext_localconf.php index 9032cc3bf..235610bc6 100755 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,10 +1,10 @@ BackendRouteInitialization::class, - ]; - /************************************************ Record Extension ************************************************/ $file = Environment::getVarPath() . '/cache/code/content_publisher/record_extension_trait.php'; if (file_exists($file)) { @@ -92,4 +85,7 @@ 'dateField' => 'timestamp_begin', 'expirePeriod' => 1, ]; + + /*********************************************** Enable PageTSProvider *******************************************/ + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['postUserLookUp'][1699367499] = PageTsProvider::class . '->processData'; })(); diff --git a/ext_tables.php b/ext_tables.php index 918894a26..ed03d70b0 100755 --- a/ext_tables.php +++ b/ext_tables.php @@ -3,6 +3,7 @@ use In2code\In2publishCore\Component\ConfigContainer\ConfigContainer; use In2code\In2publishCore\Controller\FileController; use In2code\In2publishCore\Controller\RecordController; +use In2code\In2publishCore\Features\AdminTools\Service\ToolsRegistry; use In2code\In2publishCore\Features\ContextMenuPublishEntry\ContextMenu\PublishItemProvider; use In2code\In2publishCore\Features\RedirectsSupport\Controller\RedirectController; use In2code\In2publishCore\Features\WarningOnForeign\Service\HeaderWarningColorRenderer; @@ -34,6 +35,8 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Utility\ExtensionUtility; +use const In2code\In2publishCore\TYPO3_V11; + (static function (): void { /***************************************************** Guards *****************************************************/ if (!defined('TYPO3')) { @@ -43,10 +46,6 @@ // Early return when installing per ZIP: autoload is not yet generated return; } - if (!(TYPO3_REQUESTTYPE & (TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI))) { - // Do nothing when not in any of the desirable modes. - return; - } /**************************************************** Instances ***************************************************/ $configContainer = GeneralUtility::makeInstance(ConfigContainer::class); @@ -64,38 +63,84 @@ return; } - /******************************************** Register Backend Modules ********************************************/ - if ($configContainer->get('module.m1')) { - ExtensionUtility::registerModule( - 'in2publish_core', - 'web', - 'm1', - '', - [ - RecordController::class => 'index,detail,publishRecord,toggleFilterStatus', - ], - [ - 'access' => 'user,group', - 'icon' => 'EXT:in2publish_core/Resources/Public/Icons/Overview.svg', - 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod1.xlf', - ], - ); - } - if ($configContainer->get('module.m3')) { - ExtensionUtility::registerModule( - 'in2publish_core', - 'file', - 'm3', - '', - [ - FileController::class => 'index,publishFolder,publishFile,toggleFilterStatus', - ], - [ - 'access' => 'user,group', - 'icon' => 'EXT:in2publish_core/Resources/Public/Icons/File.svg', - 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod3.xlf', - ], - ); + if (TYPO3_V11) { + /** + * Deprecated registering of Backend Modules + * Register Backend Modules for TYPO3 v11 + * can be removed in TYPO3 v13 + */ + if ($configContainer->get('module.m1')) { + ExtensionUtility::registerModule( + 'in2publish_core', + 'web', + 'm1', + '', + [ + RecordController::class => 'index,detail,publishRecord,toggleFilterStatus', + ], + [ + 'access' => 'user,group', + 'iconIdentifier' => 'in2publish-core-overview-module', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod1.xlf', + ], + ); + } + if ($configContainer->get('module.m3')) { + ExtensionUtility::registerModule( + 'in2publish_core', + 'file', + 'm3', + '', + [ + FileController::class => 'index,publishFolder,publishFile,toggleFilterStatus', + ], + [ + 'access' => 'user,group', + 'iconIdentifier' => 'in2publish-core-file-module', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod3.xlf', + ], + ); + } + + if ($configContainer->get('module.m4')) { + $toolsRegistry = GeneralUtility::makeInstance(ToolsRegistry::class); + $controllerActions = $toolsRegistry->processDataForTypo3V11(); + if (!empty($controllerActions)) { + ExtensionUtility::registerModule( + 'in2publish_core', + 'tools', + 'm4', + '', + $controllerActions, + [ + 'access' => 'admin', + 'iconIdentifier' => 'in2publish-core-tools-module', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod4.xlf', + ], + ); + } + } + + /************************************************ Redirect Support ************************************************/ + if ( + $configContainer->get('features.redirectsSupport.enable') + && ExtensionManagementUtility::isLoaded('redirects') + ) { + ExtensionUtility::registerModule( + 'in2publish_core', + 'site', + 'm5', + 'after:redirects', + [ + RedirectController::class => 'list,publish,selectSite', + ], + [ + 'access' => 'user,group', + 'iconIdentifier' => 'in2publish-core-redirect-module', + 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod5.xlf', + ], + ); + } } /******************************************* Context Menu Publish Entry *******************************************/ @@ -130,25 +175,4 @@ $GLOBALS['in2publish_core']['tests'][] = ForeignConfigurationFormatTest::class; $GLOBALS['in2publish_core']['tests'][] = SiteConfigurationTest::class; $GLOBALS['in2publish_core']['tests'][] = TableGarbageCollectorTest::class; - - /************************************************ Redirect Support ************************************************/ - if ( - $configContainer->get('features.redirectsSupport.enable') - && ExtensionManagementUtility::isLoaded('redirects') - ) { - ExtensionUtility::registerModule( - 'in2publish_core', - 'site', - 'm5', - 'after:redirects', - [ - RedirectController::class => 'list,publish,selectSite', - ], - [ - 'access' => 'user,group', - 'icon' => 'EXT:in2publish_core/Resources/Public/Icons/Redirect.svg', - 'labels' => 'LLL:EXT:in2publish_core/Resources/Private/Language/locallang_mod5.xlf', - ], - ); - } })(); diff --git a/ext_typoscript_setup.txt b/ext_typoscript_setup.typoscript similarity index 100% rename from ext_typoscript_setup.txt rename to ext_typoscript_setup.typoscript