diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 209014a9..791a6367 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,7 +1,7 @@ name: interest type: php docroot: "" -php_version: "7.4" +php_version: "8.1" webserver_type: nginx-fpm #router_http_port: "8081" #router_https_port: "4434" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 782304ce..441f54e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,18 +121,6 @@ jobs: fail-fast: false matrix: include: - - typo3-version: ^9.5 - php-version: 7.4 - composer-dependencies: highest - - typo3-version: ^9.5 - php-version: 7.4 - composer-dependencies: lowest - - typo3-version: ^10.4 - php-version: 7.4 - composer-dependencies: highest - - typo3-version: ^10.4 - php-version: 7.4 - composer-dependencies: lowest - typo3-version: ^11.5 php-version: 7.4 composer-dependencies: highest @@ -157,6 +145,18 @@ jobs: - typo3-version: ^11.5 php-version: 8.2 composer-dependencies: lowest + - typo3-version: ^12.4 + php-version: 8.1 + composer-dependencies: highest + - typo3-version: ^12.4 + php-version: 8.1 + composer-dependencies: lowest + - typo3-version: ^12.4 + php-version: 8.2 + composer-dependencies: highest + - typo3-version: ^12.4 + php-version: 8.2 + composer-dependencies: lowest functional-tests: name: "Functional tests" runs-on: ubuntu-20.04 @@ -208,18 +208,6 @@ jobs: fail-fast: false matrix: include: - - typo3-version: ^9.5 - php-version: 7.4 - composer-dependencies: highest - - typo3-version: ^9.5 - php-version: 7.4 - composer-dependencies: lowest - - typo3-version: ^10.4 - php-version: 7.4 - composer-dependencies: highest - - typo3-version: ^10.4 - php-version: 7.4 - composer-dependencies: lowest - typo3-version: ^11.5 php-version: 7.4 composer-dependencies: highest @@ -244,3 +232,15 @@ jobs: - typo3-version: ^11.5 php-version: 8.2 composer-dependencies: lowest + - typo3-version: ^12.4 + php-version: 8.1 + composer-dependencies: highest + - typo3-version: ^12.4 + php-version: 8.1 + composer-dependencies: lowest + - typo3-version: ^12.4 + php-version: 8.2 + composer-dependencies: highest + - typo3-version: ^12.4 + php-version: 8.2 + composer-dependencies: lowest diff --git a/.php_cs.php b/.php_cs.php index 12a9c4a9..b37c242e 100644 --- a/.php_cs.php +++ b/.php_cs.php @@ -97,7 +97,7 @@ 'no_spaces_inside_parenthesis' => true, 'no_superfluous_elseif' => true, 'no_trailing_comma_in_list_call' => true, - 'no_trailing_comma_in_singleline_array' => true, + 'no_trailing_comma_in_singleline' => true, 'no_unneeded_control_parentheses' => true, 'no_unneeded_curly_braces' => true, 'no_unneeded_final_method' => true, diff --git a/Classes/Authentication/AbstractHttpBackendUserAuthentication.php b/Classes/Authentication/AbstractHttpBackendUserAuthentication.php new file mode 100644 index 00000000..8dfbed20 --- /dev/null +++ b/Classes/Authentication/AbstractHttpBackendUserAuthentication.php @@ -0,0 +1,111 @@ +getUserId() !== 0; + } + + /** + * Returns the user's UID. + * + * @return int + */ + public function getUserId(): int + { + return $this->user['uid'] ?? 0; + } + + /** + * Fetches login credentials from basic HTTP authentication header. + * + * @param ServerRequestInterface $request + * @return array + * @throws UnauthorizedAccessException + * @throws InvalidArgumentException + */ + protected function internalGetLoginFormData(ServerRequestInterface $request) + { + if (strtolower($request->getMethod()) !== 'post') { + throw new UnauthorizedAccessException( + 'Authorization requires POST method.', + $request + ); + } + + $authorizationHeader = $request->getHeader('authorization')[0] + ?? $request->getHeader('redirect_http_authorization')[0] + ?? ''; + + [$scheme, $authorizationData] = GeneralUtility::trimExplode(' ', $authorizationHeader, true); + + if ($scheme === null) { + throw new InvalidArgumentException( + 'No authorization scheme provided.', + $request + ); + } + + if (strtolower($scheme) !== 'basic') { + throw new InvalidArgumentException( + 'Unknown authorization scheme "' . $scheme . '".', + $request + ); + } + + $authorizationData = base64_decode($authorizationData, true); + + if (strpos($authorizationData, ':') === false) { + throw new InvalidArgumentException( + 'Authorization data couldn\'t be decoded. Missing ":" separating username and password.', + $request + ); + } + + [$username, $password] = explode(':', $authorizationData); + + $loginData = [ + 'status' => LoginType::LOGIN, + 'uname' => $username, + 'uident' => $password, + ]; + + if (CompatibilityUtility::typo3VersionIsLessThan('12.0')) { + return $this->processLoginData($loginData); + } + + return $this->processLoginData($loginData, $request); + } + + /** + * Returns the authentication service configuration with `BE_fetchUserIfNoSession` set to true. + * + * @return array + */ + protected function getAuthServiceConfiguration(): array + { + $configuration = parent::getAuthServiceConfiguration(); + + $configuration['BE_fetchUserIfNoSession'] = true; + + return $configuration; + } +} diff --git a/Classes/Authentication/HttpBackendUserAuthentication.php b/Classes/Authentication/HttpBackendUserAuthentication.php deleted file mode 100644 index 80f10bf6..00000000 --- a/Classes/Authentication/HttpBackendUserAuthentication.php +++ /dev/null @@ -1,225 +0,0 @@ -isUserAllowedToLogin()) { - throw new \RuntimeException('Login Error: TYPO3 is in maintenance mode at the moment. Only' . - ' administrators are allowed access.', 1483971855); - } - - $this->dontSetCookie = true; - - parent::__construct(); - } - - /** - * Replacement for AbstactUserAuthentication::start() - * - * We do not need support for sessions, cookies, $_GET-modes, the postUserLookup hook or - * a database connectiona during CLI Bootstrap - * - * @param ServerRequestInterface|null $request - */ - public function start(?ServerRequestInterface $request = null) - { - $this->logger->debug('## Beginning of auth logging.'); - } - - /** - * Logs in the TYPO3 Backend user supplied in HTTP headers. - * - * @param bool $proceedIfNoUserIsLoggedIn IGNORED - */ - public function backendCheckLogin($proceedIfNoUserIsLoggedIn = false) - { - } - - /** - * Logs-in the backend user. - * - * @param int $userId The ID of the backend user to log in. - * - * @throws \RuntimeException when the user could not log in or it is an admin - */ - public function authenticate(int $userId) - { - $this->setBeUserByUid($userId); - - // The groups are fetched and ready for permission checking in this initialization. - $this->fetchGroupData(); - $this->backendSetUC(); - } - - /** - * Checks if a submission of username and password is present or use other authentication by auth services - * - * @param ServerRequestInterface|null $request - * @internal - * - * phpcs:disable Generic.Metrics.CyclomaticComplexity - */ - public function checkAuthentication(?ServerRequestInterface $request = null) - { - $tempuser = null; - - $authenticated = false; - // The info array provide additional information for auth services - $authInfo = $this->getAuthInfoArray(); - // Get Login/Logout data submitted by a form or params - $loginData = $this->getLoginFormData(); - $this->logger->debug('Login data', $this->removeSensitiveLoginDataForLoggingInfo($loginData)); - - $tempuserArr = []; - - // Use 'auth' service to find the user - // First found user will be used - $subType = 'getUser' . $this->loginType; - foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) { - $row = $serviceObj->getUser(); - if ($row) { - $tempuserArr[] = $row; - $this->logger->debug('User found', [ - $this->userid_column => $row[$this->userid_column], - $this->username_column => $row[$this->username_column], - ]); - - break; - } - } - - if (empty($tempuserArr)) { - $this->logger->debug('No user found by services'); - } else { - $this->logger->debug(count($tempuserArr) . ' user records found by services'); - } - - // Authenticate the user if needed - if (!empty($tempuserArr)) { - foreach ($tempuserArr as $tempuser) { - // Use 'auth' service to authenticate the user - // If one service returns FALSE then authentication failed - // a service might return 100 which means there's no reason to stop but the user can't be authenticated - // by that service - $this->logger->debug('Auth user', $this->removeSensitiveLoginDataForLoggingInfo($tempuser, true)); - $subType = 'authUser' . $this->loginType; - - foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) { - $ret = $serviceObj->authUser($tempuser); - if ($ret > 0) { - // If the service returns >=200 then no more checking is needed - useful for IP checking without - // password - if ((int)$ret >= 200) { - $authenticated = true; - break; - } - if ((int)$ret < 100) { - $authenticated = true; - } - } else { - $authenticated = false; - break; - } - } - - if ($authenticated) { - // Leave foreach() because a user is authenticated - break; - } - } - } - - // If user is authenticated a valid user is in $tempuser - if ($authenticated) { - $this->user = $tempuser; - // The login session is started. - $this->loginSessionStarted = true; - if (is_array($this->user)) { - $this->logger->debug('User session finally read', [ - $this->userid_column => $this->user[$this->userid_column], - $this->username_column => $this->user[$this->username_column], - ]); - } - - // User logged in - write that to the log! - if ($this->writeStdLog) { - $this->writelog( - 255, - 1, - 0, - 1, - 'User %s logged in from ###IP###', - [$tempuser[$this->username_column]], - '', - '', - '' - ); - } - - $this->logger->debug( - 'User ' . $tempuser[$this->username_column] . ' authenticated from ' - . GeneralUtility::getIndpEnv('REMOTE_ADDR') - ); - } else { - if (empty($tempuserArr)) { - $logData = [ - 'loginData' => $this->removeSensitiveLoginDataForLoggingInfo($loginData), - ]; - $this->logger->debug('Login failed', $logData); - } - - if (!empty($tempuserArr)) { - $logData = [ - $this->userid_column => $tempuser[$this->userid_column], - $this->username_column => $tempuser[$this->username_column], - ]; - $this->logger->debug('Login failed', $logData); - } - } - } - - /** - * Determines whether a backend user is allowed to access TYPO3. - * - * @return bool True if $GLOBALS[TYPO3_CONF_VARS][BE][adminOnly] is zero. - */ - public function isUserAllowedToLogin() - { - return (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly'] === 0; - } - - /** - * @param array $data - */ - public function setLoginFormData(array $data) - { - $this->loginFormData = $data; - } - - /** - * Returns an info array with Login/Logout data from headers - * - * @return array - * @internal - */ - public function getLoginFormData() - { - return $this->loginFormData; - } -} diff --git a/Classes/Command/AbstractRecordCommandController.php b/Classes/Command/AbstractRecordCommandController.php index 58125343..5ba9c0ef 100644 --- a/Classes/Command/AbstractRecordCommandController.php +++ b/Classes/Command/AbstractRecordCommandController.php @@ -42,13 +42,15 @@ protected function configure() 'b', InputOption::VALUE_NONE, 'If set, is ignored and is an array where each key is a remote ID and each ' - . 'value field data. Each set of remote ID and field data will be processed.' + . 'value field data. Each set of remote ID and field data will be processed.', + false ) ->addOption( 'disableReferenceIndex', null, InputOption::VALUE_NONE, - 'If set, the reference index will not be updated.' + 'If set, the reference index will not be updated.', + false ); } @@ -114,7 +116,7 @@ protected function parseData(InputInterface $input) ); } - if ($input->getOption('batch')) { + if ($input->getOption('batch') === true) { $input->setOption('data', $data); } else { $input->setOption('data', [$input->getArgument('remoteId') => $data]); diff --git a/Classes/Command/ClearRecordHashCommandController.php b/Classes/Command/ClearRecordHashCommandController.php index b73e845d..eaf9c642 100644 --- a/Classes/Command/ClearRecordHashCommandController.php +++ b/Classes/Command/ClearRecordHashCommandController.php @@ -35,7 +35,8 @@ protected function configure() 'contains', 'c', InputOption::VALUE_NONE, - 'The remoteId argument is a partial remote ID. Match all IDs containing the string.' + 'The remoteId argument is a partial remote ID. Match all IDs containing the string.', + false ); } @@ -44,7 +45,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $queryBuilder = $this->getQueryBuilder(); if ($input->getArgument('remoteId') !== null) { - if ($input->getOption('contains')) { + if ($input->getOption('contains') === true) { $queryBuilder->where($queryBuilder->expr()->like( 'remote_id', $queryBuilder->createNamedParameter( @@ -59,10 +60,10 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $rows = (int)$queryBuilder + $rows = $queryBuilder ->update(RemoteIdMappingRepository::TABLE_NAME) ->set('record_hash', '') - ->execute(); + ->executeStatement(); $output->writeln('Cleared hash in ' . $rows . ' rows.'); diff --git a/Classes/Command/CreateCommandController.php b/Classes/Command/CreateCommandController.php index 978f0fb8..c47b7280 100644 --- a/Classes/Command/CreateCommandController.php +++ b/Classes/Command/CreateCommandController.php @@ -32,7 +32,8 @@ protected function configure() 'update', 'u', InputOption::VALUE_NONE, - 'Quietly update the record if it already exists.' + 'Quietly update the record if it already exists.', + false ); } @@ -66,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output) continue; } catch (IdentityConflictException $exception) { - if (!$input->getOption('update')) { + if ($input->getOption('update') === false) { throw $exception; } diff --git a/Classes/Command/PendingRelationsCommandController.php b/Classes/Command/PendingRelationsCommandController.php index 15b3ac4f..611c19b0 100644 --- a/Classes/Command/PendingRelationsCommandController.php +++ b/Classes/Command/PendingRelationsCommandController.php @@ -41,7 +41,8 @@ protected function configure() 'resolve', 'r', InputOption::VALUE_NONE, - 'Attempt to resolve pending relations where both sides exist.' + 'Attempt to resolve pending relations where both sides exist.', + false ); } @@ -63,9 +64,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $queryBuilder = $this->getQueryBuilder(); $counts['_total']['count'] = $queryBuilder - ->count('*') - ->from(PendingRelationsRepository::TABLE_NAME) - ->execute() + ->count('*')->from(PendingRelationsRepository::TABLE_NAME)->executeQuery() ->fetchOne(); if ($counts['_total']['count'] === 0) { @@ -79,11 +78,16 @@ protected function execute(InputInterface $input, OutputInterface $output) $counts['_total']['resolvable'] = $queryBuilder ->count('*') ->from(PendingRelationsRepository::TABLE_NAME, 'p') - ->join('p', RemoteIdMappingRepository::TABLE_NAME, 'm', $queryBuilder->expr()->eq( - 'p.remote_id', - $queryBuilder->quoteIdentifier('m.remote_id') - )) - ->execute() + ->join( + 'p', + RemoteIdMappingRepository::TABLE_NAME, + 'm', + $queryBuilder->expr()->eq( + 'p.remote_id', + $queryBuilder->quoteIdentifier('m.remote_id') + ) + ) + ->executeQuery() ->fetchOne(); $queryBuilder = $this->getQueryBuilder(); @@ -91,9 +95,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $tables = array_column( $queryBuilder ->select('table') - ->from(PendingRelationsRepository::TABLE_NAME) - ->groupBy('table') - ->execute() + ->from(PendingRelationsRepository::TABLE_NAME)->groupBy('table')->executeQuery() ->fetchAllNumeric(), 'table' ); @@ -115,9 +117,12 @@ protected function execute(InputInterface $input, OutputInterface $output) ->count('*') ->from(PendingRelationsRepository::TABLE_NAME) ->where( - $queryBuilder->expr()->eq('table', $queryBuilder->createNamedParameter($table)) + $queryBuilder->expr()->eq( + 'table', + $queryBuilder->createNamedParameter($table) + ) ) - ->execute() + ->executeQuery() ->fetchFirstColumn(); $queryBuilder = $this->getQueryBuilder(); @@ -125,14 +130,22 @@ protected function execute(InputInterface $input, OutputInterface $output) $counts[$table]['resolvable'] = (int)$queryBuilder ->count('*') ->from(PendingRelationsRepository::TABLE_NAME, 'p') - ->join('p', RemoteIdMappingRepository::TABLE_NAME, 'm', $queryBuilder->expr()->eq( - 'p.remote_id', - $queryBuilder->quoteIdentifier('m.remote_id') - )) + ->join( + 'p', + RemoteIdMappingRepository::TABLE_NAME, + 'm', + $queryBuilder->expr()->eq( + 'p.remote_id', + $queryBuilder->quoteIdentifier('m.remote_id') + ) + ) ->where( - $queryBuilder->expr()->eq('p.table', $queryBuilder->createNamedParameter($table)) + $queryBuilder->expr()->eq( + 'p.table', + $queryBuilder->createNamedParameter($table) + ) ) - ->execute() + ->executeQuery() ->fetchFirstColumn(); $rows[] = [ @@ -149,7 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $table->render(); - if (!$input->getOption('resolve')) { + if ($input->getOption('resolve') === false) { return 0; } @@ -158,11 +171,16 @@ protected function execute(InputInterface $input, OutputInterface $output) $resolvableRelations = $queryBuilder ->select('p.*', 'm.table as _foreign_table', 'm.uid_local as _foreign_uid') ->from(PendingRelationsRepository::TABLE_NAME, 'p') - ->join('p', RemoteIdMappingRepository::TABLE_NAME, 'm', $queryBuilder->expr()->eq( - 'p.remote_id', - $queryBuilder->quoteIdentifier('m.remote_id') - )) - ->execute(); + ->join( + 'p', + RemoteIdMappingRepository::TABLE_NAME, + 'm', + $queryBuilder->expr()->eq( + 'p.remote_id', + $queryBuilder->quoteIdentifier('m.remote_id') + ) + ) + ->executeQuery(); if (!($resolvableRelations instanceof Result)) { throw new InvalidQueryResultException( diff --git a/Classes/Command/UpdateCommandController.php b/Classes/Command/UpdateCommandController.php index 4a94616c..fdd683aa 100644 --- a/Classes/Command/UpdateCommandController.php +++ b/Classes/Command/UpdateCommandController.php @@ -32,7 +32,8 @@ protected function configure() 'create', 'c', InputOption::VALUE_NONE, - 'Quietly create the record if it doesn\'t already exist.' + 'Quietly create the record if it doesn\'t already exist.', + false ); } @@ -66,7 +67,7 @@ protected function execute(InputInterface $input, OutputInterface $output) continue; } catch (NotFoundException $exception) { - if (!$input->getOption('create')) { + if ($input->getOption('create') === false) { throw $exception; } diff --git a/Classes/Compatibility/Resource/Security/FileNameValidator.php b/Classes/Compatibility/Resource/Security/FileNameValidator.php deleted file mode 100644 index c576b9a6..00000000 --- a/Classes/Compatibility/Resource/Security/FileNameValidator.php +++ /dev/null @@ -1,91 +0,0 @@ -fileDenyPattern = $fileDenyPattern; - } elseif (isset($GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'])) { - $this->fileDenyPattern = (string)$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern']; - } else { - $this->fileDenyPattern = static::DEFAULT_FILE_DENY_PATTERN; - } - } - - /** - * Verifies the input filename against the 'fileDenyPattern' - * - * Filenames are not allowed to contain control characters. Therefore we - * always filter on [[:cntrl:]]. - * - * @param string $fileName File path to evaluate - * @return bool Returns TRUE if the file name is OK. - */ - public function isValid(string $fileName): bool - { - $pattern = '/[[:cntrl:]]/'; - if ($fileName !== '' && $this->fileDenyPattern !== '') { - $pattern = '/(?:[[:cntrl:]]|' . $this->fileDenyPattern . ')/iu'; - } - return preg_match($pattern, $fileName) === 0; - } - - /** - * Find out if there is a custom file deny pattern configured. - * - * @return bool - */ - public function customFileDenyPatternConfigured(): bool - { - return $this->fileDenyPattern !== self::DEFAULT_FILE_DENY_PATTERN; - } - - /** - * Checks if the given file deny pattern does not have parts that the default pattern should - * recommend. Used in status overview. - * - * @return bool - */ - public function missingImportantPatterns(): bool - { - $defaultParts = explode('|', self::DEFAULT_FILE_DENY_PATTERN); - $givenParts = explode('|', $this->fileDenyPattern); - $missingParts = array_diff($defaultParts, $givenParts); - return !empty($missingParts); - } -} diff --git a/Classes/Configuration/ConfigurationProvider.php b/Classes/Configuration/ConfigurationProvider.php index ec55660e..ac555849 100644 --- a/Classes/Configuration/ConfigurationProvider.php +++ b/Classes/Configuration/ConfigurationProvider.php @@ -36,12 +36,12 @@ public function __construct() $this->extensionConfiguration['log'] = (int)( getenv('APP_INTEREST_LOG') !== false ? getenv('APP_INTEREST_LOG') - : $this->extensionConfiguration['log'] + : ($this->extensionConfiguration['log'] ?? 0) ); $this->extensionConfiguration['logMs'] = (int)( getenv('APP_INTEREST_LOG_MS') !== false ? getenv('APP_INTEREST_LOG_MS') - : $this->extensionConfiguration['logMs'] + : ($this->extensionConfiguration['logMs'] ?? 0) ); } diff --git a/Classes/Console/OptimizedCommandRequestHandler.php b/Classes/Console/OptimizedCommandRequestHandler.php deleted file mode 100644 index 2f8de14e..00000000 --- a/Classes/Console/OptimizedCommandRequestHandler.php +++ /dev/null @@ -1,34 +0,0 @@ - $command) { - if ($commandName === $GLOBALS['argv'][1] && !in_array($GLOBALS['argv'][1], ['help', 'list'])) { - /** @var Command $command */ - $this->application->add($command); - } - } - } -} diff --git a/Classes/DataHandling/Operation/AbstractConstructiveRecordOperation.php b/Classes/DataHandling/Operation/AbstractConstructiveRecordOperation.php new file mode 100644 index 00000000..ad313ee0 --- /dev/null +++ b/Classes/DataHandling/Operation/AbstractConstructiveRecordOperation.php @@ -0,0 +1,76 @@ +recordRepresentation = $recordRepresentation; + $this->dataForDataHandler = $this->recordRepresentation->getData(); + $this->metaData = $metaData ?? []; + + $this->configurationProvider = GeneralUtility::makeInstance(ConfigurationProvider::class); + + $this->mappingRepository = GeneralUtility::makeInstance(RemoteIdMappingRepository::class); + + $this->pendingRelationsRepository = GeneralUtility::makeInstance(PendingRelationsRepository::class); + + $this->createTranslationFields(); + + $this->contentObjectRenderer = $this->createContentObjectRenderer(); + + if (isset($this->getDataForDataHandler()['pid']) || $this instanceof CreateRecordOperation) { + $this->storagePid = $this->resolveStoragePid(); + } + + $this->hash = md5(static::class . serialize($this->getArguments())); + + try { + GeneralUtility::makeInstance(EventDispatcher::class)->dispatch(new BeforeRecordOperationEvent($this)); + } catch (StopRecordOperationException $exception) { + $this->operationStopped = true; + + throw $exception; + } + + $this->validateFieldNames(); + + $this->contentObjectRenderer->data['language'] + = $this->getLanguage() === null ? null : $this->getLanguage()->getHreflang(); + + $this->applyFieldDataTransformations(); + + $this->prepareRelations(); + + $this->dataHandler = GeneralUtility::makeInstance(DataHandler::class); + $this->dataHandler->start([], []); + + if (!isset($this->getDataForDataHandler()['pid']) && $this instanceof CreateRecordOperation) { + $this->dataForDataHandler['pid'] = $this->storagePid; + } + } +} diff --git a/Classes/DataHandling/Operation/AbstractRecordOperation.php b/Classes/DataHandling/Operation/AbstractRecordOperation.php index 7db1373d..253ea9bf 100644 --- a/Classes/DataHandling/Operation/AbstractRecordOperation.php +++ b/Classes/DataHandling/Operation/AbstractRecordOperation.php @@ -7,8 +7,6 @@ use Pixelant\Interest\Configuration\ConfigurationProvider; use Pixelant\Interest\DataHandling\DataHandler; use Pixelant\Interest\DataHandling\Operation\Event\AfterRecordOperationEvent; -use Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent; -use Pixelant\Interest\DataHandling\Operation\Event\Exception\StopRecordOperationException; use Pixelant\Interest\DataHandling\Operation\Exception\ConflictException; use Pixelant\Interest\DataHandling\Operation\Exception\DataHandlerErrorException; use Pixelant\Interest\DataHandling\Operation\Exception\InvalidArgumentException; @@ -16,16 +14,15 @@ use Pixelant\Interest\Domain\Model\Dto\RecordRepresentation; use Pixelant\Interest\Domain\Repository\PendingRelationsRepository; use Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository; -use Pixelant\Interest\Utility\CompatibilityUtility; use Pixelant\Interest\Utility\DatabaseUtility; use Pixelant\Interest\Utility\RelationUtility; use Pixelant\Interest\Utility\TcaUtility; use TYPO3\CMS\Backend\Utility\BackendUtility; +use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; use TYPO3\CMS\Core\Site\Entity\SiteLanguage; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; -use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; /** * Abstract class for handling record operations like create, delete, read, and update. @@ -115,61 +112,6 @@ abstract class AbstractRecordOperation */ protected array $updatedForeignFieldValues = []; - /** - * @param RecordRepresentation $recordRepresentation to perform the operation on. - * @param array|null $metaData any additional data items not to be persisted but used in processing. - * - * @throws StopRecordOperationException is re-thrown from BeforeRecordOperationEvent handlers - */ - public function __construct( - RecordRepresentation $recordRepresentation, - ?array $metaData = [] - ) { - $this->recordRepresentation = $recordRepresentation; - $this->dataForDataHandler = $this->recordRepresentation->getData(); - $this->metaData = $metaData ?? []; - - $this->configurationProvider = GeneralUtility::makeInstance(ConfigurationProvider::class); - - $this->mappingRepository = GeneralUtility::makeInstance(RemoteIdMappingRepository::class); - - $this->pendingRelationsRepository = GeneralUtility::makeInstance(PendingRelationsRepository::class); - - $this->createTranslationFields(); - - $this->contentObjectRenderer = $this->createContentObjectRenderer(); - - if (isset($this->getDataForDataHandler()['pid']) || $this instanceof CreateRecordOperation) { - $this->storagePid = $this->resolveStoragePid(); - } - - $this->hash = md5(static::class . serialize($this->getArguments())); - - try { - CompatibilityUtility::dispatchEvent(new BeforeRecordOperationEvent($this)); - } catch (StopRecordOperationException $exception) { - $this->operationStopped = true; - - throw $exception; - } - - $this->validateFieldNames(); - - $this->contentObjectRenderer->data['language'] - = $this->getLanguage() === null ? null : $this->getLanguage()->getHreflang(); - - $this->applyFieldDataTransformations(); - - $this->prepareRelations(); - - $this->dataHandler = GeneralUtility::makeInstance(DataHandler::class); - $this->dataHandler->start([], []); - - if (!isset($this->getDataForDataHandler()['pid']) && $this instanceof CreateRecordOperation) { - $this->dataForDataHandler['pid'] = $this->storagePid; - } - } - public function __invoke() { if ($this->operationStopped) { @@ -192,7 +134,7 @@ public function __invoke() $this->dataHandler->process_cmdmap(); } - if (!empty($this->dataHandler->errorLog)) { + if (count($this->dataHandler->errorLog) > 0) { throw new DataHandlerErrorException( 'Error occurred during the data handling: ' . implode(', ', $this->dataHandler->errorLog) . ' Datamap: ' . json_encode($this->dataHandler->datamap) @@ -233,7 +175,7 @@ public function __invoke() $this->persistPendingRelations(); - CompatibilityUtility::dispatchEvent(new AfterRecordOperationEvent($this)); + GeneralUtility::makeInstance(EventDispatcher::class)->dispatch(new AfterRecordOperationEvent($this)); } /** @@ -258,7 +200,7 @@ public function getArguments(): array * * @throws ConflictException */ - private function validateFieldNames(): void + protected function validateFieldNames(): void { $fieldsNotInTca = array_diff_key( $this->getDataForDataHandler(), @@ -280,7 +222,7 @@ private function validateFieldNames(): void * @throws NotFoundException * @throws InvalidArgumentException */ - private function resolveStoragePid(): int + protected function resolveStoragePid(): int { if (($GLOBALS['TCA'][$this->getTable()]['ctrl']['rootLevel'] ?? null) === 1) { return 0; @@ -304,7 +246,7 @@ private function resolveStoragePid(): int $settings['persistence.']['storagePid.'] ?? [] ); - if (empty($pid)) { + if ($pid === null || $pid === '') { $pid = 0; } @@ -321,17 +263,9 @@ private function resolveStoragePid(): int /** * @return ContentObjectRenderer */ - private function createContentObjectRenderer(): ContentObjectRenderer + protected function createContentObjectRenderer(): ContentObjectRenderer { - if (CompatibilityUtility::typo3VersionIsLessThan('10')) { - /** @var ContentObjectRenderer $contentObjectRenderer */ - $contentObjectRenderer = GeneralUtility::makeInstance( - ContentObjectRenderer::class, - GeneralUtility::makeInstance(TypoScriptFrontendController::class, null, 0, 0) - ); - } else { - $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); - } + $contentObjectRenderer = GeneralUtility::makeInstance(ContentObjectRenderer::class); $contentObjectRenderer->data = [ 'table' => $this->getTable(), @@ -348,7 +282,7 @@ private function createContentObjectRenderer(): ContentObjectRenderer /** * Applies field value transformations defined in `tx_interest.transformations..`. */ - private function applyFieldDataTransformations(): void + protected function applyFieldDataTransformations(): void { $settings = $this->configurationProvider->getSettings(); @@ -368,7 +302,6 @@ private function applyFieldDataTransformations(): void * information is temporarily added to $this->pendingRelations and persisted using persistPendingRelations(). * * @see persistPendingRelations() - * @throws \TYPO3\CMS\Extbase\Object\Exception */ protected function prepareRelations() { @@ -490,7 +423,7 @@ protected function isRelationalField(string $field): bool && isset($tca['foreign_table']) ) || ( - $tca['type'] === 'category' + in_array($tca['type'], ['category', 'file', 'image'], true) ); } @@ -520,7 +453,7 @@ protected function isSingleRelationField(string $field): bool $tca = $this->getTcaFieldConfigurationAndRespectColumnsOverrides($field); - return ($tca['maxitems'] ?? 0) === 1 && empty($tca['foreign_table']); + return ($tca['maxitems'] ?? 0) === 1 && (($tca['foreign_table'] ?? '') === ''); } /** @@ -559,12 +492,18 @@ protected function createTranslationFields() = $this->getLanguage()->getLanguageId(); $transOrigPointerField = TcaUtility::getTransOrigPointerField($this->getTable()); - if (!empty($transOrigPointerField) && !isset($this->dataForDataHandler[$transOrigPointerField])) { + if ( + ($transOrigPointerField ?? '') !== '' + && !isset($this->dataForDataHandler[$transOrigPointerField]) + ) { $this->dataForDataHandler[$transOrigPointerField] = $baseLanguageRemoteId; } $translationSourceField = TcaUtility::getTranslationSourceField($this->getTable()); - if (!empty($translationSourceField) && !isset($this->dataForDataHandler[$translationSourceField])) { + if ( + ($translationSourceField ?? '') !== '' + && !isset($this->dataForDataHandler[$translationSourceField]) + ) { $this->dataForDataHandler[$translationSourceField] = $baseLanguageRemoteId; } } @@ -604,24 +543,6 @@ public function getRemoteId(): string return $this->recordRepresentation->getRecordInstanceIdentifier()->getRemoteIdWithAspects(); } - /** - * @return array - * @deprecated Will be removed in v2. Use getDataForDataHandler() instead. - */ - public function getData(): array - { - return $this->getDataForDataHandler(); - } - - /** - * @param array $data - * @deprecated Will be removed in v2. Use setDataForDataHandler() instead. - */ - public function setData(array $data) - { - $this->setDataForDataHandler($data); - } - /** * @return array */ @@ -792,32 +713,31 @@ protected function reduceSingleValueArrayToScalar(): void */ protected function reduceFieldSingleValueArrayToScalar(string $fieldName): void { - foreach ($this->dataForDataHandler as $fieldName => $fieldValue) { - if (is_array($fieldValue) && count($fieldValue) <= 1) { - if ( - $fieldValue === [] - && ( - $fieldName === TcaUtility::getTranslationSourceField($this->getTable()) - || $fieldName === TcaUtility::getTransOrigPointerField($this->getTable()) - ) - ) { - $this->dataForDataHandler[$fieldName] = 0; - - continue; - } + if (isset($this->dataForDataHandler[$fieldName]) && $this->dataForDataHandler[$fieldName] === null) { + unset($this->dataForDataHandler[$fieldName]); - $this->dataForDataHandler[$fieldName] = $fieldValue[array_key_first($fieldValue)] ?? null; + return; + } - // Unset empty single-relation fields (1:n) in new records. - if (count($fieldValue) === 0 && $this->isSingleRelationField($fieldName) && $this->getUid() === 0) { - unset($this->dataForDataHandler[$fieldName]); - } - } + $fieldValue = $this->dataForDataHandler[$fieldName]; - if (isset($this->dataForDataHandler[$fieldName]) && $this->dataForDataHandler[$fieldName] === null) { - unset($this->dataForDataHandler[$fieldName]); - } + if (!is_array($fieldValue)) { + return; } + + if ( + $fieldValue === [] + && ( + $fieldName === TcaUtility::getTranslationSourceField($this->getTable()) + || $fieldName === TcaUtility::getTransOrigPointerField($this->getTable()) + ) + ) { + $this->dataForDataHandler[$fieldName] = 0; + + return; + } + + $this->dataForDataHandler[$fieldName] = implode(',', $fieldValue); } /** @@ -847,10 +767,10 @@ protected function processUpdatedForeignFieldValues(): void foreach ($data as $field => $value) { $newValues = GeneralUtility::trimExplode(',', $value, true); $fieldRelations = RelationUtility::getRelationsFromField($this->getTable(), $id, $field); - foreach ($fieldRelations as $relationTable => $values) { - foreach ($values as $value) { - if (!in_array($value, $newValues)) { - $this->dataHandler->cmdmap[$relationTable][$value]['delete'] = 1; + foreach ($fieldRelations as $relationTable => $relationTableValues) { + foreach ($relationTableValues as $relationTableValue) { + if (!in_array((string)$relationTableValue, $newValues, true)) { + $this->dataHandler->cmdmap[$relationTable][$relationTableValue]['delete'] = 1; } } } diff --git a/Classes/DataHandling/Operation/CreateRecordOperation.php b/Classes/DataHandling/Operation/CreateRecordOperation.php index 78480457..06b3a618 100644 --- a/Classes/DataHandling/Operation/CreateRecordOperation.php +++ b/Classes/DataHandling/Operation/CreateRecordOperation.php @@ -13,7 +13,7 @@ /** * Handle a record creation operation. */ -class CreateRecordOperation extends AbstractRecordOperation +class CreateRecordOperation extends AbstractConstructiveRecordOperation { public function __construct( RecordRepresentation $recordRepresentation, @@ -39,7 +39,12 @@ public function __construct( ); } - $uid = $this->getUid() ?: StringUtility::getUniqueId('NEW'); + $uid = $this->getUid(); + + if ($uid === 0) { + $uid = StringUtility::getUniqueId('NEW'); + } + $table = $recordRepresentation->getRecordInstanceIdentifier()->getTable(); $this->dataHandler->datamap[$table][$uid] = $this->getDataForDataHandler(); diff --git a/Classes/DataHandling/Operation/DeleteRecordOperation.php b/Classes/DataHandling/Operation/DeleteRecordOperation.php index b930618d..146a9bff 100644 --- a/Classes/DataHandling/Operation/DeleteRecordOperation.php +++ b/Classes/DataHandling/Operation/DeleteRecordOperation.php @@ -1,9 +1,5 @@ recordRepresentation = $recordRepresentation; $this->mappingRepository = GeneralUtility::makeInstance(RemoteIdMappingRepository::class); @@ -46,7 +46,7 @@ public function __construct( $this->hash = md5(static::class . serialize($this->getArguments())); try { - CompatibilityUtility::dispatchEvent(new BeforeRecordOperationEvent($this)); + GeneralUtility::makeInstance(EventDispatcher::class)->dispatch(new BeforeRecordOperationEvent($this)); } catch (StopRecordOperationException $exception) { $this->operationStopped = true; diff --git a/Classes/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandler.php b/Classes/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandler.php index 4635843e..da52e77c 100644 --- a/Classes/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandler.php +++ b/Classes/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandler.php @@ -71,7 +71,7 @@ protected function getMmFieldConfigurations(): array $persistedRecordData ); - if (!empty($fieldConfiguration['MM'] ?? '')) { + if (($fieldConfiguration['MM'] ?? '') !== '') { $fieldConfigurations[$fieldName] = $fieldConfiguration; } } @@ -112,16 +112,13 @@ protected function orderOnForeignSideOfRelation(string $table, int $relationId): RelationSortingAsMetaDataEventHandler::class ) ?? []; - $fieldName = null; - $orderingIntent = null; - foreach ($orderingIntents as $fieldName => $orderingIntent) { - if (in_array($localRemoteId, $orderingIntent)) { + if (in_array($localRemoteId, $orderingIntent, true)) { break; } } - if ($orderingIntent === null) { + if (!isset($fieldName) || !isset($orderingIntent)) { return []; } @@ -188,7 +185,7 @@ protected function generateSortingData(): array foreach ($this->getMmFieldConfigurations() as $fieldName => $fieldConfiguration) { $relationIds = $this->event->getRecordOperation()->getDataForDataHandler()[$fieldName] ?? []; - if (empty($relationIds)) { + if ($relationIds === [] || $relationIds === '') { continue; } @@ -232,7 +229,7 @@ protected function persistData(array $data): void $dataHandler->start($data, []); $dataHandler->process_datamap(); - if (!empty($dataHandler->errorLog)) { + if (count($dataHandler->errorLog) > 0) { throw new DataHandlerErrorException( 'Error occurred during foreign-side relation ordering in remote ID based on relations' . ' from remote ID "' . $this->event->getRecordOperation()->getRemoteId() . '": ' diff --git a/Classes/DataHandling/Operation/Event/Handler/PersistFileDataEventHandler.php b/Classes/DataHandling/Operation/Event/Handler/PersistFileDataEventHandler.php index 33f3cd07..cb493bf9 100644 --- a/Classes/DataHandling/Operation/Event/Handler/PersistFileDataEventHandler.php +++ b/Classes/DataHandling/Operation/Event/Handler/PersistFileDataEventHandler.php @@ -10,11 +10,11 @@ use Pixelant\Interest\DataHandling\Operation\DeleteRecordOperation; use Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent; use Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEventHandlerInterface; +use Pixelant\Interest\DataHandling\Operation\Event\Exception\StopRecordOperationException; use Pixelant\Interest\DataHandling\Operation\Exception\IdentityConflictException; -use Pixelant\Interest\DataHandling\Operation\Exception\MissingArgumentException; +use Pixelant\Interest\DataHandling\Operation\Exception\InvalidArgumentException; use Pixelant\Interest\DataHandling\Operation\Exception\NotFoundException; use Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository; -use Pixelant\Interest\Utility\CompatibilityUtility; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Http\RequestFactory; use TYPO3\CMS\Core\Resource\DuplicationBehavior; @@ -24,9 +24,11 @@ use TYPO3\CMS\Core\Resource\Exception\InvalidFileNameException; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Resource\InaccessibleFolder; use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceStorage; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\PathUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -63,7 +65,7 @@ public function __invoke(BeforeRecordOperationEvent $event): void $fileBaseName = $data['name'] ?? ''; - if (!CompatibilityUtility::getFileNameValidator()->isValid($fileBaseName)) { + if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fileBaseName)) { throw new InvalidFileNameException( 'Invalid file name: "' . $fileBaseName . '"', 1634664683340 @@ -101,8 +103,7 @@ public function __invoke(BeforeRecordOperationEvent $event): void $downloadFolder = $this->resourceFactory->getFolderObjectFromCombinedIdentifier('0:fileadmin/_temp_/'); } - /** @var File $file */ - [$data, $file] = $this->getFileWithContent($data, $downloadFolder, $fileBaseName); + $file = $this->getFileWithContent($data, $downloadFolder, $fileBaseName); if ($replaceFile) { $temporaryFile = $file; @@ -204,7 +205,7 @@ protected function handleBase64Input(string $fileData): string * @throws ClientException * @throws NotFoundException */ - protected function handleUrlInput(string $url): ?string + protected function fetchContentFromUrl(string $url): ?string { /** @var RequestFactory $httpClient */ $httpClient = GeneralUtility::makeInstance(RequestFactory::class); @@ -216,11 +217,11 @@ protected function handleUrlInput(string $url): ?string $headers = []; - if (!empty($metaData['date'])) { + if (($metaData['date'] ?? []) !== []) { $headers['If-Modified-Since'] = $metaData['date']; } - if (!empty($metaData['etag'])) { + if (($metaData['etag'] ?? []) !== []) { $headers['If-None-Match'] = $metaData['etag']; } @@ -272,7 +273,7 @@ protected function renameFile(File $file, string $fileName) * @param ResourceStorage $storage * @param array $persistenceSettings * @param string $fileBaseName - * @return Folder|\TYPO3\CMS\Core\Resource\InaccessibleFolder|void + * @return Folder|InaccessibleFolder|void */ protected function getDownloadFolder( string $storagePath, @@ -348,33 +349,28 @@ protected function getFileFromMediaUrl(string $url, Folder $downloadFolder, stri * @param array $data * @param Folder $downloadFolder * @param string $fileBaseName - * @return array + * @return File * @throws InvalidFileNameException - * @throws MissingArgumentException */ - protected function getFileWithContent(array $data, Folder $downloadFolder, string $fileBaseName): array + protected function getFileWithContent(array $data, Folder $downloadFolder, string $fileBaseName): File { $file = null; + $fileContent = null; - if (!empty($data['fileData'])) { + if (($data['fileData'] ?? '') !== '') { $fileContent = $this->handleBase64Input($data['fileData']); - } else { - if ( - empty($data['url']) - && get_class($this->event->getRecordOperation()) === CreateRecordOperation::class - ) { - throw new MissingArgumentException( - 'Cannot download file. Missing property "url" in the data.', - 1634667221986 - ); - } - if (!empty($data['url'])) { - $file = $this->getFileFromMediaUrl($data['url'], $downloadFolder, $fileBaseName); + } elseif (($data['url'] ?? '') !== '') { + $file = $this->getFileFromMediaUrl($data['url'], $downloadFolder, $fileBaseName); - if ($file === null) { - $fileContent = $this->handleUrlInput($data['url']); - } + if ($file === null) { + $fileContent = $this->fetchContentFromUrl($data['url']); } + } else { + $fileContent = ''; + } + + if ($fileContent === '' || ($file !== null && $file->getSize() > 0)) { + $this->handleEmptyFile(); } if ($fileBaseName === '' && $file === null) { @@ -386,13 +382,11 @@ protected function getFileWithContent(array $data, Folder $downloadFolder, strin if ($file === null) { $file = $this->createFileObject($downloadFolder, $fileBaseName); - } - if (!empty($fileContent)) { $file->setContents($fileContent); } - return[$data, $file]; + return $file; } /** @@ -413,10 +407,12 @@ protected function getUniqueFileName(string $fileName, Folder $folder): string $fileInfo = PathUtility::pathinfo($fileName); - $originalExtension = ($fileInfo['extension'] ?? '') ? '.' . $fileInfo['extension'] : ''; + $originalExtension = strlen($fileInfo['extension'] ?? '') > 0 ? '.' . $fileInfo['extension'] : ''; $fileName = $fileInfo['filename']; + $newFileName = ''; + for ($a = 1; $a <= $maxNumber + 1; $a++) { if ($a <= $maxNumber) { $insert = '_' . sprintf('%02d', $a); @@ -467,4 +463,23 @@ protected function handleExistingFile(string $fileBaseName, Folder $downloadFold return [$fileBaseName, $replaceFile]; } + + /** + * Handle empty files based on instructions in $data[emptyFileHandling]. + * + * @throws StopRecordOperationException if $data[emptyFileHandling] === 1 + * @throws InvalidArgumentException if $data[emptyFileHandling] === 2 + */ + protected function handleEmptyFile(): void + { + $handleEmptyFile = GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->get('interest', 'handleEmptyFile') ?? 0; + + switch ((int)$handleEmptyFile) { + case 1: + throw new StopRecordOperationException('Empty file', 1692921622763); + case 2: + throw new InvalidArgumentException('Empty file', 1692921660432); + } + } } diff --git a/Classes/DataHandling/Operation/Event/Handler/ProcessDeferredRecordOperationsEventHandler.php b/Classes/DataHandling/Operation/Event/Handler/ProcessDeferredRecordOperationsEventHandler.php index 190664af..abbb8ed1 100644 --- a/Classes/DataHandling/Operation/Event/Handler/ProcessDeferredRecordOperationsEventHandler.php +++ b/Classes/DataHandling/Operation/Event/Handler/ProcessDeferredRecordOperationsEventHandler.php @@ -28,10 +28,16 @@ public function __invoke(AfterRecordOperationEvent $event): void $previousHash = ''; foreach ($repository->get($event->getRecordOperation()->getRemoteId()) as $deferredRow) { + $deferredRowClassParents = class_parents($deferredRow['class']); + + if (!is_array($deferredRowClassParents)) { + $deferredRowClassParents = []; + } + if ( $previousHash !== $deferredRow['_hash'] && $deferredRow['class'] !== DeleteRecordOperation::class - && !in_array(DeleteRecordOperation::class, class_parents($deferredRow['class']) ?: []) + && !in_array(DeleteRecordOperation::class, $deferredRowClassParents, true) ) { $previousHash = $deferredRow['_hash']; @@ -41,7 +47,7 @@ public function __invoke(AfterRecordOperationEvent $event): void } catch (IdentityConflictException $exception) { if ( $deferredRow['class'] === CreateRecordOperation::class - || in_array(CreateRecordOperation::class, class_parents($deferredRow['class']) ?: []) + || in_array(CreateRecordOperation::class, $deferredRowClassParents, true) ) { $deferredOperation = new UpdateRecordOperation(... $deferredRow['arguments']); } else { diff --git a/Classes/DataHandling/Operation/Event/Handler/RelationSortingAsMetaDataEventHandler.php b/Classes/DataHandling/Operation/Event/Handler/RelationSortingAsMetaDataEventHandler.php index 452ef84e..5c992e15 100644 --- a/Classes/DataHandling/Operation/Event/Handler/RelationSortingAsMetaDataEventHandler.php +++ b/Classes/DataHandling/Operation/Event/Handler/RelationSortingAsMetaDataEventHandler.php @@ -63,7 +63,7 @@ protected function getSortedMmRelationFieldConfigurations(): array ); if ( - !empty($fieldConfiguration['MM'] ?? '') + ($fieldConfiguration['MM'] ?? '') !== '' && (!isset($fieldConfiguration['maxitems']) || $fieldConfiguration['maxitems'] > 1) ) { $fieldConfigurations[$fieldName] = $fieldConfiguration; @@ -86,7 +86,7 @@ protected function addSortingIntentToMetaData(array $fieldConfigurations) foreach ($fieldConfigurations as $fieldName => $configuration) { $sortingIntent = $recordOperation->getDataForDataHandler()[$fieldName] ?? []; - if (empty($sortingIntent)) { + if (($sortingIntent ?? []) === []) { continue; } diff --git a/Classes/DataHandling/Operation/UpdateRecordOperation.php b/Classes/DataHandling/Operation/UpdateRecordOperation.php index 3b712c6d..397b5669 100644 --- a/Classes/DataHandling/Operation/UpdateRecordOperation.php +++ b/Classes/DataHandling/Operation/UpdateRecordOperation.php @@ -12,7 +12,7 @@ /** * Performs an update operation on a record. */ -class UpdateRecordOperation extends AbstractRecordOperation +class UpdateRecordOperation extends AbstractConstructiveRecordOperation { public function __construct( RecordRepresentation $recordRepresentation, diff --git a/Classes/Domain/Model/Dto/RecordInstanceIdentifier.php b/Classes/Domain/Model/Dto/RecordInstanceIdentifier.php index 62c0ffd5..eb24ad6d 100644 --- a/Classes/Domain/Model/Dto/RecordInstanceIdentifier.php +++ b/Classes/Domain/Model/Dto/RecordInstanceIdentifier.php @@ -192,7 +192,7 @@ public function removeAspectsFromRemoteId(string $remoteId): string */ protected function resolveLanguage(?string $language): ?SiteLanguage { - if (!TcaUtility::isLocalizable($this->getTable()) || empty($language)) { + if (!TcaUtility::isLocalizable($this->getTable()) || ($language ?? '') === '') { return null; } diff --git a/Classes/Domain/Repository/DeferredRecordOperationRepository.php b/Classes/Domain/Repository/DeferredRecordOperationRepository.php index 7b54f093..c877f5a1 100644 --- a/Classes/Domain/Repository/DeferredRecordOperationRepository.php +++ b/Classes/Domain/Repository/DeferredRecordOperationRepository.php @@ -4,7 +4,7 @@ namespace Pixelant\Interest\Domain\Repository; -use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Result; use Pixelant\Interest\DataHandling\Operation\AbstractRecordOperation; use Pixelant\Interest\Domain\Model\Dto\RecordInstanceIdentifier; use Pixelant\Interest\Domain\Model\Dto\RecordRepresentation; @@ -25,14 +25,13 @@ public function add(string $dependentRemoteId, AbstractRecordOperation $operatio $queryBuilder = $this->getQueryBuilder(); $queryBuilder - ->insert(self::TABLE_NAME) - ->values([ + ->insert(self::TABLE_NAME)->values([ 'crdate' => time(), 'dependent_remote_id' => $dependentRemoteId, 'class' => get_class($operation), 'arguments' => serialize($operation->getRecordRepresentation()), ]) - ->execute(); + ->executeStatement(); } /** @@ -56,7 +55,7 @@ public function get(string $dependentRemoteId): array ) ) ->orderBy('crdate') - ->execute(); + ->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( @@ -110,6 +109,6 @@ public function delete(int $uid) $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT) ) ) - ->execute(); + ->executeStatement(); } } diff --git a/Classes/Domain/Repository/PendingRelationsRepository.php b/Classes/Domain/Repository/PendingRelationsRepository.php index 29b79d2d..31f98eb3 100644 --- a/Classes/Domain/Repository/PendingRelationsRepository.php +++ b/Classes/Domain/Repository/PendingRelationsRepository.php @@ -4,7 +4,7 @@ namespace Pixelant\Interest\Domain\Repository; -use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Result; use Pixelant\Interest\Domain\Repository\Exception\InvalidQueryResultException; /** @@ -28,11 +28,13 @@ public function get(string $remoteId): array $result = $queryBuilder ->select('*') ->from(self::TABLE_NAME) - ->where($queryBuilder->expr()->eq( - 'remote_id', - $queryBuilder->createNamedParameter($remoteId, \PDO::PARAM_STR) - )) - ->execute(); + ->where( + $queryBuilder->expr()->eq( + 'remote_id', + $queryBuilder->createNamedParameter($remoteId, \PDO::PARAM_STR) + ) + ) + ->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( @@ -70,7 +72,6 @@ public function set(string $table, string $field, int $uid, array $remoteIds): v * @param string $field * @param int $uid * @param string $remoteId - * @throws \Doctrine\DBAL\DBALException */ public function setSingle(string $table, string $field, int $uid, string $remoteId) { @@ -84,7 +85,7 @@ public function setSingle(string $table, string $field, int $uid, string $remote 'field' => $field, 'record_uid' => $uid, ]) - ->execute(); + ->executeStatement(); } /** @@ -112,9 +113,9 @@ public function removeLocal(string $table, string $field, int $uid): void $queryBuilder->expr()->eq( 'record_uid', $queryBuilder->createNamedParameter($uid) - ), + ) ) - ->execute(); + ->executeStatement(); } /** @@ -134,6 +135,6 @@ public function removeRemote(string $remoteId): void $queryBuilder->createNamedParameter($remoteId) ) ) - ->execute(); + ->executeStatement(); } } diff --git a/Classes/Domain/Repository/RemoteIdMappingRepository.php b/Classes/Domain/Repository/RemoteIdMappingRepository.php index 431c108f..dbc6fb2f 100644 --- a/Classes/Domain/Repository/RemoteIdMappingRepository.php +++ b/Classes/Domain/Repository/RemoteIdMappingRepository.php @@ -4,7 +4,7 @@ namespace Pixelant\Interest\Domain\Repository; -use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Result; use Pixelant\Interest\DataHandling\Operation\AbstractRecordOperation; use Pixelant\Interest\DataHandling\Operation\Exception\IdentityConflictException; use Pixelant\Interest\Domain\Repository\Exception\InvalidQueryResultException; @@ -61,7 +61,7 @@ public function get(string $remoteId, ?AbstractRecordOperation $recordOperation $queryBuilder->createNamedParameter($remoteId, \PDO::PARAM_STR) ) ) - ->execute(); + ->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( @@ -140,7 +140,7 @@ public function add( 'touched' => time(), 'metadata' => json_encode(self::$unmappedMetaDataEntries[$remoteId] ?? []), ]) - ->execute(); + ->executeStatement(); self::$remoteToLocalIdCache[$remoteId] = $uid; self::$remoteIdToTableCache[$remoteId] = $tableName; @@ -159,11 +159,13 @@ public function touch(string $remoteId): void $queryBuilder ->update(self::TABLE_NAME) ->set('touched', time()) - ->where($queryBuilder->expr()->eq( - 'remote_id', - $queryBuilder->createNamedParameter($remoteId) - )) - ->execute(); + ->where( + $queryBuilder->expr()->eq( + 'remote_id', + $queryBuilder->createNamedParameter($remoteId) + ) + ) + ->executeStatement(); } /** @@ -179,11 +181,13 @@ public function touched(string $remoteId): int return (int)$queryBuilder ->select('touched') ->from(self::TABLE_NAME) - ->where($queryBuilder->expr()->eq( - 'remote_id', - $queryBuilder->createNamedParameter($remoteId) - )) - ->execute() + ->where( + $queryBuilder->expr()->eq( + 'remote_id', + $queryBuilder->createNamedParameter($remoteId) + ) + ) + ->executeQuery() ->fetchOne(); } @@ -208,7 +212,7 @@ public function findAllUntouchedSince(int $timestamp, bool $excludeManual = true return $queryBuilder ->select('remote_id') ->from(self::TABLE_NAME) - ->execute() + ->executeQuery() ->fetchFirstColumn(); } @@ -233,7 +237,7 @@ public function findAllTouchedSince(int $timestamp, bool $excludeManual = true): return $queryBuilder ->select('remote_id') ->from(self::TABLE_NAME) - ->execute() + ->executeQuery() ->fetchFirstColumn(); } @@ -242,7 +246,6 @@ public function findAllTouchedSince(int $timestamp, bool $excludeManual = true): * * @param string $remoteId * @return bool - * @throws \TYPO3\CMS\Extbase\Object\Exception */ public function exists(string $remoteId): bool { @@ -265,11 +268,13 @@ public function remove(string $remoteId, ?AbstractRecordOperation $recordOperati $queryBuilder ->delete(self::TABLE_NAME) - ->where($queryBuilder->expr()->eq( - 'remote_id', - $queryBuilder->createNamedParameter($remoteId) - )) - ->execute(); + ->where( + $queryBuilder->expr()->eq( + 'remote_id', + $queryBuilder->createNamedParameter($remoteId) + ) + ) + ->executeStatement(); } /** @@ -288,12 +293,18 @@ public function getRemoteId(string $table, int $uid) ->select('remote_id') ->from(self::TABLE_NAME) ->where( - $queryBuilder->expr()->andX( - $queryBuilder->expr()->eq('table', $queryBuilder->createNamedParameter($table)), - $queryBuilder->expr()->eq('uid_local', $queryBuilder->createNamedParameter($uid)) + $queryBuilder->expr()->and( + $queryBuilder->expr()->eq( + 'table', + $queryBuilder->createNamedParameter($table) + ), + $queryBuilder->expr()->eq( + 'uid_local', + $queryBuilder->createNamedParameter($uid) + ) ) ) - ->execute(); + ->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( @@ -319,11 +330,13 @@ public function update(AbstractRecordOperation $recordOperation) ->set('record_hash', $recordOperation->getHash()) ->set('tstamp', time()) ->set('touched', time()) - ->where($queryBuilder->expr()->eq( - 'remote_id', - $queryBuilder->createNamedParameter($recordOperation->getRemoteId()) - )) - ->execute(); + ->where( + $queryBuilder->expr()->eq( + 'remote_id', + $queryBuilder->createNamedParameter($recordOperation->getRemoteId()) + ) + ) + ->executeStatement(); } /** @@ -356,7 +369,7 @@ public function isSameAsPrevious(AbstractRecordOperation $recordOperation): bool $queryBuilder->createNamedParameter($recordOperation->getHash()) ) ) - ->execute(); + ->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( @@ -388,7 +401,7 @@ public function getMetaData(string $remoteId): array $queryBuilder->createNamedParameter($remoteId, \PDO::PARAM_STR) ) ) - ->execute(); + ->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( @@ -456,11 +469,13 @@ public function setMetaDataValue(string $remoteId, string $key, $value) ->update(self::TABLE_NAME) ->set('metadata', json_encode($metaData)) ->set('tstamp', time()) - ->where($queryBuilder->expr()->eq( - 'remote_id', - $queryBuilder->createNamedParameter($remoteId) - )) - ->execute(); + ->where( + $queryBuilder->expr()->eq( + 'remote_id', + $queryBuilder->createNamedParameter($remoteId) + ) + ) + ->executeStatement(); } /** diff --git a/Classes/Domain/Repository/TokenRepository.php b/Classes/Domain/Repository/TokenRepository.php index f4c61465..4f587f12 100644 --- a/Classes/Domain/Repository/TokenRepository.php +++ b/Classes/Domain/Repository/TokenRepository.php @@ -4,7 +4,7 @@ namespace Pixelant\Interest\Domain\Repository; -use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Result; use Pixelant\Interest\Domain\Repository\Exception\InvalidQueryResultException; use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Crypto\Random; @@ -33,12 +33,12 @@ public function findBackendUserIdByToken(string $token): int ->from(self::TABLE_NAME) ->where( $queryBuilder->expr()->eq('token', $queryBuilder->createNamedParameter($token)), - $queryBuilder->expr()->orX( + $queryBuilder->expr()->or( $queryBuilder->expr()->eq('expiry', 0), $queryBuilder->expr()->gt('expiry', time()) ) ) - ->execute(); + ->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( @@ -77,7 +77,7 @@ public function createTokenForBackendUser(int $id): string 'be_user' => $id, 'expiry' => time() + $tokenLifetime, ]) - ->execute(); + ->executeStatement(); return $token; } diff --git a/Classes/Middleware/RequestMiddleware.php b/Classes/Middleware/RequestMiddleware.php index b23e781e..ef2269a8 100644 --- a/Classes/Middleware/RequestMiddleware.php +++ b/Classes/Middleware/RequestMiddleware.php @@ -5,7 +5,6 @@ use Pixelant\Interest\Configuration\ConfigurationProvider; use Pixelant\Interest\Middleware\Event\HttpResponseEvent; use Pixelant\Interest\Router\HttpRequestRouter; -use Pixelant\Interest\Utility\CompatibilityUtility; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; @@ -13,6 +12,7 @@ use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; +use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; use TYPO3\CMS\Core\Utility\GeneralUtility; class RequestMiddleware implements MiddlewareInterface @@ -37,7 +37,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $response = HttpRequestRouter::route($request); - $response = CompatibilityUtility::dispatchEvent( + $response = GeneralUtility::makeInstance(EventDispatcher::class)->dispatch( new HttpResponseEvent($response) )->getResponse(); @@ -89,7 +89,7 @@ protected function logRequest( 'response_headers' => substr(json_encode($response->getHeaders()), 0, 65535), 'response_body' => substr((string)$response->getBody(), 0, 16777215), ]) - ->execute(); + ->executeStatement(); } } } diff --git a/Classes/RequestHandler/AbstractRecordRequestHandler.php b/Classes/RequestHandler/AbstractRecordRequestHandler.php index 23e30685..053895c5 100644 --- a/Classes/RequestHandler/AbstractRecordRequestHandler.php +++ b/Classes/RequestHandler/AbstractRecordRequestHandler.php @@ -13,6 +13,7 @@ use Pixelant\Interest\RequestHandler\ExceptionConverter\OperationToRequestHandlerExceptionConverter; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\StreamInterface; use TYPO3\CMS\Core\Database\RelationHandler; use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Utility\ArrayUtility; @@ -66,19 +67,19 @@ public function __construct(array $entryPointParts, ServerRequestInterface $requ */ protected function compileData(): void { - $body = null; + $body = ''; - if ($this->getRequest()->getBody()) { + if ($this->getRequest()->getBody() instanceof StreamInterface) { $this->getRequest()->getBody()->rewind(); $body = $this->getRequest()->getBody()->getContents(); } - if (!static::EXPECT_EMPTY_REQUEST && empty($body)) { + if (!static::EXPECT_EMPTY_REQUEST && $body === '') { return; } - if (empty($body)) { + if ($body === '') { $decodedContent = []; } else { $decodedContent = json_decode($body) ?? []; @@ -121,7 +122,7 @@ protected function compileData(): void */ public function handle(): ResponseInterface { - if (empty($this->data)) { + if ($this->data === []) { return GeneralUtility::makeInstance( JsonResponse::class, [ @@ -147,7 +148,7 @@ public function handle(): ResponseInterface ); } - if ($operationCount === 1 && count($exceptions)) { + if ($operationCount === 1 && count($exceptions) > 0) { throw OperationToRequestHandlerExceptionConverter::convert( current(ArrayUtility::flatten($exceptions)), $this->getRequest() @@ -266,7 +267,7 @@ protected function formatDataArray( do { $layerCount++; - $currentLayer = current($currentLayer); + $currentLayer = current((array)$currentLayer); } while ($currentLayer !== false && !$this->isRecordData($currentLayer)); $data = $this->convertObjectToArrayRecursive((array)$data); diff --git a/Classes/RequestHandler/AuthenticateRequestHandler.php b/Classes/RequestHandler/AuthenticateRequestHandler.php index c169e06a..68656e17 100644 --- a/Classes/RequestHandler/AuthenticateRequestHandler.php +++ b/Classes/RequestHandler/AuthenticateRequestHandler.php @@ -4,12 +4,9 @@ namespace Pixelant\Interest\RequestHandler; -use Pixelant\Interest\Authentication\HttpBackendUserAuthentication; use Pixelant\Interest\Domain\Repository\TokenRepository; -use Pixelant\Interest\RequestHandler\Exception\InvalidArgumentException; use Pixelant\Interest\RequestHandler\Exception\UnauthorizedAccessException; use Psr\Http\Message\ResponseInterface; -use TYPO3\CMS\Core\Authentication\LoginType; use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -18,60 +15,10 @@ class AuthenticateRequestHandler extends AbstractRequestHandler /** * @return ResponseInterface * @throws UnauthorizedAccessException - * @throws InvalidArgumentException */ public function handle(): ResponseInterface { - if (strtolower($this->request->getMethod()) !== 'post') { - throw new UnauthorizedAccessException( - 'Authorization requires POST method.', - $this->request - ); - } - - $authorizationHeader = $this->request->getHeader('authorization')[0] - ?? $this->request->getHeader('redirect_http_authorization')[0] - ?? ''; - - [$scheme, $authorizationData] = GeneralUtility::trimExplode(' ', $authorizationHeader, true); - - if ($scheme === null) { - throw new InvalidArgumentException( - 'No authorization scheme provided.', - $this->request - ); - } - - if (strtolower($scheme) !== 'basic') { - throw new InvalidArgumentException( - 'Unknown authorization scheme "' . $scheme . '".', - $this->request - ); - } - - $authorizationData = base64_decode($authorizationData, true); - - if (strpos($authorizationData, ':') === false) { - throw new InvalidArgumentException( - 'Authorization data couldn\'t be decoded. Missing ":" separating username and password.', - $this->request - ); - } - - [$username, $password] = explode(':', $authorizationData); - - /** @var HttpBackendUserAuthentication $backendUser */ - $backendUser = $GLOBALS['BE_USER']; - - $backendUser->setLoginFormData([ - 'status' => LoginType::LOGIN, - 'uname' => $username, - 'uident_text' => $password, - ]); - - $backendUser->checkAuthentication(); - - if (empty($backendUser->user['uid'])) { + if (!$GLOBALS['BE_USER']->isAuthenticated()) { throw new UnauthorizedAccessException( 'Basic login failed.', $this->request @@ -79,7 +26,7 @@ public function handle(): ResponseInterface } $token = GeneralUtility::makeInstance(TokenRepository::class) - ->createTokenForBackendUser($backendUser->user[$backendUser->userid_column]); + ->createTokenForBackendUser($GLOBALS['BE_USER']->getUserId()); return GeneralUtility::makeInstance( JsonResponse::class, diff --git a/Classes/Router/HttpRequestRouter.php b/Classes/Router/HttpRequestRouter.php index 36e3e626..3d9f6c3b 100644 --- a/Classes/Router/HttpRequestRouter.php +++ b/Classes/Router/HttpRequestRouter.php @@ -4,10 +4,10 @@ namespace Pixelant\Interest\Router; -use Pixelant\Interest\Authentication\HttpBackendUserAuthentication; +use Pixelant\Interest\Authentication\HttpBackendUserAuthenticationForTypo3v11; +use Pixelant\Interest\Authentication\HttpBackendUserAuthenticationForTypo3v12; use Pixelant\Interest\DataHandling\Operation\Exception\AbstractException; use Pixelant\Interest\Domain\Repository\TokenRepository; -use Pixelant\Interest\DynamicCompatibility\Authentication\HttpBackendUserAuthenticationBeforeTypo3v11; use Pixelant\Interest\RequestHandler\AuthenticateRequestHandler; use Pixelant\Interest\RequestHandler\CreateOrUpdateRequestHandler; use Pixelant\Interest\RequestHandler\CreateRequestHandler; @@ -24,7 +24,9 @@ use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Context\Context; use TYPO3\CMS\Core\Core\Bootstrap; +use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Domain\Repository\PageRepository; +use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; use TYPO3\CMS\Core\Http\JsonResponse; use TYPO3\CMS\Core\Routing\PageArguments; use TYPO3\CMS\Core\Site\Entity\Site; @@ -90,7 +92,7 @@ public static function route(ServerRequestInterface $request): ResponseInterface } catch (\Throwable $throwable) { $trace = []; - if (CompatibilityUtility::getApplicationContext()->isDevelopment()) { + if (Environment::getContext()->isDevelopment()) { $trace = self::generateExceptionTrace($throwable); } @@ -148,18 +150,24 @@ protected static function authenticateBearerToken(ServerRequestInterface $reques */ protected static function initialize(ServerRequestInterface $request) { - if (CompatibilityUtility::typo3VersionIsLessThan('11')) { + if (CompatibilityUtility::typo3VersionIsLessThan('12.0')) { require_once GeneralUtility::getFileAbsFileName( - 'EXT:interest/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationBeforeTypo3v11.php' + 'EXT:interest/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v11.php' ); // @phpstan-ignore-next-line - Bootstrap::initializeBackendUser(HttpBackendUserAuthenticationBeforeTypo3v11::class); + Bootstrap::initializeBackendUser(HttpBackendUserAuthenticationForTypo3v11::class, $request); } else { - Bootstrap::initializeBackendUser(HttpBackendUserAuthentication::class); - self::bootFrontendController($request); + require_once GeneralUtility::getFileAbsFileName( + 'EXT:interest/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v12.php' + ); + + // @phpstan-ignore-next-line + Bootstrap::initializeBackendUser(HttpBackendUserAuthenticationForTypo3v12::class, $request); } + self::bootFrontendController($request); + ExtensionManagementUtility::loadExtTables(); Bootstrap::initializeLanguageObject(); } @@ -204,7 +212,7 @@ protected static function generateExceptionTrace(\Throwable $throwable): array */ protected static function handleByMethod(ServerRequestInterface $request, array $entryPointParts): ResponseInterface { - $event = CompatibilityUtility::dispatchEvent( + $event = GeneralUtility::makeInstance(EventDispatcher::class)->dispatch( new HttpRequestRouterHandleByEvent($request, $entryPointParts) ); diff --git a/Classes/Updates/RemovePendingRelationsWithEmptyRemoteIdUpdateWizard.php b/Classes/Updates/RemovePendingRelationsWithEmptyRemoteIdUpdateWizard.php index 64ecf15c..846972fa 100644 --- a/Classes/Updates/RemovePendingRelationsWithEmptyRemoteIdUpdateWizard.php +++ b/Classes/Updates/RemovePendingRelationsWithEmptyRemoteIdUpdateWizard.php @@ -38,7 +38,7 @@ public function executeUpdate(): bool ->where( $queryBuilder->expr()->eq('remote_id', $queryBuilder->quote('')) ) - ->execute(); + ->executeStatement(); if ($this->output !== null) { $this->output->writeln('Deleted ' . $deletedCount . ' pending relations with empty remote ID.'); @@ -58,9 +58,12 @@ public function updateNecessary(): bool ->count('*') ->from(PendingRelationsRepository::TABLE_NAME) ->where( - $queryBuilder->expr()->eq('remote_id', $queryBuilder->quote('')) + $queryBuilder->expr()->eq( + 'remote_id', + $queryBuilder->quote('') + ) ) - ->execute() + ->executeQuery() ->fetchOne(); } diff --git a/Classes/Utility/CompatibilityUtility.php b/Classes/Utility/CompatibilityUtility.php index eff480fc..026cc9b2 100644 --- a/Classes/Utility/CompatibilityUtility.php +++ b/Classes/Utility/CompatibilityUtility.php @@ -1,16 +1,13 @@ dispatch( - $eventClassName, - self::classNameToSignalName($eventClassName), - [$event] - ); - - return $event; - } - - /** @var EventDispatcher $eventDispatcher */ - $eventDispatcher = GeneralUtility::makeInstance(EventDispatcher::class); - - return $eventDispatcher->dispatch($event); - } - - /** - * Register a PSR-14 event as a signal slot in TYPO3 v9. - * - * @param string $eventClassName - * @param string $eventHandlerClassName - */ - public static function registerEventHandlerAsSignalSlot(string $eventClassName, string $eventHandlerClassName) - { - if (self::typo3VersionIsGreaterThanOrEqualTo('10')) { - return; - } - - /** @var Dispatcher $signalSlotDispatcher */ - $signalSlotDispatcher = GeneralUtility::makeInstance(Dispatcher::class); - - $signalSlotDispatcher->connect( - $eventClassName, - self::classNameToSignalName($eventClassName), - $eventHandlerClassName, - '__invoke' - ); - } - - /** - * Returns "className" from "Foo\Bar\ClassName". - * - * @param string $className - * @return string - */ - protected static function classNameToSignalName(string $className) - { - $explodedFqcn = explode('\\', $className); - - return lcfirst(array_pop($explodedFqcn)); - } - - /** - * @return InterestFileNameValidator|FileNameValidator + * @param array $tableTca The TCA array for a table. [ctrl => ..., ...] */ - public static function getFileNameValidator(): object + public static function backportVersion12TcaFeaturesForTable(array $tableTca) { - if (self::typo3VersionIsLessThan('10.4')) { - return GeneralUtility::makeInstance(InterestFileNameValidator::class); + foreach ($tableTca['columns'] as &$fieldTca) { + foreach ($fieldTca['config'] as $configurationProperty => $configurationValue) { + switch ($configurationProperty) { + case 'items': + $items = []; + + foreach ($configurationValue as $item) { + $items[] = [ + $item['label'] ?? '', + $item['value'] ?? null, + $item['icon'] ?? null, + $item['group'] ?? null, + $item['description'] ?? null, + ]; + } + + $fieldTca['config']['items'] = $items; + + break; + case 'required': + if ($configurationValue === true) { + if (!isset($fieldTca['config']['eval'])) { + $fieldTca['config']['eval'] = 'required'; + } else { + $fieldTca['config']['eval'] .= ',required'; + } + } + + unset($fieldTca['config']['required']); + + break; + case 'type': + switch ($configurationValue) { + case 'datetime': + $fieldTca['config']['type'] = 'input'; + $fieldTca['config']['renderType'] = 'inputDateTime'; + break; + case 'number': + $fieldTca['config']['type'] = 'input'; + + if (!isset($fieldTca['config']['eval'])) { + $fieldTca['config']['eval'] = 'int'; + } else { + $fieldTca['config']['eval'] .= ',int'; + } + break; + } + + break; + } + } } - return GeneralUtility::makeInstance(FileNameValidator::class); + return $tableTca; } } diff --git a/Classes/Utility/DatabaseUtility.php b/Classes/Utility/DatabaseUtility.php index 59446263..820eeae4 100644 --- a/Classes/Utility/DatabaseUtility.php +++ b/Classes/Utility/DatabaseUtility.php @@ -4,7 +4,7 @@ namespace Pixelant\Interest\Utility; -use Doctrine\DBAL\Driver\Result; +use Doctrine\DBAL\Result; use Pixelant\Interest\Domain\Repository\Exception\InvalidQueryResultException; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\QueryBuilder; @@ -25,7 +25,7 @@ class DatabaseUtility public static function getRecord(string $table, int $uid, array $fields = ['*']) { // Ensure we have a valid uid (not 0 and not NEWxxxx) and a valid TCA - if (!empty($GLOBALS['TCA'][$table])) { + if (($GLOBALS['TCA'][$table] ?? []) !== []) { $queryBuilder = self::getQueryBuilderForTable($table); // do not use enabled fields here @@ -36,9 +36,9 @@ public static function getRecord(string $table, int $uid, array $fields = ['*']) $queryBuilder ->select(...$fields) ->from($table) - ->where($queryBuilder->expr()->eq('uid', (int)$uid)); + ->where($queryBuilder->expr()->eq('uid', $uid)); - $result = $queryBuilder->execute(); + $result = $queryBuilder->executeQuery(); if (!($result instanceof Result)) { throw new InvalidQueryResultException( diff --git a/Classes/Utility/RelationUtility.php b/Classes/Utility/RelationUtility.php index 62e17420..068bffbf 100644 --- a/Classes/Utility/RelationUtility.php +++ b/Classes/Utility/RelationUtility.php @@ -169,7 +169,7 @@ public static function getSiblingRelationsOfForeignParent( $parentTableFieldConfig ); - if (!in_array($localUid, $relations[$localTable])) { + if (!in_array($localUid, $relations[$localTable], true)) { return []; } @@ -233,7 +233,7 @@ function (int $carry, array $records) { $foreignUid ) ) - ->execute(); + ->executeStatement(); } } } diff --git a/Classes/Utility/TcaUtility.php b/Classes/Utility/TcaUtility.php index db23550a..c4484934 100644 --- a/Classes/Utility/TcaUtility.php +++ b/Classes/Utility/TcaUtility.php @@ -142,18 +142,18 @@ public static function getInlineRelationsToTable(string $tableName): array if (self::$inlineRelationsToTablesCache === null) { $cacheManager = GeneralUtility::makeInstance(CacheManager::class); - $cacheName = CompatibilityUtility::typo3VersionIsLessThan('10') ? 'cache_hash' : 'hash'; - $cacheHash = md5(self::class . '_inlineRelationsToTables'); - $cache = $cacheManager->getCache($cacheName); + $cache = $cacheManager->getCache('hash'); - self::$inlineRelationsToTablesCache = $cache->get($cacheHash) ?: null; + $inlineRelationsToTables = $cache->get($cacheHash); - if (self::$inlineRelationsToTablesCache === null || !is_array(self::$inlineRelationsToTablesCache)) { + if (!is_array($inlineRelationsToTables)) { self::populateInlineRelationsToTablesCache(); $cache->set($cacheHash, self::$inlineRelationsToTablesCache); + } else { + self::$inlineRelationsToTablesCache = $inlineRelationsToTables; } } diff --git a/Configuration/Commands.php b/Configuration/Commands.php deleted file mode 100644 index ac9c76f1..00000000 --- a/Configuration/Commands.php +++ /dev/null @@ -1,32 +0,0 @@ - [ - 'class' => \Pixelant\Interest\Command\CreateCommandController::class, - 'schedulable' => false, - ], - 'interest:delete' => [ - 'class' => \Pixelant\Interest\Command\DeleteCommandController::class, - 'schedulable' => false, - ], - 'interest:update' => [ - 'class' => \Pixelant\Interest\Command\UpdateCommandController::class, - 'schedulable' => false, - ], - 'interest:read' => [ - 'class' => \Pixelant\Interest\Command\ReadCommandController::class, - 'schedulable' => false, - ], - 'interest:pendingrelations' => [ - 'class' => \Pixelant\Interest\Command\PendingRelationsCommandController::class, - 'schedulable' => false, - ], - 'interest:clearhash' => [ - 'class' => \Pixelant\Interest\Command\ClearRecordHashCommandController::class, - 'schedulable' => false, - ], -]; diff --git a/Configuration/Icons.php b/Configuration/Icons.php index 7ac5b16e..d626baed 100644 --- a/Configuration/Icons.php +++ b/Configuration/Icons.php @@ -1,12 +1,14 @@ [ - 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + 'provider' => SvgIconProvider::class, 'source' => 'EXT:interest/Resources/Public/Icons/RemoteIdMapping.svg', ], 'ext-interest-mapping-manual' => [ - 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, + 'provider' => SvgIconProvider::class, 'source' => 'EXT:interest/Resources/Public/Icons/ManualRemoteIdMapping.svg', ], ]; diff --git a/Configuration/RequestMiddlewares.php b/Configuration/RequestMiddlewares.php index eadcf516..3fe3e37a 100644 --- a/Configuration/RequestMiddlewares.php +++ b/Configuration/RequestMiddlewares.php @@ -1,22 +1,13 @@ [ 'interest-rest-requests' => [ - 'target' => Pixelant\Interest\Middleware\RequestMiddleware::class, - 'before' => $before, - 'after' => $after, + 'target' => RequestMiddleware::class, + 'before' => ['typo3/cms-frontend/backend-user-authentication'], + 'after' => ['typo3/cms-frontend/site'], ], ], ]; diff --git a/Configuration/TCA/tx_interest_remote_id_mapping.php b/Configuration/TCA/tx_interest_remote_id_mapping.php index 550c2ddb..50a0fa5e 100644 --- a/Configuration/TCA/tx_interest_remote_id_mapping.php +++ b/Configuration/TCA/tx_interest_remote_id_mapping.php @@ -1,15 +1,16 @@ [ 'title' => $ll . 'tx_interest_remote_id_mapping', 'label' => 'remote_id', 'type' => 'manual', 'tstamp' => 'tstamp', 'crdate' => 'crdate', - 'cruser_id' => 'cruser_id', 'rootLevel' => -1, 'default_sortby' => 'ORDER BY uid', 'enablecolumns' => [], @@ -31,17 +32,13 @@ 'crdate' => [ 'label' => 'crdate', 'config' => [ - 'type' => 'input', - 'renderType' => 'inputDateTime', - 'eval' => 'datetime', + 'type' => 'datetime', ], ], 'tstamp' => [ 'label' => 'tstamp', 'config' => [ - 'type' => 'input', - 'renderType' => 'inputDateTime', - 'eval' => 'datetime', + 'type' => 'datetime', ], ], 'remote_id' => [ @@ -50,7 +47,8 @@ 'config' => [ 'type' => 'input', 'size' => 30, - 'eval' => 'required,unique,alphanum_x,trim', + 'eval' => 'unique,alphanum_x,trim', + 'required' => true, ], ], 'table' => [ @@ -59,16 +57,17 @@ 'config' => [ 'type' => 'input', 'size' => 30, - 'eval' => 'required,alphanum_x,trim', + 'eval' => 'alphanum_x,trim', + 'required' => true, ], ], 'uid_local' => [ 'exclude' => false, 'label' => $ll . 'tx_interest_remote_id_mapping.local_uid', 'config' => [ - 'type' => 'input', + 'type' => 'number', 'size' => 10, - 'eval' => 'required,int', + 'required' => true, ], ], 'manual' => [ @@ -81,14 +80,18 @@ 'default' => 1, 'items' => [ [ - 0 => '', - 1 => '', + 'label' => '', + 'value' => 0, + ], + [ + 'label' => '', + 'value' => 1, ], ], ], ], 'metadata' => [ - 'label' => 'Meta dats', + 'label' => 'Meta data', 'config' => [ 'type' => 'passthrough', 'default' => '', @@ -101,3 +104,9 @@ ], ], ]; + +if (CompatibilityUtility::typo3VersionIsLessThan('12.0')) { + $tca = CompatibilityUtility::backportVersion12TcaFeaturesForTable($tca); +} + +return $tca; diff --git a/Documentation/Configuration/Extension/Index.rst b/Documentation/Configuration/Extension/Index.rst index 11bac4b5..5db3ad70 100644 --- a/Documentation/Configuration/Extension/Index.rst +++ b/Documentation/Configuration/Extension/Index.rst @@ -52,6 +52,28 @@ REST Behavior -------- +.. confval:: Handle Empty Files + + :Required: true + :Type: int + :Default: 0 + :Key: `handleEmptyFile` + + How to handle files that are empty. Available options are: + + **0** + Treat as any other file. You can also use this option if you want to + handle empty files with a custom + :php:`Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent`. + Just make sure your EventHandler it is executed after + :php:`\Pixelant\Interest\DataHandling\Operation\Event\Handler\PersistFileDataEventHandler`. + **1** + Stop processing the record. This might result in pending relation records + that will never be resolved in the database, but if that's OK for you it + won't cause any issues. + **2** + Fail. The operation will be treated as failed and returns an error. + .. confval:: Handle Existing Files :Required: true @@ -91,13 +113,13 @@ Logging of REST calls, including request and response data and execution time. Enable logging and specify where to log. Available values: - 0 + **0** Disabled. No logging. - 1 + **1** Log in response headers - 2 + **2** Log in database. - 3 + **3** Log in both response headers and database. .. confval:: Logging threshold diff --git a/Documentation/Implementing/Cli/Index.rst b/Documentation/Implementing/Cli/Index.rst index 003646f2..dd71f510 100644 --- a/Documentation/Implementing/Cli/Index.rst +++ b/Documentation/Implementing/Cli/Index.rst @@ -6,13 +6,6 @@ Command Line API ================ -.. warning:: - - Because Extbase takes a lot of time to initialize, this extension disables - extbase-based commands from being initialized when none-Extbase commands are - being executed. This doesn't affect the execution of Extbase-based commands, - but prevents the help command from working properly. - .. _implementing-cli-authentication: Authentication diff --git a/Documentation/Implementing/Extending/Index.rst b/Documentation/Implementing/Extending/Index.rst index 0e306f3d..1358dcb0 100644 --- a/Documentation/Implementing/Extending/Index.rst +++ b/Documentation/Implementing/Extending/Index.rst @@ -6,7 +6,7 @@ Changing and Extending ====================== -If you need additional functionality or the extisting functionality of the extension isn't quite what you need, this section tells you how to change the behavior of the Interest extension. It also tells you how to extend the functionality, as well as a bit about the extension's inner workings. +If you need additional functionality or the existing functionality of the extension isn't quite what you need, this section tells you how to change the behavior of the Interest extension. It also tells you how to extend the functionality, as well as a bit about the extension's inner workings. .. _extending-events: @@ -94,31 +94,6 @@ The events are listed in order of execution. :param Psr\Http\Message\ResponseInterface $response: -.. _extending-events-typo3v9 - -In TYPO3 version 9 ------------------- - -TYPO3 version 9 doesn't support PSR-14 events, but it's using signals and slots instead. Luckily, PSR-14 Events and EventHandlers can be made to work with them as well. You can register an EventHandler as a SignalSlot using this convenience function: - -.. code-block:: php - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - string $eventClassName, - string $eventHandlerClassName - ); - -It will map the class and methods correctly. The only difference is that you can't change the order of execution, as the slots are called in the order they are registered. - -Here's how the function is used by the Interest extension itself: - -.. code-block:: php - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\StopIfRepeatingPreviousRecordOperation::class - ); - .. _extending-how-it-works: How it works diff --git a/Documentation/Implementing/FileHandling/Index.rst b/Documentation/Implementing/FileHandling/Index.rst index 35b12a38..488350cd 100644 --- a/Documentation/Implementing/FileHandling/Index.rst +++ b/Documentation/Implementing/FileHandling/Index.rst @@ -101,4 +101,4 @@ If the `name` field is used, the file will use the name supplied there and the c File name sanitation ==================== -File names are santized using TYPO3's standard sanitation methods, so expect spaces to be replaced with underscores, etc. +File names are sanitized using TYPO3's standard sanitation methods, so expect spaces to be replaced with underscores, etc. diff --git a/Documentation/Settings.cfg b/Documentation/Settings.cfg index 3ba91846..b2f9029f 100644 --- a/Documentation/Settings.cfg +++ b/Documentation/Settings.cfg @@ -13,7 +13,7 @@ release = latest # ................................................................................. # ... (recommended) displayed in footer # ................................................................................. -copyright = 2022 by Pixelant.net +copyright = 2023 by Pixelant.net [html_theme_options] diff --git a/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationBeforeTypo3v11.php b/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationBeforeTypo3v11.php deleted file mode 100644 index e0842ada..00000000 --- a/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationBeforeTypo3v11.php +++ /dev/null @@ -1,233 +0,0 @@ -isUserAllowedToLogin()) { - throw new \RuntimeException('Login Error: TYPO3 is in maintenance mode at the moment. Only' . - ' administrators are allowed access.', 1483971855); - } - - $this->dontSetCookie = true; - - parent::__construct(); - } - - /** - * Replacement for AbstactUserAuthentication::start() - * - * We do not need support for sessions, cookies, $_GET-modes, the postUserLookup hook or - * a database connectiona during CLI Bootstrap - */ - public function start() - { - $this->logger->debug('## Beginning of auth logging.'); - // svConfig is unused, but we set it, as the property is public and might be used by extensions - $this->svConfig = $GLOBALS['TYPO3_CONF_VARS']['SVCONF']['auth'] ?? []; - } - - /** - * Logs in the TYPO3 Backend user supplied in HTTP headers. - * - * @param bool $proceedIfNoUserIsLoggedIn IGNORED - */ - public function backendCheckLogin($proceedIfNoUserIsLoggedIn = false) - { - } - - /** - * Logs-in the backend user. - * - * @param int $userId The ID of the backend user to log in. - * - * @throws \RuntimeException when the user could not log in or it is an admin - */ - public function authenticate(int $userId) - { - $this->setBeUserByUid($userId); - - // The groups are fetched and ready for permission checking in this initialization. - $this->fetchGroupData(); - $this->backendSetUC(); - } - - /** - * Checks if a submission of username and password is present or use other authentication by auth services - * - * @throws \RuntimeException - * @internal - * - * phpcs:disable Generic.Metrics.CyclomaticComplexity - */ - public function checkAuthentication() - { - $tempuser = null; - - $authenticated = false; - // The info array provide additional information for auth services - $authInfo = $this->getAuthInfoArray(); - // Get Login/Logout data submitted by a form or params - $loginData = $this->getLoginFormData(); - $this->logger->debug('Login data', $this->removeSensitiveLoginDataForLoggingInfo($loginData)); - - $tempuserArr = []; - - // Use 'auth' service to find the user - // First found user will be used - $subType = 'getUser' . $this->loginType; - foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) { - $row = $serviceObj->getUser(); - if ($row) { - $tempuserArr[] = $row; - $this->logger->debug('User found', [ - $this->userid_column => $row[$this->userid_column], - $this->username_column => $row[$this->username_column], - ]); - // User found, just stop to search for more if not configured to go on - if (empty($this->svConfig['setup'][$this->loginType . '_fetchAllUsers'])) { - break; - } - } - } - - if (empty($tempuserArr)) { - $this->logger->debug('No user found by services'); - } else { - $this->logger->debug(count($tempuserArr) . ' user records found by services'); - } - - // Authenticate the user if needed - if (!empty($tempuserArr)) { - foreach ($tempuserArr as $tempuser) { - // Use 'auth' service to authenticate the user - // If one service returns FALSE then authentication failed - // a service might return 100 which means there's no reason to stop but the user can't be authenticated - // by that service - $this->logger->debug('Auth user', $this->removeSensitiveLoginDataForLoggingInfo($tempuser, true)); - $subType = 'authUser' . $this->loginType; - - foreach ($this->getAuthServices($subType, $loginData, $authInfo) as $serviceObj) { - $ret = $serviceObj->authUser($tempuser); - if ($ret > 0) { - // If the service returns >=200 then no more checking is needed - useful for IP checking without - // password - if ((int)$ret >= 200) { - $authenticated = true; - break; - } - if ((int)$ret < 100) { - $authenticated = true; - } - } else { - $authenticated = false; - break; - } - } - - if ($authenticated) { - // Leave foreach() because a user is authenticated - break; - } - } - } - - // If user is authenticated a valid user is in $tempuser - if ($authenticated) { - // Reset failure flag - $this->loginFailure = false; - // Insert session record if needed: - - $this->user = $tempuser; - // The login session is started. - $this->loginSessionStarted = true; - if (is_array($this->user)) { - $this->logger->debug('User session finally read', [ - $this->userid_column => $this->user[$this->userid_column], - $this->username_column => $this->user[$this->username_column], - ]); - } - - // User logged in - write that to the log! - if ($this->writeStdLog) { - $this->writelog( - 255, - 1, - 0, - 1, - 'User %s logged in from ###IP###', - [$tempuser[$this->username_column]], - '', - '', - '' - ); - } - - $this->logger->debug( - 'User ' . $tempuser[$this->username_column] . ' authenticated from ' - . GeneralUtility::getIndpEnv('REMOTE_ADDR') - ); - } else { - $this->loginFailure = true; - - if (empty($tempuserArr)) { - $logData = [ - 'loginData' => $this->removeSensitiveLoginDataForLoggingInfo($loginData), - ]; - $this->logger->debug('Login failed', $logData); - } - - if (!empty($tempuserArr)) { - $logData = [ - $this->userid_column => $tempuser[$this->userid_column], - $this->username_column => $tempuser[$this->username_column], - ]; - $this->logger->debug('Login failed', $logData); - } - } - } - - /** - * Determines whether a backend user is allowed to access TYPO3. - * - * @return bool True if $GLOBALS[TYPO3_CONF_VARS][BE][adminOnly] is zero. - */ - protected function isUserAllowedToLogin() - { - return (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['adminOnly'] === 0; - } - - /** - * @param array $data - */ - public function setLoginFormData(array $data) - { - $this->loginFormData = $data; - } - - /** - * Returns an info array with Login/Logout data from headers - * - * @return array - * @internal - */ - public function getLoginFormData() - { - return $this->loginFormData; - } -} diff --git a/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v11.php b/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v11.php new file mode 100644 index 00000000..9f23d285 --- /dev/null +++ b/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v11.php @@ -0,0 +1,18 @@ +internalGetLoginFormData($GLOBALS['TYPO3_REQUEST']); + } +} diff --git a/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v12.php b/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v12.php new file mode 100644 index 00000000..303dc498 --- /dev/null +++ b/DynamicCompatibility/Authentication/HttpBackendUserAuthenticationForTypo3v12.php @@ -0,0 +1,22 @@ +internalGetLoginFormData($request); + } +} diff --git a/Tests/Functional/DataHandling/Operation/AbstractRecordOperationFunctionalTestCase.php b/Tests/Functional/DataHandling/Operation/AbstractRecordOperationFunctionalTestCase.php index a2e04ef2..bb8a063b 100644 --- a/Tests/Functional/DataHandling/Operation/AbstractRecordOperationFunctionalTestCase.php +++ b/Tests/Functional/DataHandling/Operation/AbstractRecordOperationFunctionalTestCase.php @@ -6,6 +6,7 @@ use Pixelant\Interest\Utility\CompatibilityUtility; use TYPO3\CMS\Core\Configuration\SiteConfiguration; +use TYPO3\CMS\Core\EventDispatcher\EventDispatcher; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; @@ -13,19 +14,15 @@ abstract class AbstractRecordOperationFunctionalTestCase extends FunctionalTestCase { /** - * @var array + * @var array */ - protected $testExtensionsToLoad = ['typo3conf/ext/interest']; + protected array $testExtensionsToLoad = ['typo3conf/ext/interest']; protected function setUp(): void { parent::setUp(); - if (CompatibilityUtility::typo3VersionIsLessThan('10')) { - $this->importCSVDataSet(__DIR__ . '/Fixtures/BackendUserForVersion9.csv'); - } else { - $this->importCSVDataSet(__DIR__ . '/Fixtures/BackendUser.csv'); - } + $this->importCSVDataSet(__DIR__ . '/Fixtures/BackendUser.csv'); $this->importCSVDataSet(__DIR__ . '/Fixtures/Records.csv'); @@ -35,9 +32,22 @@ protected function setUp(): void GeneralUtility::setIndpEnv('TYPO3_REQUEST_URL', 'http://www.example.com/'); - $siteConfiguration = new SiteConfiguration( - GeneralUtility::getFileAbsFileName('EXT:interest/Tests/Functional/DataHandling/Operation/Fixtures/Sites') - ); + if (CompatibilityUtility::typo3VersionIsLessThan('12.0')) { + $siteConfiguration = GeneralUtility::makeInstance( + SiteConfiguration::class, + GeneralUtility::getFileAbsFileName( + 'EXT:interest/Tests/Functional/DataHandling/Operation/Fixtures/Sites' + ), + ); + } else { + $siteConfiguration = GeneralUtility::makeInstance( + SiteConfiguration::class, + GeneralUtility::getFileAbsFileName( + 'EXT:interest/Tests/Functional/DataHandling/Operation/Fixtures/Sites' + ), + GeneralUtility::makeInstance(EventDispatcher::class) + ); + } GeneralUtility::setSingletonInstance(SiteConfiguration::class, $siteConfiguration); diff --git a/Tests/Functional/DataHandling/Operation/CreateRecordOperationTest.php b/Tests/Functional/DataHandling/Operation/CreateRecordOperationTest.php index e1d8ed02..b588c6d8 100644 --- a/Tests/Functional/DataHandling/Operation/CreateRecordOperationTest.php +++ b/Tests/Functional/DataHandling/Operation/CreateRecordOperationTest.php @@ -11,9 +11,14 @@ use Pixelant\Interest\DataHandling\Operation\CreateRecordOperation; use Pixelant\Interest\DataHandling\Operation\Event\Exception\StopRecordOperationException; +use Pixelant\Interest\DataHandling\Operation\Exception\InvalidArgumentException; use Pixelant\Interest\Domain\Model\Dto\RecordInstanceIdentifier; use Pixelant\Interest\Domain\Model\Dto\RecordRepresentation; use Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository; +use Pixelant\Interest\Utility\CompatibilityUtility; +use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; +use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Utility\GeneralUtility; class CreateRecordOperationTest extends AbstractRecordOperationFunctionalTestCase { @@ -93,7 +98,7 @@ protected function createOperationResultsInCorrectRecordDataIteration( self::assertEquals($createdRecord, $expectedRow, 'Comparing created record with expected data.'); } - public function recordRepresentationAndCorrespondingRowDataProvider() + public function recordRepresentationAndCorrespondingRowDataProvider(): array { return [ 'Base language record' => [ @@ -195,15 +200,20 @@ public function createAdvancedInlineMmRelationsInDifferentOrder() }; $createSysFileReference = function (string $iteration) { + $recordRepresentationData = [ + 'pid' => 'RootPage', + 'uid_local' => 'MediaElementSysFile_' . $iteration, + 'uid_foreign' => 'MediaContentElement_' . $iteration, + 'fieldname' => 'image', + ]; + + if (CompatibilityUtility::typo3VersionIsLessThan('12.0')) { + $recordRepresentationData['table_local'] = 'sys_file'; + } + (new CreateRecordOperation( new RecordRepresentation( - [ - 'pid' => 'RootPage', - 'uid_local' => 'MediaElementSysFile_' . $iteration, - 'table_local' => 'sys_file', - 'uid_foreign' => 'MediaContentElement_' . $iteration, - 'fieldname' => 'image', - ], + $recordRepresentationData, new RecordInstanceIdentifier( 'sys_file_reference', 'MediaElementSysFileReference_' . $iteration @@ -306,22 +316,27 @@ public function createAdvancedInlineMmRelationsInDifferentOrder() 'Created content element iteration ' . $iteration ); + $expectedReturnData = [ + 'uid_local' => $mappingRepository->get('MediaElementSysFile_' . $iteration), + 'uid_foreign' => $mappingRepository->get('MediaContentElement_' . $iteration), + 'fieldname' => 'image', + ]; + + if (CompatibilityUtility::typo3VersionIsLessThan('12.0')) { + $expectedReturnData['table_local'] = 'sys_file'; + } + $createdSysFileReference = $this ->getConnectionPool() ->getConnectionForTable('tt_content') ->executeQuery( - 'SELECT uid_local, table_local, uid_foreign, fieldname FROM sys_file_reference WHERE uid = ' + 'SELECT ' . implode(',', array_keys($expectedReturnData)) . ' FROM sys_file_reference WHERE uid = ' . $mappingRepository->get('MediaElementSysFileReference_' . $iteration) ) ->fetchAssociative(); self::assertEquals( - [ - 'uid_local' => $mappingRepository->get('MediaElementSysFile_' . $iteration), - 'table_local' => 'sys_file', - 'uid_foreign' => $mappingRepository->get('MediaContentElement_' . $iteration), - 'fieldname' => 'image', - ], + $expectedReturnData, $createdSysFileReference, 'Created sys_file_reference iteration ' . $iteration ); @@ -344,4 +359,56 @@ public function createAdvancedInlineMmRelationsInDifferentOrder() ); } } + + /** + * @test + */ + public function createEmptyFileIsHandledAsConfigured() + { + $createEmptySysFile = function () { + (new CreateRecordOperation( + new RecordRepresentation( + [ + 'fileData' => '', + 'name' => 'emptyFile.txt', + ], + new RecordInstanceIdentifier( + 'sys_file', + 'EmptyFile' + ) + ) + ))(); + }; + + GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->set('interest', ['handleEmptyFile' => '1']); + + self::expectException(StopRecordOperationException::class); + self::expectExceptionCode(1692921622763); + + $createEmptySysFile(); + + GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->set('interest', ['handleEmptyFile' => '2']); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionCode(1692921660432); + + $createEmptySysFile(); + + GeneralUtility::makeInstance(ExtensionConfiguration::class) + ->set('interest', ['handleEmptyFile' => '0']); + + $createEmptySysFile(); + + $mappingRepository = new RemoteIdMappingRepository(); + + $fileId = $mappingRepository->get('EmptyFile'); + + $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject($fileId); + + self::assertIsObject($file, 'File object was found'); + self::assertEquals(0, $file->getSize(), 'File size is zero'); + self::assertEmpty($file->getSize(), 'File content is empty'); + } } diff --git a/Tests/Functional/DataHandling/Operation/Fixtures/BackendUser.csv b/Tests/Functional/DataHandling/Operation/Fixtures/BackendUser.csv index 553e8a73..e3d1eb3f 100644 --- a/Tests/Functional/DataHandling/Operation/Fixtures/BackendUser.csv +++ b/Tests/Functional/DataHandling/Operation/Fixtures/BackendUser.csv @@ -1,3 +1,3 @@ -be_users,,,,,,,,,,,,,,,,, -,uid,pid,tstamp,username,password,admin,disable,starttime,endtime,options,crdate,cruser_id,workspace_perms,deleted,TSconfig,lastlogin,workspace_id -,1,0,1366642540,admin,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,1,0,0,0,0,1366642540,0,1,0,,1371033743,0 +be_users,,,,,,,,,,,,,,,, +,uid,pid,tstamp,username,password,admin,disable,starttime,endtime,options,crdate,workspace_perms,deleted,TSconfig,lastlogin,workspace_id +,1,0,1366642540,admin,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,1,0,0,0,0,1366642540,1,0,,1371033743,0 diff --git a/Tests/Functional/DataHandling/Operation/Fixtures/BackendUserForVersion9.csv b/Tests/Functional/DataHandling/Operation/Fixtures/BackendUserForVersion9.csv deleted file mode 100644 index 7f3ac3ca..00000000 --- a/Tests/Functional/DataHandling/Operation/Fixtures/BackendUserForVersion9.csv +++ /dev/null @@ -1,3 +0,0 @@ -be_users,,,,,,,,,,,,,,,,, -,uid,pid,tstamp,username,password,admin,disable,starttime,endtime,options,crdate,cruser_id,workspace_perms,deleted,TSconfig,lastlogin,workspace_id,disableIPlock -,1,0,1366642540,admin,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,1,0,0,0,0,1366642540,0,1,0,,1371033743,0,1 diff --git a/Tests/Functional/DataHandling/Operation/Fixtures/Records.csv b/Tests/Functional/DataHandling/Operation/Fixtures/Records.csv index 28d80a56..eb259dd4 100644 --- a/Tests/Functional/DataHandling/Operation/Fixtures/Records.csv +++ b/Tests/Functional/DataHandling/Operation/Fixtures/Records.csv @@ -7,11 +7,6 @@ pages,,,,,,,,,,,,,,,,, ,5,1,Dummy 1-5,256,0,15,,,,,,,,,,, ,6,5,Dummy 1-5-6,128,0,15,,,,,,,,,,, ,7,0,Root 2,256,0,15,,,,,,,,,,, -sys_language,,,,,,,,,,,,,,,,, -,uid,pid,tstamp,title,flag,language_isocode,,,,,,,,,,, -,1,0,1622463076,german,de,de,,,,,,,,,,, -,2,0,1623662734,spanish,es,es,,,,,,,,,,, -,3,0,1623662780,french,fr,fr,,,,,,,,,,, tt_content,,,,,,,,,,,,,,,,, ,uid,pid,sorting,deleted,sys_language_uid,l18n_parent,l10n_source,t3_origuid,header,CType,records,,,,,, ,296,1,256,0,0,0,0,0,Regular Element #2,text,,,,,,, diff --git a/Tests/Functional/DataHandling/Operation/UpdateRecordOperationTest.php b/Tests/Functional/DataHandling/Operation/UpdateRecordOperationTest.php index c93b4fb7..f2d31abe 100644 --- a/Tests/Functional/DataHandling/Operation/UpdateRecordOperationTest.php +++ b/Tests/Functional/DataHandling/Operation/UpdateRecordOperationTest.php @@ -15,6 +15,7 @@ use Pixelant\Interest\Domain\Model\Dto\RecordInstanceIdentifier; use Pixelant\Interest\Domain\Model\Dto\RecordRepresentation; use Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository; +use Pixelant\Interest\Utility\CompatibilityUtility; class UpdateRecordOperationTest extends AbstractRecordOperationFunctionalTestCase { @@ -27,8 +28,6 @@ public function updatingPageChangesFields() 'title' => 'INTEREST', ]; - $mappingRepository = new RemoteIdMappingRepository(); - (new UpdateRecordOperation( new RecordRepresentation( $data, @@ -123,8 +122,8 @@ public function updatingForeignFieldRemovesNonExistingRelationsAndUseCorrectSort ], ]; - foreach ($imageUpdates as $imageUpdates) { - $this->updateMediaContentElementImages('MediaContentElement_Sample', $imageUpdates); + foreach ($imageUpdates as $imageUpdateRemoteIdentifiers) { + $this->updateMediaContentElementImages('MediaContentElement_Sample', $imageUpdateRemoteIdentifiers); $query = 'SELECT uid_local FROM sys_file_reference WHERE uid_foreign = ' . $contentUid; $query .= ' AND tablenames = \'tt_content\' AND fieldname = \'image\' AND deleted = 0'; @@ -139,7 +138,7 @@ public function updatingForeignFieldRemovesNonExistingRelationsAndUseCorrectSort $databaseImageIds = array_column($imageSysFileReferences, 'uid_local'); $expectedImageIds = []; - foreach ($imageUpdates as $sfrRemoteIdentifier) { + foreach ($imageUpdateRemoteIdentifiers as $sfrRemoteIdentifier) { $expectedImageIds[] = $mappingRepository->get($sfrRemoteIdentifier); } @@ -185,15 +184,20 @@ protected function updateMediaContentElementImages( foreach ($sysFilesRemoteIdentifiers as $sysFilesRemoteIdentifier) { $sfrRemoteIdentifier = $contentElementRemoteIdentifier . '_' . $sysFilesRemoteIdentifier; if ($mappingRepository->get($sfrRemoteIdentifier) === 0) { + $recordRepresentationData = [ + 'pid' => 'RootPage', + 'uid_local' => $sysFilesRemoteIdentifier, + 'uid_foreign' => $contentElementRemoteIdentifier, + 'fieldname' => 'image', + ]; + + if (CompatibilityUtility::typo3VersionIsLessThan('12.0')) { + $recordRepresentationData['table_local'] = 'sys_file'; + } + (new CreateRecordOperation( new RecordRepresentation( - [ - 'pid' => 'RootPage', - 'uid_local' => $sysFilesRemoteIdentifier, - 'table_local' => 'sys_file', - 'uid_foreign' => $contentElementRemoteIdentifier, - 'fieldname' => 'image', - ], + $recordRepresentationData, new RecordInstanceIdentifier( 'sys_file_reference', $sfrRemoteIdentifier diff --git a/Tests/Functional/Domain/Repository/TokenRepositoryTest.php b/Tests/Functional/Domain/Repository/TokenRepositoryTest.php index d22c4aa3..58ea9f4c 100644 --- a/Tests/Functional/Domain/Repository/TokenRepositoryTest.php +++ b/Tests/Functional/Domain/Repository/TokenRepositoryTest.php @@ -10,9 +10,9 @@ class TokenRepositoryTest extends FunctionalTestCase { /** - * @var array + * @var array */ - protected $testExtensionsToLoad = ['typo3conf/ext/interest']; + protected array $testExtensionsToLoad = ['typo3conf/ext/interest']; /** * @var TokenRepository @@ -34,7 +34,7 @@ public function createTokenReturnsHashWithExpiryGreaterThanCreationDate() $token = $this->subject->createTokenForBackendUser(1); self::assertIsString($token, 'Token is a string.'); - self::assertRegExp('/^[0-9a-f]{32}$/', $token, 'Token is a 32-character hexademical string.'); + self::assertMatchesRegularExpression('/^[0-9a-f]{32}$/', $token, 'Token is a 32-character hexademical string.'); $databaseRow = $this ->getConnectionPool() diff --git a/Tests/Unit/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandlerTest.php b/Tests/Unit/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandlerTest.php index 3444ffb1..887dcc9a 100644 --- a/Tests/Unit/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandlerTest.php +++ b/Tests/Unit/DataHandling/Operation/Event/Handler/ForeignRelationSortingEventHandlerTest.php @@ -8,7 +8,6 @@ use Pixelant\Interest\DataHandling\Operation\Event\Handler\ForeignRelationSortingEventHandler; use Pixelant\Interest\DataHandling\Operation\UpdateRecordOperation; use Pixelant\Interest\Domain\Repository\RemoteIdMappingRepository; -use Pixelant\Interest\Utility\CompatibilityUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; @@ -35,19 +34,11 @@ public function invokingGeneratesCorrectSortingData( GeneralUtility::setSingletonInstance(RemoteIdMappingRepository::class, $mappingRepositoryMock); - if (CompatibilityUtility::typo3VersionIsLessThan('10')) { - $recordOperationMock = $this - ->getMockBuilder(UpdateRecordOperation::class) - ->disableOriginalConstructor() - ->setMethods(['setDataForDataHandler', 'getDataForDataHandler']) - ->getMock(); - } else { - $recordOperationMock = $this - ->getMockBuilder(UpdateRecordOperation::class) - ->disableOriginalConstructor() - ->onlyMethods(['setDataForDataHandler', 'getDataForDataHandler']) - ->getMock(); - } + $recordOperationMock = $this + ->getMockBuilder(UpdateRecordOperation::class) + ->disableOriginalConstructor() + ->onlyMethods(['setDataForDataHandler', 'getDataForDataHandler']) + ->getMock(); $recordOperationMock ->method('getDataForDataHandler') @@ -55,17 +46,10 @@ public function invokingGeneratesCorrectSortingData( $event = new AfterRecordOperationEvent($recordOperationMock); - if (CompatibilityUtility::typo3VersionIsLessThan('10')) { - $subjectMock = $this - ->getMockBuilder(ForeignRelationSortingEventHandler::class) - ->setMethods(['getMmFieldConfigurations', 'orderOnForeignSideOfRelation', 'persistData']) - ->getMock(); - } else { - $subjectMock = $this - ->getMockBuilder(ForeignRelationSortingEventHandler::class) - ->onlyMethods(['getMmFieldConfigurations', 'orderOnForeignSideOfRelation', 'persistData']) - ->getMock(); - } + $subjectMock = $this + ->getMockBuilder(ForeignRelationSortingEventHandler::class) + ->onlyMethods(['getMmFieldConfigurations', 'orderOnForeignSideOfRelation', 'persistData']) + ->getMock(); $subjectMock ->method('getMmFieldConfigurations') diff --git a/Tests/Unit/Domain/Repository/PendingRelationsRepositoryTest.php b/Tests/Unit/Domain/Repository/PendingRelationsRepositoryTest.php index 08d34f1c..e44b6472 100644 --- a/Tests/Unit/Domain/Repository/PendingRelationsRepositoryTest.php +++ b/Tests/Unit/Domain/Repository/PendingRelationsRepositoryTest.php @@ -5,7 +5,6 @@ namespace Pixelant\Interest\Tests\Unit\Domain\Repository; use Pixelant\Interest\Domain\Repository\PendingRelationsRepository; -use Pixelant\Interest\Utility\CompatibilityUtility; use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\TestingFramework\Core\Unit\UnitTestCase; @@ -16,15 +15,9 @@ class PendingRelationsRepositoryTest extends UnitTestCase */ public function emptyRemoteIdsAreIgnored(): void { - if (CompatibilityUtility::typo3VersionIsLessThan('10')) { - $subject = $this->getMockBuilder(PendingRelationsRepository::class) - ->setMethods(['getQueryBuilder', 'setSingle']) - ->getMock(); - } else { - $subject = $this->getMockBuilder(PendingRelationsRepository::class) - ->onlyMethods(['getQueryBuilder', 'setSingle']) - ->getMock(); - } + $subject = $this->getMockBuilder(PendingRelationsRepository::class) + ->onlyMethods(['getQueryBuilder', 'setSingle']) + ->getMock(); $subject->method('getQueryBuilder')->willReturn( $this->createMock(QueryBuilder::class) diff --git a/composer.json b/composer.json index e6a1a6a5..004cd48f 100644 --- a/composer.json +++ b/composer.json @@ -19,36 +19,33 @@ "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0", "ext-json": "*", "ext-pdo": "*", - "doctrine/dbal": "~2.11.2 || >2.13.1", - "typo3/cms-core": "^9.5.8 || ^10.4.6 || ^11.5.5", - "typo3/cms-extbase": "^9.5.8 || ^10.4.6 || ^11.5.5", - "typo3/cms-fluid": "^9.5.8 || ^10.4.6 || ^11.5.5", - "typo3/cms-frontend": "^9.5.8 || ^10.4.6 || ^11.5.5" + "typo3/cms-core": "^11.5.8 || ^12.4.6", + "typo3/cms-fluid": "^11.5.8 || ^12.4.6", + "typo3/cms-frontend": "^11.5.8 || ^12.4.6" }, "require-dev": { - "codeception/codeception": "^4.1.30", - "doctrine/dbal": "^2.13.8", - "ergebnis/composer-normalize": "^2.19.0", - "friendsofphp/php-cs-fixer": "^3.4.0", - "helmich/typo3-typoscript-lint": "^2.5.2", + "doctrine/dbal": "^2.13.5 || ^3.6.2", + "ergebnis/composer-normalize": "^2.28.3", + "friendsofphp/php-cs-fixer": "^3.14.1", + "helmich/typo3-typoscript-lint": "^3.1.0", "jangregor/phpstan-prophecy": "^1.0.0", - "phpstan/extension-installer": "^1.1.0", - "phpstan/phpstan": "^1.4.9", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpunit/phpunit": "^7.5.6 || ^8.5.24", - "saschaegerer/phpstan-typo3": "^1.1.2", - "seld/jsonlint": "^1.8.3", - "squizlabs/php_codesniffer": "^3.6.2", - "symfony/yaml": "^4.4.29 || ^5.3.6 || ^6.0", - "typo3/coding-standards": "^0.5.0", - "typo3/testing-framework": "^4.15.5 || ^6.15.3" + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.10.30", + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^9.6.11", + "saschaegerer/phpstan-typo3": "^1.8.9", + "seld/jsonlint": "^1.10.0", + "squizlabs/php_codesniffer": "^3.7.2", + "symfony/yaml": "^5.3.6 || ^6.2.0", + "typo3/coding-standards": "^0.6.1", + "typo3/testing-framework": "^7.0.4" }, "replace": { "typo3-ter/interest": "self.version" }, "conflict": { - "phpstan/phpstan": "1.7.12 || 1.7.13 || 1.7.14", - "typo3/class-alias-loader": "< 1.1.0" + "phpstan/phpstan": "1.7.12 || 1.7.13 || 1.7.14" }, "prefer-stable": true, "autoload": { diff --git a/ext_conf_template.txt b/ext_conf_template.txt index 1ebd3937..bf5d606a 100644 --- a/ext_conf_template.txt +++ b/ext_conf_template.txt @@ -7,6 +7,9 @@ tokenLifetime = 86400 # cat=Behavior; type=options[Fail with exception=cancel,Rename the new file=rename,Replace the existing file=replace]; label=Handle Existing Files: How to handle files that already exist in the filesystem. handleExistingFile = cancel +# cat=Behavior; type=options[Treat as normal file=0,Stop record processing=1,Fail=2]; label=Handle Empty Files: How to handle files that are empty. +handleEmptyFile = 0 + # cat=Logging; type=options[Disabled=0,Headers=1,Database=2,Both=3]; label=Enable logging: In response headers and/or database. log = 0 diff --git a/ext_emconf.php b/ext_emconf.php index 14f5d2b5..93a626c6 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -9,7 +9,7 @@ 'category' => 'plugin', 'constraints' => [ 'depends' => [ - 'typo3' => '9.5.8-11.5.99', + 'typo3' => '11.5.8-12.4.99', ], ], 'autoload' => [ diff --git a/ext_localconf.php b/ext_localconf.php index e30dc433..c701f9c9 100644 --- a/ext_localconf.php +++ b/ext_localconf.php @@ -1,94 +1,19 @@ registerIcon( - 'ext-interest-mapping', - \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, - ['source' => 'EXT:interest/Resources/Public/Icons/RemoteIdMapping.svg'] - ); - - $iconRegistry->registerIcon( - 'ext-interest-mapping-manual', - \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class, - ['source' => 'EXT:interest/Resources/Public/Icons/ManualRemoteIdMapping.svg'] - ); - - if (\Pixelant\Interest\Utility\CompatibilityUtility::typo3VersionIsGreaterThanOrEqualTo('10')) { - return; - } - - // TYPO3 v9 compatibility beyond this point. - - $signalSlotDispatcher = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance( - \TYPO3\CMS\Extbase\SignalSlot\Dispatcher::class - ); - - $signalSlotDispatcher->connect( - \TYPO3\CMS\Core\Resource\ResourceStorage::class, - \TYPO3\CMS\Core\Resource\ResourceStorage::SIGNAL_PostFileDelete, - \Pixelant\Interest\Slot\DeleteRemoteIdForDeletedFileSlot::class, - '__invoke' - ); - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\StopIfRepeatingPreviousRecordOperation::class - ); - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\PersistFileDataEventHandler::class - ); - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\DeferSysFileReferenceRecordOperationEventHandler::class - ); - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\RelationSortingAsMetaDataEventHandler::class - ); - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\BeforeRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\UpdateCountOnForeignSideOfInlineRecordEventHandler - ::class - ); - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\AfterRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\ProcessDeferredRecordOperationsEventHandler::class - ); - - \Pixelant\Interest\Utility\CompatibilityUtility::registerEventHandlerAsSignalSlot( - \Pixelant\Interest\DataHandling\Operation\Event\AfterRecordOperationEvent::class, - \Pixelant\Interest\DataHandling\Operation\Event\Handler\ForeignRelationSortingEventHandler::class - ); - - $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Core\Console\CommandRequestHandler::class] = [ - 'className' => \Pixelant\Interest\Console\OptimizedCommandRequestHandler::class, - ]; + RemovePendingRelationsWithEmptyRemoteIdUpdateWizard::IDENTIFIER + ] = RemovePendingRelationsWithEmptyRemoteIdUpdateWizard::class; })(); diff --git a/phpstan.neon b/phpstan.neon index d7a61fdc..4b0bf1bb 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -23,4 +23,4 @@ parameters: excludePaths: - Tests/Acceptance/ - - Classes/Console/OptimizedCommandRequestHandler.php + - Classes/Authentication/*