From 595ae45b7b2af08b4081fd7b35c7d54758a9abb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Mon, 19 Jun 2023 14:57:11 +0200 Subject: [PATCH 01/37] IBX-5630 --- .../Core/ApiLoader/RepositoryFactory.php | 12 +- src/bundle/Core/Resources/config/events.yml | 7 - src/bundle/Core/Resources/config/papi.yml | 4 + src/bundle/Core/Resources/config/services.yml | 4 + .../Event/ResolveUrlAliasSchemaEvent.php | 60 ++++++++ .../Container/ApiLoader/RepositoryFactory.php | 6 + src/lib/Event/Repository.php | 20 ++- .../EventSubscriber/NameSchemaSubscriber.php | 94 +++++++++++++ .../Repository/Helper/NameSchemaService.php | 131 +++++++++--------- .../Helper/SchemaIdentifierExtractor.php | 37 +++++ src/lib/Repository/Repository.php | 10 ++ .../Repository/SiteAccessAware/Repository.php | 20 ++- .../settings/repository/siteaccessaware.yml | 2 + .../Helper/SchemaIdentifierExtractorTest.php | 45 ++++++ tests/lib/Repository/Service/Mock/Base.php | 4 + 15 files changed, 384 insertions(+), 72 deletions(-) delete mode 100644 src/bundle/Core/Resources/config/events.yml create mode 100644 src/contracts/Event/ResolveUrlAliasSchemaEvent.php create mode 100644 src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php create mode 100644 src/lib/Repository/Helper/SchemaIdentifierExtractor.php create mode 100644 tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php diff --git a/src/bundle/Core/ApiLoader/RepositoryFactory.php b/src/bundle/Core/ApiLoader/RepositoryFactory.php index 45df9b0283..2652af59c5 100644 --- a/src/bundle/Core/ApiLoader/RepositoryFactory.php +++ b/src/bundle/Core/ApiLoader/RepositoryFactory.php @@ -19,6 +19,7 @@ use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Helper\RelationProcessor; +use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Mapper; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; @@ -28,6 +29,7 @@ use Psr\Log\NullLogger; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class RepositoryFactory implements ContainerAwareInterface { @@ -51,12 +53,16 @@ class RepositoryFactory implements ContainerAwareInterface /** @var \Ibexa\Contracts\Core\Repository\LanguageResolver */ private $languageResolver; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; public function __construct( ConfigResolverInterface $configResolver, $repositoryClass, array $policyMap, LanguageResolver $languageResolver, + EventDispatcherInterface $eventDispatcher, + SchemaIdentifierExtractor $schemaIdentifierExtractor, LoggerInterface $logger = null ) { $this->configResolver = $configResolver; @@ -64,6 +70,8 @@ public function __construct( $this->policyMap = $policyMap; $this->languageResolver = $languageResolver; $this->logger = null !== $logger ? $logger : new NullLogger(); + $this->eventDispatcher = $eventDispatcher; + $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; } /** @@ -93,7 +101,7 @@ public function buildRepository( PasswordValidatorInterface $passwordValidator, ConfigResolverInterface $configResolver ): Repository { - $config = $this->container->get(\Ibexa\Bundle\Core\ApiLoader\RepositoryConfigurationProvider::class)->getRepositoryConfig(); + $config = $this->container->get(RepositoryConfigurationProvider::class)->getRepositoryConfig(); return new $this->repositoryClass( $persistenceHandler, @@ -116,6 +124,8 @@ public function buildRepository( $locationFilteringHandler, $passwordValidator, $configResolver, + $this->eventDispatcher, + $this->schemaIdentifierExtractor, [ 'role' => [ 'policyMap' => $this->policyMap, diff --git a/src/bundle/Core/Resources/config/events.yml b/src/bundle/Core/Resources/config/events.yml deleted file mode 100644 index 185f72926e..0000000000 --- a/src/bundle/Core/Resources/config/events.yml +++ /dev/null @@ -1,7 +0,0 @@ -services: - _defaults: - autowire: true - autoconfigure: true - public: false - - Ibexa\Core\Repository\EventSubscriber\DeleteUserSubscriber: ~ diff --git a/src/bundle/Core/Resources/config/papi.yml b/src/bundle/Core/Resources/config/papi.yml index 475b7471a8..2e02d765a1 100644 --- a/src/bundle/Core/Resources/config/papi.yml +++ b/src/bundle/Core/Resources/config/papi.yml @@ -17,10 +17,14 @@ services: - Ibexa\Core\Repository\Repository - '%ibexa.api.role.policy_map%' - '@Ibexa\Contracts\Core\Repository\LanguageResolver' + - '@event_dispatcher' + - '@Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor' - "@?logger" calls: - [setContainer, ["@service_container"]] + Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor: ~ + Ibexa\Bundle\Core\ApiLoader\StorageEngineFactory: class: Ibexa\Bundle\Core\ApiLoader\StorageEngineFactory arguments: diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml index c3c6cf156c..c154ad30a0 100644 --- a/src/bundle/Core/Resources/config/services.yml +++ b/src/bundle/Core/Resources/config/services.yml @@ -363,3 +363,7 @@ services: $entityManagers: '%doctrine.entity_managers%' Ibexa\Bundle\Core\Translation\Policy\PolicyTranslationDefinitionProvider: ~ + + Ibexa\Core\Repository\EventSubscriber\NameSchemaSubscriber: + tags: + - { name: kernel.event_subscriber } diff --git a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php new file mode 100644 index 0000000000..6a1cbba8b0 --- /dev/null +++ b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php @@ -0,0 +1,60 @@ +schemaIdentifiers = $schemaIdentifiers; + $this->content = $content; + $this->contentType = $contentType; + $this->schemaName = $schemaName; + } + + public function getSchemaIdentifiers(): array + { + return $this->schemaIdentifiers; + } + + public function getContent(): Content + { + return $this->content; + } + + public function getContentType(): ?ContentType + { + return $this->contentType; + } + + public function getSchemaName(): string + { + return $this->schemaName; + } + + public function getNames(): array + { + return $this->names; + } + + public function setNames(array $names): void + { + $this->names = $names; + } +} diff --git a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php index c3703ee72b..a94b3531e2 100644 --- a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php +++ b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php @@ -20,6 +20,7 @@ use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Helper\RelationProcessor; +use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Mapper; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; @@ -27,6 +28,7 @@ use Ibexa\Core\Search\Common\BackgroundIndexer; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class RepositoryFactory implements ContainerAwareInterface { @@ -83,6 +85,8 @@ public function buildRepository( LocationFilteringHandler $locationFilteringHandler, PasswordValidatorInterface $passwordValidator, ConfigResolverInterface $configResolver, + EventDispatcherInterface $eventDispatcher, + SchemaIdentifierExtractor $schemaIdentifierExtractor, array $languages ): Repository { return new $this->repositoryClass( @@ -106,6 +110,8 @@ public function buildRepository( $locationFilteringHandler, $passwordValidator, $configResolver, + $eventDispatcher, + $schemaIdentifierExtractor, [ 'role' => [ 'policyMap' => $this->policyMap, diff --git a/src/lib/Event/Repository.php b/src/lib/Event/Repository.php index c8164d2eb7..7b1a040bf8 100644 --- a/src/lib/Event/Repository.php +++ b/src/lib/Event/Repository.php @@ -27,6 +27,8 @@ use Ibexa\Contracts\Core\Repository\URLWildcardService as URLWildcardServiceInterface; use Ibexa\Contracts\Core\Repository\UserPreferenceService as UserPreferenceServiceInterface; use Ibexa\Contracts\Core\Repository\UserService as UserServiceInterface; +use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class Repository implements RepositoryInterface { @@ -83,6 +85,8 @@ final class Repository implements RepositoryInterface /** @var \Ibexa\Contracts\Core\Repository\UserService */ private $userService; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; public function __construct( RepositoryInterface $repository, @@ -102,7 +106,9 @@ public function __construct( URLServiceInterface $urlService, URLWildcardServiceInterface $urlWildcardService, UserPreferenceServiceInterface $userPreferenceService, - UserServiceInterface $userService + UserServiceInterface $userService, + EventDispatcherInterface $eventDispatcher, + SchemaIdentifierExtractor $schemaIdentifierExtractor ) { $this->repository = $repository; $this->bookmarkService = $bookmarkService; @@ -122,6 +128,8 @@ public function __construct( $this->urlWildcardService = $urlWildcardService; $this->userPreferenceService = $userPreferenceService; $this->userService = $userService; + $this->eventDispatcher = $eventDispatcher; + $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) @@ -233,6 +241,16 @@ public function getUserService(): UserServiceInterface { return $this->userService; } + + public function getEventDispatcher(): EventDispatcherInterface + { + return $this->eventDispatcher; + } + + public function getSchemaIdentifierExtractor(): SchemaIdentifierExtractor + { + return $this->schemaIdentifierExtractor; + } } class_alias(Repository::class, 'eZ\Publish\Core\Event\Repository'); diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php new file mode 100644 index 0000000000..9b49b8d00c --- /dev/null +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -0,0 +1,94 @@ + 'send', + ]; + } + + public function send(ResolveUrlAliasSchemaEvent $event): void + { + $contentType = $event->getContentType(); + /** @var array $languageCodes */ + $languageCodes = $event->getContent()->versionInfo->languageCodes; + + $names = []; + + $identifiers = $event->getSchemaIdentifiers()['field'] ?? []; + $inputString = $event->getSchemaName(); + + $contentType->fieldDefinitions; + + foreach ($languageCodes as $languageCode) { + $replacedString = $inputString; + foreach ($identifiers as $identifier) { + /** @var \Ibexa\Core\Repository\Values\ContentType\FieldDefinition $fd */ + foreach ($contentType->getFieldDefinitions() as $key => $fd) { + $pattern = '/<(\w+)>/'; // Exclude the attribute prefix from the pattern + $replacedString = preg_replace_callback($pattern, function ($matches) use ($identifier, $key) { + if ($identifier == $key) { + $identifier = $matches[1]; + + if (isset($replacementValues[$identifier])) { + return $replacementValues[$identifier]; + } + } + + return ''; // Return the original token if no replacement value found + }, $replacedString); + + break; + } + } + + $names[$languageCode] = $replacedString; + } + + $event->setNames($names); + } + + function gggg() + { + + + $inputString = '--'; + $replacementValue = 'Replacement for description'; + +// Define an array with the identifier and their corresponding replacement values + $replacementValues = [ + 'name' => 'Replacement for name', + 'description' => $replacementValue, + ]; + +// Use regular expressions to match and replace the tokens + $pattern = '/<(\w+)>/'; // Exclude the attribute prefix from the pattern + $replacedString = preg_replace_callback($pattern, function ($matches) use ($replacementValues) { + $identifier = $matches[1]; + + if (isset($replacementValues[$identifier])) { + return $replacementValues[$identifier]; + } + + return $matches[0]; // Return the original token if no replacement value found + }, $inputString); + + echo $replacedString; + + + } +} diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index 11f5a42b73..5a48efb4bc 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -6,13 +6,13 @@ */ namespace Ibexa\Core\Repository\Helper; -use Ibexa\Contracts\Core\Persistence\Content\Type as SPIContentType; use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as ContentTypeHandler; use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; -use Ibexa\Core\Base\Exceptions\InvalidArgumentType; +use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * NameSchemaService is internal service for resolving content name and url alias patterns. @@ -58,6 +58,8 @@ class NameSchemaService /** @var array */ protected $settings; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; /** * Constructs a object to resolve $nameSchema with $contentVersion fields values. @@ -66,11 +68,15 @@ class NameSchemaService * @param \Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper $contentTypeDomainMapper * @param \Ibexa\Core\FieldType\FieldTypeRegistry $fieldTypeRegistry * @param array $settings + * @param SchemaIdentifierExtractor $schemaIdentifierExtractor + * @param EventDispatcherInterface $eventDispatcher */ public function __construct( ContentTypeHandler $contentTypeHandler, ContentTypeDomainMapper $contentTypeDomainMapper, FieldTypeRegistry $fieldTypeRegistry, + SchemaIdentifierExtractor $schemaIdentifierExtractor, + EventDispatcherInterface $eventDispatcher, array $settings = [] ) { $this->contentTypeHandler = $contentTypeHandler; @@ -81,6 +87,8 @@ public function __construct( 'limit' => 150, 'sequence' => '...', ]; + $this->eventDispatcher = $eventDispatcher; + $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; } /** @@ -91,18 +99,25 @@ public function __construct( * * @return array */ - public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null) + public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null):array { - if ($contentType === null) { - $contentType = $this->contentTypeHandler->load($content->contentInfo->contentTypeId); - } + $contentType = $contentType ?? $content->getContentType(); + $schemaName = $contentType->urlAliasSchema ?? $contentType->nameSchema; + $schemaIdentifiers = $this->schemaIdentifierExtractor->extract($schemaName); + + $event = new ResolveUrlAliasSchemaEvent( + $schemaName, + $schemaIdentifiers, + $content, + $contentType + ); - return $this->resolve( - strlen($contentType->urlAliasSchema) === 0 ? $contentType->nameSchema : $contentType->urlAliasSchema, - $contentType, - $content->fields, - $content->versionInfo->languageCodes + /** @var ResolveUrlAliasSchemaEvent $event */ + $this->eventDispatcher->dispatch( + $event ); + + return []; } /** @@ -166,19 +181,41 @@ protected function mergeFieldMap(Content $content, array $fieldMap, array $langu /** * Returns the real name for a content name pattern. * + * @deprecated + * * @param string $nameSchema - * @param \Ibexa\Contracts\Core\Persistence\Content\Type|\Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType * @param array $fieldMap * @param array $languageCodes * * @return string[] */ - public function resolve($nameSchema, $contentType, array $fieldMap, array $languageCodes) + public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes) { list($filteredNameSchema, $groupLookupTable) = $this->filterNameSchema($nameSchema); $tokens = $this->extractTokens($filteredNameSchema); $schemaIdentifiers = $this->getIdentifiers($nameSchema); +/* + * -- + * -- + * + * Array +( + [field] => Array + ( + [0] => x + [1] => y + [2] => name + ) + + [attribute] => Array + ( + [0] => xyz + ) + [xyz] => +) + */ $names = []; foreach ($languageCodes as $languageCode) { @@ -191,7 +228,7 @@ public function resolve($nameSchema, $contentType, array $fieldMap, array $langu $string = $this->resolveToken($token, $titles, $groupLookupTable); $name = str_replace($token, $string, $name); } - +//- // Make sure length is not longer then $limit unless it's 0 if ($this->settings['limit'] && mb_strlen($name) > $this->settings['limit']) { $name = rtrim(mb_substr($name, 0, $this->settings['limit'] - strlen($this->settings['sequence']))) . $this->settings['sequence']; @@ -210,7 +247,7 @@ public function resolve($nameSchema, $contentType, array $fieldMap, array $langu * @see \Ibexa\Core\Repository\Values\ContentType\FieldType::getName() * * @param string[] $schemaIdentifiers - * @param \Ibexa\Contracts\Core\Persistence\Content\Type|\Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType + * @param Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType * @param array $fieldMap * @param string $languageCode * @@ -218,40 +255,19 @@ public function resolve($nameSchema, $contentType, array $fieldMap, array $langu * * @return string[] Key is the field identifier, value is the title value */ - protected function getFieldTitles(array $schemaIdentifiers, $contentType, array $fieldMap, $languageCode) + protected function getFieldTitles(array $schemaIdentifiers, ContentType $contentType, array $fieldMap, string $languageCode): array { $fieldTitles = []; foreach ($schemaIdentifiers as $fieldDefinitionIdentifier) { if (isset($fieldMap[$fieldDefinitionIdentifier][$languageCode])) { - if ($contentType instanceof SPIContentType) { - $fieldDefinition = null; - foreach ($contentType->fieldDefinitions as $spiFieldDefinition) { - if ($spiFieldDefinition->identifier === $fieldDefinitionIdentifier) { - $fieldDefinition = $this->contentTypeDomainMapper->buildFieldDefinitionDomainObject( - $spiFieldDefinition, - // This is probably not main language, but as we don't expose it, it's ok for now. - $languageCode - ); - break; - } - } - - if ($fieldDefinition === null) { - $fieldTitles[$fieldDefinitionIdentifier] = ''; - continue; - } - } elseif ($contentType instanceof ContentType) { - $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); - } else { - throw new InvalidArgumentType('$contentType', 'API or SPI variant of a Content Type'); - } + $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); - $fieldTypeService = $this->fieldTypeRegistry->getFieldType( + $persistenceFieldType = $this->fieldTypeRegistry->getFieldType( $fieldDefinition->fieldTypeIdentifier ); - $fieldTitles[$fieldDefinitionIdentifier] = $fieldTypeService->getName( + $fieldTitles[$fieldDefinitionIdentifier] = $persistenceFieldType->getName( $fieldMap[$fieldDefinitionIdentifier][$languageCode], $fieldDefinition, $languageCode @@ -296,7 +312,7 @@ protected function extractTokens($nameSchema) * * @return string */ - protected function resolveToken($token, $titles, $groupLookupTable) + protected function resolveToken(string $token, array $titles, array $groupLookupTable): string { $replaceString = ''; $tokenParts = $this->tokenParts($token); @@ -342,7 +358,7 @@ protected function resolveToken($token, $titles, $groupLookupTable) * * @return bool */ - protected function isTokenGroup($identifier) + protected function isTokenGroup(string $identifier): bool { if (strpos($identifier, self::META_STRING) === false) { return false; @@ -365,7 +381,7 @@ protected function isTokenGroup($identifier) * * @return array */ - protected function tokenParts($token) + protected function tokenParts(string $token): array { return preg_split('#\\W#', $token, -1, PREG_SPLIT_NO_EMPTY); } @@ -380,9 +396,9 @@ protected function tokenParts($token) * * @param string $nameSchema * - * @return string + * @return array */ - protected function filterNameSchema($nameSchema) + protected function filterNameSchema(string $nameSchema): array { $retNamePattern = ''; $foundGroups = preg_match_all('/[<|\\|](\\(.+\\))[\\||>]/U', $nameSchema, $groupArray); @@ -416,27 +432,18 @@ protected function filterNameSchema($nameSchema) * * @return array */ - protected function getIdentifiers($schemaString) + protected function getIdentifiers(string $schemaString): array { - $allTokens = '#<(.*)>#U'; - $identifiers = '#\\W#'; + $allTokensPattern = '#<(.*)>#U'; + $identifiersPattern = '#([^<>]+)#'; - $tmpArray = []; - preg_match_all($allTokens, $schemaString, $matches); - - foreach ($matches[1] as $match) { - $tmpArray[] = preg_split($identifiers, $match, -1, PREG_SPLIT_NO_EMPTY); - } + $matches = []; + preg_match_all($allTokensPattern, $schemaString, $matches); $retArray = []; - foreach ($tmpArray as $matchGroup) { - if (is_array($matchGroup)) { - foreach ($matchGroup as $item) { - $retArray[] = $item; - } - } else { - $retArray[] = $matchGroup; - } + foreach ($matches[1] as $match) { + preg_match_all($identifiersPattern, $match, $tokens); + $retArray = array_merge($retArray, $tokens[1]); } return $retArray; diff --git a/src/lib/Repository/Helper/SchemaIdentifierExtractor.php b/src/lib/Repository/Helper/SchemaIdentifierExtractor.php new file mode 100644 index 0000000000..4a5e2d369d --- /dev/null +++ b/src/lib/Repository/Helper/SchemaIdentifierExtractor.php @@ -0,0 +1,37 @@ +/'; + + + if (false === preg_match_all($allTokens, $schemaString, $matches)) { + return []; + }; + + $strategyIdentifiers = []; + foreach ($matches[1] as $tokenExpression) { + $strategyToken = explode(':', $tokenExpression, 2); + if (count($strategyToken) === 2) { + [$strategy, $token] = $strategyToken; + } else { + $token = $strategyToken[0]; + $strategy = 'field'; + } + + $strategyIdentifiers[$strategy] = array_merge($strategyIdentifiers[$strategy] ?? [], explode('|', $token)); + + } + + return $strategyIdentifiers; + } +} diff --git a/src/lib/Repository/Repository.php b/src/lib/Repository/Repository.php index 0255311edc..eb3660c643 100644 --- a/src/lib/Repository/Repository.php +++ b/src/lib/Repository/Repository.php @@ -42,6 +42,7 @@ use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Helper\NameSchemaService; use Ibexa\Core\Repository\Helper\RelationProcessor; +use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperInterface; @@ -50,6 +51,7 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use RuntimeException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Repository class. @@ -265,6 +267,8 @@ class Repository implements RepositoryInterface private $passwordValidator; private ConfigResolverInterface $configResolver; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; public function __construct( PersistenceHandler $persistenceHandler, @@ -287,6 +291,8 @@ public function __construct( LocationFilteringHandler $locationFilteringHandler, PasswordValidatorInterface $passwordValidator, ConfigResolverInterface $configResolver, + EventDispatcherInterface $eventDispatcher, + SchemaIdentifierExtractor $schemaIdentifierExtractor, array $serviceSettings = [], ?LoggerInterface $logger = null ) { @@ -337,6 +343,8 @@ public function __construct( $this->contentValidator = $contentValidator; $this->passwordValidator = $passwordValidator; $this->configResolver = $configResolver; + $this->eventDispatcher = $eventDispatcher; + $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) @@ -747,6 +755,8 @@ public function getNameSchemaService(): NameSchemaService $this->persistenceHandler->contentTypeHandler(), $this->contentTypeDomainMapper, $this->fieldTypeRegistry, + $this->schemaIdentifierExtractor, + $this->eventDispatcher, $this->serviceSettings['nameSchema'] ); diff --git a/src/lib/Repository/SiteAccessAware/Repository.php b/src/lib/Repository/SiteAccessAware/Repository.php index 750ce2adcc..13f3a0a0a4 100644 --- a/src/lib/Repository/SiteAccessAware/Repository.php +++ b/src/lib/Repository/SiteAccessAware/Repository.php @@ -27,6 +27,8 @@ use Ibexa\Contracts\Core\Repository\URLWildcardService as URLWildcardServiceInterface; use Ibexa\Contracts\Core\Repository\UserPreferenceService as UserPreferenceServiceInterface; use Ibexa\Contracts\Core\Repository\UserService as UserServiceInterface; +use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Repository class. @@ -68,6 +70,8 @@ class Repository implements RepositoryInterface /** @var \Ibexa\Core\Repository\NotificationService */ protected $notificationService; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; /** * Construct repository object from aggregated repository. @@ -84,7 +88,9 @@ public function __construct( TrashService $trashService, LocationService $locationService, LanguageService $languageService, - NotificationService $notificationService + NotificationService $notificationService, + EventDispatcherInterface $eventDispatcher, + SchemaIdentifierExtractor $schemaIdentifierExtractor ) { $this->repository = $repository; $this->contentService = $contentService; @@ -98,6 +104,8 @@ public function __construct( $this->locationService = $locationService; $this->languageService = $languageService; $this->notificationService = $notificationService; + $this->eventDispatcher = $eventDispatcher; + $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) @@ -209,6 +217,16 @@ public function rollback(): void { $this->repository->rollback(); } + + public function getEventDispatcher(): EventDispatcherInterface + { + return $this->eventDispatcher; + } + + public function getSchemaIdentifierExtractor(): SchemaIdentifierExtractor + { + return $this->schemaIdentifierExtractor; + } } class_alias(Repository::class, 'eZ\Publish\Core\Repository\SiteAccessAware\Repository'); diff --git a/src/lib/Resources/settings/repository/siteaccessaware.yml b/src/lib/Resources/settings/repository/siteaccessaware.yml index b8182cf663..6736d8c6e2 100644 --- a/src/lib/Resources/settings/repository/siteaccessaware.yml +++ b/src/lib/Resources/settings/repository/siteaccessaware.yml @@ -20,6 +20,8 @@ services: - '@Ibexa\Core\Repository\SiteAccessAware\LocationService' - '@Ibexa\Core\Repository\SiteAccessAware\LanguageService' - '@Ibexa\Core\Repository\SiteAccessAware\NotificationService' + - '@event_dispatcher' + - '@Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor' Ibexa\Core\Repository\SiteAccessAware\ContentService: arguments: diff --git a/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php new file mode 100644 index 0000000000..2a0c91588d --- /dev/null +++ b/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php @@ -0,0 +1,45 @@ +||'; + $expectedResult = [ + 'field' => ['username', 'title'], + 'relation' => ['author'] + ]; + $this->assertEquals($expectedResult, $extractor->extract($schemaString)); + + // Test with an empty schema string + $schemaString = ''; + $expectedResult = []; + $this->assertEquals($expectedResult, $extractor->extract($schemaString)); + + // Test with a schema string without tokens + $schemaString = 'This is a plain text.'; + $expectedResult = []; + $this->assertEquals($expectedResult, $extractor->extract($schemaString)); + + // Test with a schema string containing invalid tokens + $schemaString = '||'; + $expectedResult = [ + 'field' => ['username'], + 'relation' => ['author'] + ]; + $this->assertEquals($expectedResult, $extractor->extract($schemaString)); + } +} diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index a2df11176d..3fff2c02a3 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -20,6 +20,7 @@ use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\FieldTypeService; use Ibexa\Core\Repository\Helper\RelationProcessor; +use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Mapper\ContentDomainMapper; use Ibexa\Core\Repository\Mapper\ContentMapper; use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; @@ -38,6 +39,7 @@ use Ibexa\Core\Search\Common\BackgroundIndexer\NullIndexer; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Base test case for tests on services using Mock testing. @@ -126,6 +128,8 @@ protected function getRepository(array $serviceSettings = []) $this->getLocationFilteringHandlerMock(), $this->createMock(PasswordValidatorInterface::class), $this->createMock(ConfigResolverInterface::class), + $this->createMock(EventDispatcherInterface::class), + $this->createMock(SchemaIdentifierExtractor::class), $serviceSettings, ); From 093436eff26582bb8bbbde17ddb6ad5ef0966a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Mon, 19 Jun 2023 15:00:06 +0200 Subject: [PATCH 02/37] more readabillity --- .../EventSubscriber/NameSchemaSubscriber.php | 84 +++++++------------ 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index 9b49b8d00c..d1434c1025 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -1,59 +1,39 @@ 'send', + ResolveUrlAliasSchemaEvent::class => 'onResolveUrlAliasSchema', ]; } - public function send(ResolveUrlAliasSchemaEvent $event): void + public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void { + $inputString = $event->getSchemaName(); + $identifiers = $event->getSchemaIdentifiers()['field'] ?? []; $contentType = $event->getContentType(); - /** @var array $languageCodes */ $languageCodes = $event->getContent()->versionInfo->languageCodes; $names = []; - $identifiers = $event->getSchemaIdentifiers()['field'] ?? []; - $inputString = $event->getSchemaName(); - - $contentType->fieldDefinitions; - foreach ($languageCodes as $languageCode) { $replacedString = $inputString; - foreach ($identifiers as $identifier) { - /** @var \Ibexa\Core\Repository\Values\ContentType\FieldDefinition $fd */ - foreach ($contentType->getFieldDefinitions() as $key => $fd) { - $pattern = '/<(\w+)>/'; // Exclude the attribute prefix from the pattern - $replacedString = preg_replace_callback($pattern, function ($matches) use ($identifier, $key) { - if ($identifier == $key) { - $identifier = $matches[1]; - - if (isset($replacementValues[$identifier])) { - return $replacementValues[$identifier]; - } - } - return ''; // Return the original token if no replacement value found - }, $replacedString); - - break; - } + foreach ($identifiers as $identifier) { + $replacedString = $this->replacePlaceholdersInString( + $replacedString, + $identifier, + $contentType + ); } $names[$languageCode] = $replacedString; @@ -62,33 +42,25 @@ public function send(ResolveUrlAliasSchemaEvent $event): void $event->setNames($names); } - function gggg() - { - - - $inputString = '--'; - $replacementValue = 'Replacement for description'; - -// Define an array with the identifier and their corresponding replacement values - $replacementValues = [ - 'name' => 'Replacement for name', - 'description' => $replacementValue, - ]; - -// Use regular expressions to match and replace the tokens - $pattern = '/<(\w+)>/'; // Exclude the attribute prefix from the pattern - $replacedString = preg_replace_callback($pattern, function ($matches) use ($replacementValues) { - $identifier = $matches[1]; - - if (isset($replacementValues[$identifier])) { - return $replacementValues[$identifier]; + private function replacePlaceholdersInString( + string $inputString, + string $identifier, + $contentType + ): string { + $pattern = '/<(\w+)>/'; + + return preg_replace_callback($pattern, function ($matches) use ($identifier, $contentType) { + $fieldIdentifier = $matches[1]; + + foreach ($contentType->getFieldDefinitions() as $key => $fieldDefinition) { + if ($identifier == $key) { + if (isset($replacementValues[$fieldIdentifier])) { + return $replacementValues[$fieldIdentifier]; + } + } } - return $matches[0]; // Return the original token if no replacement value found + return ''; // Return the original token if no replacement value found }, $inputString); - - echo $replacedString; - - } } From 635cbac91272dc8d4959dde45e4a1bcf31837bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Wed, 21 Jun 2023 00:24:57 +0200 Subject: [PATCH 03/37] Fixed extension point --- .../Event/ResolveUrlAliasSchemaEvent.php | 8 ++- .../EventSubscriber/NameSchemaSubscriber.php | 49 +++++-------------- .../Repository/Helper/NameSchemaService.php | 39 +++------------ 3 files changed, 27 insertions(+), 69 deletions(-) diff --git a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php index 6a1cbba8b0..e6814ccf96 100644 --- a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php +++ b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php @@ -20,8 +20,12 @@ final class ResolveUrlAliasSchemaEvent extends Event private array $names = []; private string $schemaName; - public function __construct(string $schemaName, array $schemaIdentifiers, Content $content, ContentType $contentType = null) - { + public function __construct( + string $schemaName, + array $schemaIdentifiers, + Content $content, + ContentType $contentType = null + ) { $this->schemaIdentifiers = $schemaIdentifiers; $this->content = $content; $this->contentType = $contentType; diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index d1434c1025..fc65f872ca 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -12,55 +12,32 @@ final class NameSchemaSubscriber implements EventSubscriberInterface public static function getSubscribedEvents(): array { return [ - ResolveUrlAliasSchemaEvent::class => 'onResolveUrlAliasSchema', + ResolveUrlAliasSchemaEvent::class => [ + ['onResolveUrlAliasSchema', 0], + ], ]; } + public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void { $inputString = $event->getSchemaName(); - $identifiers = $event->getSchemaIdentifiers()['field'] ?? []; - $contentType = $event->getContentType(); + $content = $event->getContent(); $languageCodes = $event->getContent()->versionInfo->languageCodes; $names = []; foreach ($languageCodes as $languageCode) { - $replacedString = $inputString; - - foreach ($identifiers as $identifier) { - $replacedString = $this->replacePlaceholdersInString( - $replacedString, - $identifier, - $contentType - ); - } - - $names[$languageCode] = $replacedString; + $pattern = '/<(\w+)>/'; + $stringToReplace = $names[$languageCode] ?? $inputString; + $stringToReplace = preg_replace_callback($pattern, function ($matches) use ($content, $languageCode) { + $fieldIdentifier = $matches[1]; + + return $content->getFieldValue($fieldIdentifier, $languageCode); + }, $stringToReplace); + $names[$languageCode] = $stringToReplace; } $event->setNames($names); } - - private function replacePlaceholdersInString( - string $inputString, - string $identifier, - $contentType - ): string { - $pattern = '/<(\w+)>/'; - - return preg_replace_callback($pattern, function ($matches) use ($identifier, $contentType) { - $fieldIdentifier = $matches[1]; - - foreach ($contentType->getFieldDefinitions() as $key => $fieldDefinition) { - if ($identifier == $key) { - if (isset($replacementValues[$fieldIdentifier])) { - return $replacementValues[$fieldIdentifier]; - } - } - } - - return ''; // Return the original token if no replacement value found - }, $inputString); - } } diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index 5a48efb4bc..5907ba766e 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -105,19 +105,17 @@ public function resolveUrlAliasSchema(Content $content, ContentType $contentType $schemaName = $contentType->urlAliasSchema ?? $contentType->nameSchema; $schemaIdentifiers = $this->schemaIdentifierExtractor->extract($schemaName); - $event = new ResolveUrlAliasSchemaEvent( - $schemaName, - $schemaIdentifiers, - $content, - $contentType - ); - /** @var ResolveUrlAliasSchemaEvent $event */ - $this->eventDispatcher->dispatch( - $event + $event = $this->eventDispatcher->dispatch( + new ResolveUrlAliasSchemaEvent( + $schemaName, + $schemaIdentifiers, + $content, + $contentType + ) ); - return []; + return $event->getNames(); } /** @@ -196,26 +194,6 @@ public function resolve(string $nameSchema, ContentType $contentType, array $fie $tokens = $this->extractTokens($filteredNameSchema); $schemaIdentifiers = $this->getIdentifiers($nameSchema); -/* - * -- - * -- - * - * Array -( - [field] => Array - ( - [0] => x - [1] => y - [2] => name - ) - - [attribute] => Array - ( - [0] => xyz - ) - [xyz] => -) - */ $names = []; foreach ($languageCodes as $languageCode) { @@ -228,7 +206,6 @@ public function resolve(string $nameSchema, ContentType $contentType, array $fie $string = $this->resolveToken($token, $titles, $groupLookupTable); $name = str_replace($token, $string, $name); } -//- // Make sure length is not longer then $limit unless it's 0 if ($this->settings['limit'] && mb_strlen($name) > $this->settings['limit']) { $name = rtrim(mb_substr($name, 0, $this->settings['limit'] - strlen($this->settings['sequence']))) . $this->settings['sequence']; From 368e7c3cf0a185948827a436d49233858da065ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Mon, 26 Jun 2023 12:07:00 +0200 Subject: [PATCH 04/37] Fixed priority --- src/contracts/Event/ResolveUrlAliasSchemaEvent.php | 1 + src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php index e6814ccf96..71545c14c5 100644 --- a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php +++ b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php @@ -13,6 +13,7 @@ final class ResolveUrlAliasSchemaEvent extends Event { + /** @var array */ private array $schemaIdentifiers; private Content $content; private ?ContentType $contentType; diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index fc65f872ca..b5719fdec4 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -13,7 +13,7 @@ public static function getSubscribedEvents(): array { return [ ResolveUrlAliasSchemaEvent::class => [ - ['onResolveUrlAliasSchema', 0], + ['onResolveUrlAliasSchema', -100], ], ]; } @@ -25,7 +25,7 @@ public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void $content = $event->getContent(); $languageCodes = $event->getContent()->versionInfo->languageCodes; - $names = []; + $names = $event->getNames(); foreach ($languageCodes as $languageCode) { $pattern = '/<(\w+)>/'; From 0228b20c4c609978d8261286d9f3354869a4e4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Tue, 27 Jun 2023 00:50:26 +0200 Subject: [PATCH 05/37] Refactored NameSchemaSubscriber --- .../Core/ApiLoader/RepositoryFactory.php | 2 + .../Event/ResolveUrlAliasSchemaEvent.php | 25 ++------- src/lib/Event/Repository.php | 2 + .../EventSubscriber/NameSchemaSubscriber.php | 31 ++++++----- .../Repository/Helper/NameSchemaService.php | 53 +++++++++++++------ .../Helper/SchemaIdentifierExtractor.php | 5 +- src/lib/Repository/Repository.php | 2 + .../Repository/SiteAccessAware/Repository.php | 2 + .../Helper/SchemaIdentifierExtractorTest.php | 5 +- 9 files changed, 71 insertions(+), 56 deletions(-) diff --git a/src/bundle/Core/ApiLoader/RepositoryFactory.php b/src/bundle/Core/ApiLoader/RepositoryFactory.php index 2652af59c5..d350c3daa2 100644 --- a/src/bundle/Core/ApiLoader/RepositoryFactory.php +++ b/src/bundle/Core/ApiLoader/RepositoryFactory.php @@ -53,7 +53,9 @@ class RepositoryFactory implements ContainerAwareInterface /** @var \Ibexa\Contracts\Core\Repository\LanguageResolver */ private $languageResolver; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; public function __construct( diff --git a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php index 71545c14c5..3500817bb4 100644 --- a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php +++ b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php @@ -1,4 +1,5 @@ */ private array $schemaIdentifiers; + private Content $content; - private ?ContentType $contentType; private array $names = []; - private string $schemaName; public function __construct( - string $schemaName, array $schemaIdentifiers, - Content $content, - ContentType $contentType = null + Content $content ) { $this->schemaIdentifiers = $schemaIdentifiers; $this->content = $content; - $this->contentType = $contentType; - $this->schemaName = $schemaName; } public function getSchemaIdentifiers(): array @@ -43,22 +38,12 @@ public function getContent(): Content return $this->content; } - public function getContentType(): ?ContentType - { - return $this->contentType; - } - - public function getSchemaName(): string - { - return $this->schemaName; - } - - public function getNames(): array + public function getTokenValues(): array { return $this->names; } - public function setNames(array $names): void + public function setTokenValues(array $names): void { $this->names = $names; } diff --git a/src/lib/Event/Repository.php b/src/lib/Event/Repository.php index 7b1a040bf8..44210e05a8 100644 --- a/src/lib/Event/Repository.php +++ b/src/lib/Event/Repository.php @@ -85,7 +85,9 @@ final class Repository implements RepositoryInterface /** @var \Ibexa\Contracts\Core\Repository\UserService */ private $userService; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; public function __construct( diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index b5719fdec4..b7fdf611ea 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -1,5 +1,9 @@ getSchemaName(); + if (!array_key_exists('field', $event->getSchemaIdentifiers())) { + return; + } + $content = $event->getContent(); + $identifiers = $event->getSchemaIdentifiers()['field']; $languageCodes = $event->getContent()->versionInfo->languageCodes; - - $names = $event->getNames(); - + $tokenValues = $event->getTokenValues(); foreach ($languageCodes as $languageCode) { - $pattern = '/<(\w+)>/'; - $stringToReplace = $names[$languageCode] ?? $inputString; - $stringToReplace = preg_replace_callback($pattern, function ($matches) use ($content, $languageCode) { - $fieldIdentifier = $matches[1]; - - return $content->getFieldValue($fieldIdentifier, $languageCode); - }, $stringToReplace); - $names[$languageCode] = $stringToReplace; + foreach ($identifiers as $identifier) { + $tokenValues[$languageCode][$identifier] = $content->getFieldValue( + $identifier, + $languageCode + ) ?? $identifier; + } } - $event->setNames($names); + $event->setTokenValues($tokenValues); } } diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index 5907ba766e..4ac780a0a0 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -6,10 +6,10 @@ */ namespace Ibexa\Core\Repository\Helper; +use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as ContentTypeHandler; use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; -use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; @@ -58,7 +58,9 @@ class NameSchemaService /** @var array */ protected $settings; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; /** @@ -69,7 +71,7 @@ class NameSchemaService * @param \Ibexa\Core\FieldType\FieldTypeRegistry $fieldTypeRegistry * @param array $settings * @param SchemaIdentifierExtractor $schemaIdentifierExtractor - * @param EventDispatcherInterface $eventDispatcher + * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $eventDispatcher */ public function __construct( ContentTypeHandler $contentTypeHandler, @@ -99,23 +101,33 @@ public function __construct( * * @return array */ - public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null):array + public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array { $contentType = $contentType ?? $content->getContentType(); - $schemaName = $contentType->urlAliasSchema ?? $contentType->nameSchema; + $schemaName = $contentType->urlAliasSchema ?: $contentType->nameSchema; $schemaIdentifiers = $this->schemaIdentifierExtractor->extract($schemaName); - /** @var ResolveUrlAliasSchemaEvent $event */ + /** @var \Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent $event */ $event = $this->eventDispatcher->dispatch( new ResolveUrlAliasSchemaEvent( - $schemaName, $schemaIdentifiers, - $content, - $contentType + $content ) ); - return $event->getNames(); + $names = []; + $tokens = $event->getTokenValues(); + $extractedTokens = $this->extractTokens($schemaName); + foreach ($tokens as $languageCode => $tokenValues) { + $schema = $schemaName; + foreach ($extractedTokens as $extractedToken) { + $name = $this->resolveToken($extractedToken, $tokenValues, []); + $schema = str_replace($extractedToken, $name, $schema); + } + $names[$languageCode] = $schema; + } + + return $names; } /** @@ -179,8 +191,6 @@ protected function mergeFieldMap(Content $content, array $fieldMap, array $langu /** * Returns the real name for a content name pattern. * - * @deprecated - * * @param string $nameSchema * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType * @param array $fieldMap @@ -206,10 +216,7 @@ public function resolve(string $nameSchema, ContentType $contentType, array $fie $string = $this->resolveToken($token, $titles, $groupLookupTable); $name = str_replace($token, $string, $name); } - // Make sure length is not longer then $limit unless it's 0 - if ($this->settings['limit'] && mb_strlen($name) > $this->settings['limit']) { - $name = rtrim(mb_substr($name, 0, $this->settings['limit'] - strlen($this->settings['sequence']))) . $this->settings['sequence']; - } + $name = $this->validateNameLength($name); $names[$languageCode] = $name; } @@ -224,7 +231,7 @@ public function resolve(string $nameSchema, ContentType $contentType, array $fie * @see \Ibexa\Core\Repository\Values\ContentType\FieldType::getName() * * @param string[] $schemaIdentifiers - * @param Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType * @param array $fieldMap * @param string $languageCode * @@ -360,7 +367,7 @@ protected function isTokenGroup(string $identifier): bool */ protected function tokenParts(string $token): array { - return preg_split('#\\W#', $token, -1, PREG_SPLIT_NO_EMPTY); + return preg_split('/[^\w:]+/', $token, -1, PREG_SPLIT_NO_EMPTY); } /** @@ -425,6 +432,18 @@ protected function getIdentifiers(string $schemaString): array return $retArray; } + + public function validateNameLength(string $name): string + { + // Make sure length is not longer then $limit unless it's 0 + if ($this->settings['limit'] && mb_strlen($name) > $this->settings['limit']) { + $name = rtrim( + mb_substr($name, 0, $this->settings['limit'] - strlen($this->settings['sequence'])) + ) . $this->settings['sequence']; + } + + return $name; + } } class_alias(NameSchemaService::class, 'eZ\Publish\Core\Repository\Helper\NameSchemaService'); diff --git a/src/lib/Repository/Helper/SchemaIdentifierExtractor.php b/src/lib/Repository/Helper/SchemaIdentifierExtractor.php index 4a5e2d369d..4a650a660e 100644 --- a/src/lib/Repository/Helper/SchemaIdentifierExtractor.php +++ b/src/lib/Repository/Helper/SchemaIdentifierExtractor.php @@ -1,4 +1,5 @@ /'; - if (false === preg_match_all($allTokens, $schemaString, $matches)) { return []; - }; + } $strategyIdentifiers = []; foreach ($matches[1] as $tokenExpression) { @@ -29,7 +29,6 @@ public function extract($schemaString) } $strategyIdentifiers[$strategy] = array_merge($strategyIdentifiers[$strategy] ?? [], explode('|', $token)); - } return $strategyIdentifiers; diff --git a/src/lib/Repository/Repository.php b/src/lib/Repository/Repository.php index eb3660c643..24a5a19c09 100644 --- a/src/lib/Repository/Repository.php +++ b/src/lib/Repository/Repository.php @@ -267,7 +267,9 @@ class Repository implements RepositoryInterface private $passwordValidator; private ConfigResolverInterface $configResolver; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; public function __construct( diff --git a/src/lib/Repository/SiteAccessAware/Repository.php b/src/lib/Repository/SiteAccessAware/Repository.php index 13f3a0a0a4..ec79e7a9a1 100644 --- a/src/lib/Repository/SiteAccessAware/Repository.php +++ b/src/lib/Repository/SiteAccessAware/Repository.php @@ -70,7 +70,9 @@ class Repository implements RepositoryInterface /** @var \Ibexa\Core\Repository\NotificationService */ protected $notificationService; + private EventDispatcherInterface $eventDispatcher; + private SchemaIdentifierExtractor $schemaIdentifierExtractor; /** diff --git a/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php index 2a0c91588d..f869ebb1d9 100644 --- a/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php +++ b/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php @@ -1,4 +1,5 @@ ||'; $expectedResult = [ 'field' => ['username', 'title'], - 'relation' => ['author'] + 'relation' => ['author'], ]; $this->assertEquals($expectedResult, $extractor->extract($schemaString)); @@ -38,7 +39,7 @@ public function testExtract() $schemaString = '||'; $expectedResult = [ 'field' => ['username'], - 'relation' => ['author'] + 'relation' => ['author'], ]; $this->assertEquals($expectedResult, $extractor->extract($schemaString)); } From e7c1a20465b738bcf0b10179f8b9769e6ca1772f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Tue, 27 Jun 2023 11:31:22 +0200 Subject: [PATCH 06/37] Fixed url schema generation --- src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php | 2 +- src/lib/Repository/Helper/NameSchemaService.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index b7fdf611ea..3ba7f37a42 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -34,7 +34,7 @@ public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void $tokenValues = $event->getTokenValues(); foreach ($languageCodes as $languageCode) { foreach ($identifiers as $identifier) { - $tokenValues[$languageCode][$identifier] = $content->getFieldValue( + $tokenValues[$languageCode][$identifier] = (string) $content->getFieldValue( $identifier, $languageCode ) ?? $identifier; diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index 4ac780a0a0..181de1f33a 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -274,7 +274,7 @@ protected function getFieldTitles(array $schemaIdentifiers, ContentType $content * * @return array */ - protected function extractTokens($nameSchema) + protected function extractTokens(string $nameSchema): array { preg_match_all( '|<([^>]+)>|U', From 7a58566a9a90bc31243e284225666848bf6d34e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Wed, 28 Jun 2023 15:34:42 +0200 Subject: [PATCH 07/37] Ref --- .../Core/ApiLoader/RepositoryFactory.php | 12 +- src/bundle/Core/Resources/config/papi.yml | 3 - src/bundle/Core/Resources/config/services.yml | 17 + .../NameSchema/NameSchemaServiceInterface.php | 24 + .../SchemaIdentifierExtractorInterface.php | 17 + .../Container/ApiLoader/RepositoryFactory.php | 4 +- src/lib/Event/Repository.php | 12 +- .../Repository/Helper/NameSchemaService.php | 417 +----------------- .../NameSchema/NameSchemaService.php | 405 +++++++++++++++++ .../SchemaIdentifierExtractor.php | 21 +- src/lib/Repository/Repository.php | 27 +- .../Repository/SiteAccessAware/Repository.php | 12 +- src/lib/Repository/URLAliasService.php | 5 +- .../Resources/settings/repository/inner.yml | 1 + .../settings/repository/siteaccessaware.yml | 2 +- .../Helper/SchemaIdentifierExtractorTest.php | 2 +- tests/lib/Repository/Service/Mock/Base.php | 2 +- 17 files changed, 519 insertions(+), 464 deletions(-) create mode 100644 src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php create mode 100644 src/contracts/Repository/NameSchema/SchemaIdentifierExtractorInterface.php create mode 100644 src/lib/Repository/NameSchema/NameSchemaService.php rename src/lib/Repository/{Helper => NameSchema}/SchemaIdentifierExtractor.php (57%) diff --git a/src/bundle/Core/ApiLoader/RepositoryFactory.php b/src/bundle/Core/ApiLoader/RepositoryFactory.php index d350c3daa2..8e4c64c4f9 100644 --- a/src/bundle/Core/ApiLoader/RepositoryFactory.php +++ b/src/bundle/Core/ApiLoader/RepositoryFactory.php @@ -10,6 +10,7 @@ use Ibexa\Contracts\Core\Persistence\Filter\Location\Handler as LocationFilteringHandler; use Ibexa\Contracts\Core\Persistence\Handler as PersistenceHandler; use Ibexa\Contracts\Core\Repository\LanguageResolver; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; use Ibexa\Contracts\Core\Repository\PermissionService; use Ibexa\Contracts\Core\Repository\Repository; @@ -19,7 +20,6 @@ use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Helper\RelationProcessor; -use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Mapper; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; @@ -56,15 +56,12 @@ class RepositoryFactory implements ContainerAwareInterface private EventDispatcherInterface $eventDispatcher; - private SchemaIdentifierExtractor $schemaIdentifierExtractor; - public function __construct( ConfigResolverInterface $configResolver, $repositoryClass, array $policyMap, LanguageResolver $languageResolver, EventDispatcherInterface $eventDispatcher, - SchemaIdentifierExtractor $schemaIdentifierExtractor, LoggerInterface $logger = null ) { $this->configResolver = $configResolver; @@ -73,7 +70,7 @@ public function __construct( $this->languageResolver = $languageResolver; $this->logger = null !== $logger ? $logger : new NullLogger(); $this->eventDispatcher = $eventDispatcher; - $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; + $this->nameSchemaService = $nameSchemaService; } /** @@ -101,7 +98,8 @@ public function buildRepository( ContentFilteringHandler $contentFilteringHandler, LocationFilteringHandler $locationFilteringHandler, PasswordValidatorInterface $passwordValidator, - ConfigResolverInterface $configResolver + ConfigResolverInterface $configResolver, + NameSchemaServiceInterface $nameSchemaService ): Repository { $config = $this->container->get(RepositoryConfigurationProvider::class)->getRepositoryConfig(); @@ -127,7 +125,7 @@ public function buildRepository( $passwordValidator, $configResolver, $this->eventDispatcher, - $this->schemaIdentifierExtractor, + $nameSchemaService, [ 'role' => [ 'policyMap' => $this->policyMap, diff --git a/src/bundle/Core/Resources/config/papi.yml b/src/bundle/Core/Resources/config/papi.yml index 2e02d765a1..3e0b7fe3de 100644 --- a/src/bundle/Core/Resources/config/papi.yml +++ b/src/bundle/Core/Resources/config/papi.yml @@ -18,13 +18,10 @@ services: - '%ibexa.api.role.policy_map%' - '@Ibexa\Contracts\Core\Repository\LanguageResolver' - '@event_dispatcher' - - '@Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor' - "@?logger" calls: - [setContainer, ["@service_container"]] - Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor: ~ - Ibexa\Bundle\Core\ApiLoader\StorageEngineFactory: class: Ibexa\Bundle\Core\ApiLoader\StorageEngineFactory arguments: diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml index c154ad30a0..09f764fa2d 100644 --- a/src/bundle/Core/Resources/config/services.yml +++ b/src/bundle/Core/Resources/config/services.yml @@ -364,6 +364,23 @@ services: Ibexa\Bundle\Core\Translation\Policy\PolicyTranslationDefinitionProvider: ~ + Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor: + + Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface: + alias: 'Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor' + + Ibexa\Core\Repository\NameSchema\NameSchemaService: + lazy: true + arguments: + - '@ibexa.api.persistence_handler' + - '@Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper' + - '@Ibexa\Core\FieldType\FieldTypeRegistry' + - '@Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface' + - '@event_dispatcher' + + Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface: + alias: 'Ibexa\Core\Repository\NameSchema\NameSchemaService' + Ibexa\Core\Repository\EventSubscriber\NameSchemaSubscriber: tags: - { name: kernel.event_subscriber } diff --git a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php new file mode 100644 index 0000000000..168f95ce9e --- /dev/null +++ b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php @@ -0,0 +1,24 @@ +> + */ + public function extract(string $schemaString): array; +} diff --git a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php index a94b3531e2..57c5bba613 100644 --- a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php +++ b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php @@ -10,6 +10,7 @@ use Ibexa\Contracts\Core\Persistence\Filter\Location\Handler as LocationFilteringHandler; use Ibexa\Contracts\Core\Persistence\Handler as PersistenceHandler; use Ibexa\Contracts\Core\Repository\LanguageResolver; +use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; use Ibexa\Contracts\Core\Repository\PermissionService; use Ibexa\Contracts\Core\Repository\Repository; @@ -20,7 +21,6 @@ use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Helper\RelationProcessor; -use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Mapper; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; @@ -86,7 +86,7 @@ public function buildRepository( PasswordValidatorInterface $passwordValidator, ConfigResolverInterface $configResolver, EventDispatcherInterface $eventDispatcher, - SchemaIdentifierExtractor $schemaIdentifierExtractor, + SchemaIdentifierExtractorInterface $schemaIdentifierExtractor, array $languages ): Repository { return new $this->repositoryClass( diff --git a/src/lib/Event/Repository.php b/src/lib/Event/Repository.php index 44210e05a8..2af82174e7 100644 --- a/src/lib/Event/Repository.php +++ b/src/lib/Event/Repository.php @@ -14,6 +14,7 @@ use Ibexa\Contracts\Core\Repository\FieldTypeService as FieldTypeServiceInterface; use Ibexa\Contracts\Core\Repository\LanguageService as LanguageServiceInterface; use Ibexa\Contracts\Core\Repository\LocationService as LocationServiceInterface; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\ObjectStateService as ObjectStateServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver as PermissionResolverInterface; @@ -27,7 +28,6 @@ use Ibexa\Contracts\Core\Repository\URLWildcardService as URLWildcardServiceInterface; use Ibexa\Contracts\Core\Repository\UserPreferenceService as UserPreferenceServiceInterface; use Ibexa\Contracts\Core\Repository\UserService as UserServiceInterface; -use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class Repository implements RepositoryInterface @@ -88,7 +88,7 @@ final class Repository implements RepositoryInterface private EventDispatcherInterface $eventDispatcher; - private SchemaIdentifierExtractor $schemaIdentifierExtractor; + private NameSchemaServiceInterface $nameSchemaService; public function __construct( RepositoryInterface $repository, @@ -110,7 +110,7 @@ public function __construct( UserPreferenceServiceInterface $userPreferenceService, UserServiceInterface $userService, EventDispatcherInterface $eventDispatcher, - SchemaIdentifierExtractor $schemaIdentifierExtractor + NameSchemaServiceInterface $nameSchemaService ) { $this->repository = $repository; $this->bookmarkService = $bookmarkService; @@ -131,7 +131,7 @@ public function __construct( $this->userPreferenceService = $userPreferenceService; $this->userService = $userService; $this->eventDispatcher = $eventDispatcher; - $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; + $this->nameSchemaService = $nameSchemaService; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) @@ -249,9 +249,9 @@ public function getEventDispatcher(): EventDispatcherInterface return $this->eventDispatcher; } - public function getSchemaIdentifierExtractor(): SchemaIdentifierExtractor + public function getNameSchemaService(): NameSchemaServiceInterface { - return $this->schemaIdentifierExtractor; + return $this->nameSchemaService; } } diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index 181de1f33a..ee4531090d 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -6,13 +6,7 @@ */ namespace Ibexa\Core\Repository\Helper; -use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; -use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as ContentTypeHandler; -use Ibexa\Contracts\Core\Repository\Values\Content\Content; -use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; -use Ibexa\Core\FieldType\FieldTypeRegistry; -use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Ibexa\Core\Repository\NameSchema\NameSchemaService as NativeNameSchemaService; /** * NameSchemaService is internal service for resolving content name and url alias patterns. @@ -37,413 +31,12 @@ * Tokens are the field definition identifiers which are used in the class edit-interface. * * @internal Meant for internal use by Repository. + * + * @deprecated inject \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface instead. + * @see \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ -class NameSchemaService +final class NameSchemaService extends NativeNameSchemaService { - /** - * The string to use to signify group tokens. - * - * @var string - */ - public const META_STRING = 'EZMETAGROUP_'; - - /** @var \Ibexa\Contracts\Core\Persistence\Content\Type\Handler */ - protected $contentTypeHandler; - - /** @var \Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper */ - protected $contentTypeDomainMapper; - - /** @var \Ibexa\Core\FieldType\FieldTypeRegistry */ - protected $fieldTypeRegistry; - - /** @var array */ - protected $settings; - - private EventDispatcherInterface $eventDispatcher; - - private SchemaIdentifierExtractor $schemaIdentifierExtractor; - - /** - * Constructs a object to resolve $nameSchema with $contentVersion fields values. - * - * @param \Ibexa\Contracts\Core\Persistence\Content\Type\Handler $contentTypeHandler - * @param \Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper $contentTypeDomainMapper - * @param \Ibexa\Core\FieldType\FieldTypeRegistry $fieldTypeRegistry - * @param array $settings - * @param SchemaIdentifierExtractor $schemaIdentifierExtractor - * @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $eventDispatcher - */ - public function __construct( - ContentTypeHandler $contentTypeHandler, - ContentTypeDomainMapper $contentTypeDomainMapper, - FieldTypeRegistry $fieldTypeRegistry, - SchemaIdentifierExtractor $schemaIdentifierExtractor, - EventDispatcherInterface $eventDispatcher, - array $settings = [] - ) { - $this->contentTypeHandler = $contentTypeHandler; - $this->contentTypeDomainMapper = $contentTypeDomainMapper; - $this->fieldTypeRegistry = $fieldTypeRegistry; - // Union makes sure default settings are ignored if provided in argument - $this->settings = $settings + [ - 'limit' => 150, - 'sequence' => '...', - ]; - $this->eventDispatcher = $eventDispatcher; - $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; - } - - /** - * Convenience method for resolving URL alias schema. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType|null $contentType - * - * @return array - */ - public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array - { - $contentType = $contentType ?? $content->getContentType(); - $schemaName = $contentType->urlAliasSchema ?: $contentType->nameSchema; - $schemaIdentifiers = $this->schemaIdentifierExtractor->extract($schemaName); - - /** @var \Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent $event */ - $event = $this->eventDispatcher->dispatch( - new ResolveUrlAliasSchemaEvent( - $schemaIdentifiers, - $content - ) - ); - - $names = []; - $tokens = $event->getTokenValues(); - $extractedTokens = $this->extractTokens($schemaName); - foreach ($tokens as $languageCode => $tokenValues) { - $schema = $schemaName; - foreach ($extractedTokens as $extractedToken) { - $name = $this->resolveToken($extractedToken, $tokenValues, []); - $schema = str_replace($extractedToken, $name, $schema); - } - $names[$languageCode] = $schema; - } - - return $names; - } - - /** - * Convenience method for resolving name schema. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content - * @param array $fieldMap - * @param array $languageCodes - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType|null $contentType - * - * @return array - */ - public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null) - { - if ($contentType === null) { - $contentType = $this->contentTypeHandler->load($content->contentInfo->contentTypeId); - } - - $languageCodes = $languageCodes ?: $content->versionInfo->languageCodes; - - return $this->resolve( - $contentType->nameSchema, - $contentType, - $this->mergeFieldMap( - $content, - $fieldMap, - $languageCodes - ), - $languageCodes - ); - } - - /** - * Convenience method for resolving name schema. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content - * @param array $fieldMap - * @param array $languageCodes - * - * @return array - */ - protected function mergeFieldMap(Content $content, array $fieldMap, array $languageCodes) - { - if (empty($fieldMap)) { - return $content->fields; - } - - $mergedFieldMap = []; - - foreach ($content->fields as $fieldIdentifier => $fieldLanguageMap) { - foreach ($languageCodes as $languageCode) { - $mergedFieldMap[$fieldIdentifier][$languageCode] = isset($fieldMap[$fieldIdentifier][$languageCode]) - ? $fieldMap[$fieldIdentifier][$languageCode] - : $fieldLanguageMap[$languageCode]; - } - } - - return $mergedFieldMap; - } - - /** - * Returns the real name for a content name pattern. - * - * @param string $nameSchema - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType - * @param array $fieldMap - * @param array $languageCodes - * - * @return string[] - */ - public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes) - { - list($filteredNameSchema, $groupLookupTable) = $this->filterNameSchema($nameSchema); - $tokens = $this->extractTokens($filteredNameSchema); - $schemaIdentifiers = $this->getIdentifiers($nameSchema); - - $names = []; - - foreach ($languageCodes as $languageCode) { - // Fetch titles for language code - $titles = $this->getFieldTitles($schemaIdentifiers, $contentType, $fieldMap, $languageCode); - $name = $filteredNameSchema; - - // Replace tokens with real values - foreach ($tokens as $token) { - $string = $this->resolveToken($token, $titles, $groupLookupTable); - $name = str_replace($token, $string, $name); - } - $name = $this->validateNameLength($name); - - $names[$languageCode] = $name; - } - - return $names; - } - - /** - * Fetches the list of available Field identifiers in the token and returns - * an array of their current title value. - * - * @see \Ibexa\Core\Repository\Values\ContentType\FieldType::getName() - * - * @param string[] $schemaIdentifiers - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType - * @param array $fieldMap - * @param string $languageCode - * - * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentType - * - * @return string[] Key is the field identifier, value is the title value - */ - protected function getFieldTitles(array $schemaIdentifiers, ContentType $contentType, array $fieldMap, string $languageCode): array - { - $fieldTitles = []; - - foreach ($schemaIdentifiers as $fieldDefinitionIdentifier) { - if (isset($fieldMap[$fieldDefinitionIdentifier][$languageCode])) { - $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); - - $persistenceFieldType = $this->fieldTypeRegistry->getFieldType( - $fieldDefinition->fieldTypeIdentifier - ); - - $fieldTitles[$fieldDefinitionIdentifier] = $persistenceFieldType->getName( - $fieldMap[$fieldDefinitionIdentifier][$languageCode], - $fieldDefinition, - $languageCode - ); - } - } - - return $fieldTitles; - } - - /** - * Extract all tokens from $namePattern. - * - * Example: - * - * Text <token> more text ==> <token> - * - * - * @param string $nameSchema - * - * @return array - */ - protected function extractTokens(string $nameSchema): array - { - preg_match_all( - '|<([^>]+)>|U', - $nameSchema, - $tokenArray - ); - - return $tokenArray[0]; - } - - /** - * Looks up the value $token should be replaced with and returns this as - * a string. Meta strings denoting token groups are automatically - * inferred. - * - * @param string $token - * @param array $titles - * @param array $groupLookupTable - * - * @return string - */ - protected function resolveToken(string $token, array $titles, array $groupLookupTable): string - { - $replaceString = ''; - $tokenParts = $this->tokenParts($token); - - foreach ($tokenParts as $tokenPart) { - if ($this->isTokenGroup($tokenPart)) { - $replaceString = $groupLookupTable[$tokenPart]; - $groupTokenArray = $this->extractTokens($replaceString); - - foreach ($groupTokenArray as $groupToken) { - $replaceString = str_replace( - $groupToken, - $this->resolveToken( - $groupToken, - $titles, - $groupLookupTable - ), - $replaceString - ); - } - - // We want to stop after the first matching token part / identifier is found - // if id1 has a value, id2 will not be used. - // In this case id1 or id1 is a token group. - break; - } else { - if (array_key_exists($tokenPart, $titles) && $titles[$tokenPart] !== '' && $titles[$tokenPart] !== null) { - $replaceString = $titles[$tokenPart]; - // We want to stop after the first matching token part / identifier is found - // if id1 has a value, id2 will not be used. - break; - } - } - } - - return $replaceString; - } - - /** - * Checks whether $identifier is a placeholder for a token group. - * - * @param string $identifier - * - * @return bool - */ - protected function isTokenGroup(string $identifier): bool - { - if (strpos($identifier, self::META_STRING) === false) { - return false; - } - - return true; - } - - /** - * Returns the different constituents of $token in an array. - * The normal case here is that the different identifiers within one token - * will be tokenized and returned. - * - * Example: - * - * "<title|text>" ==> array( 'title', 'text' ) - * - * - * @param string $token - * - * @return array - */ - protected function tokenParts(string $token): array - { - return preg_split('/[^\w:]+/', $token, -1, PREG_SPLIT_NO_EMPTY); - } - - /** - * Builds a lookup / translation table for groups in the $namePattern. - * The groups are referenced with a generated meta-token in the original - * name pattern. - * - * Returns intermediate name pattern where groups are replaced with meta- - * tokens. - * - * @param string $nameSchema - * - * @return array - */ - protected function filterNameSchema(string $nameSchema): array - { - $retNamePattern = ''; - $foundGroups = preg_match_all('/[<|\\|](\\(.+\\))[\\||>]/U', $nameSchema, $groupArray); - $groupLookupTable = []; - - if ($foundGroups) { - $i = 0; - foreach ($groupArray[1] as $group) { - // Create meta-token for group - $metaToken = self::META_STRING . $i; - - // Insert the group with its placeholder token - $retNamePattern = str_replace($group, $metaToken, $nameSchema); - - // Remove the pattern "(" ")" from the tokens - $group = str_replace(['(', ')'], '', $group); - - $groupLookupTable[$metaToken] = $group; - ++$i; - } - $nameSchema = $retNamePattern; - } - - return [$nameSchema, $groupLookupTable]; - } - - /** - * Returns all identifiers from all tokens in the name schema. - * - * @param string $schemaString - * - * @return array - */ - protected function getIdentifiers(string $schemaString): array - { - $allTokensPattern = '#<(.*)>#U'; - $identifiersPattern = '#([^<>]+)#'; - - $matches = []; - preg_match_all($allTokensPattern, $schemaString, $matches); - - $retArray = []; - foreach ($matches[1] as $match) { - preg_match_all($identifiersPattern, $match, $tokens); - $retArray = array_merge($retArray, $tokens[1]); - } - - return $retArray; - } - - public function validateNameLength(string $name): string - { - // Make sure length is not longer then $limit unless it's 0 - if ($this->settings['limit'] && mb_strlen($name) > $this->settings['limit']) { - $name = rtrim( - mb_substr($name, 0, $this->settings['limit'] - strlen($this->settings['sequence'])) - ) . $this->settings['sequence']; - } - - return $name; - } } class_alias(NameSchemaService::class, 'eZ\Publish\Core\Repository\Helper\NameSchemaService'); diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php new file mode 100644 index 0000000000..585547d3c0 --- /dev/null +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -0,0 +1,405 @@ +contentTypeHandler = $contentTypeHandler; + $this->contentTypeDomainMapper = $contentTypeDomainMapper; + $this->fieldTypeRegistry = $fieldTypeRegistry; + // Union makes sure default settings are ignored if provided in argument + $this->settings = $settings + [ + 'limit' => 150, + 'sequence' => '...', + ]; + $this->eventDispatcher = $eventDispatcher; + $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; + } + + /** + * Convenience method for resolving URL alias schema. + * + * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType|null $contentType + * + * @return array + */ + public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array + { + $contentType = $contentType ?? $content->getContentType(); + $schemaName = $contentType->urlAliasSchema ?: $contentType->nameSchema; + $schemaIdentifiers = $this->schemaIdentifierExtractor->extract($schemaName); + + /** @var \Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent $event */ + $event = $this->eventDispatcher->dispatch( + new ResolveUrlAliasSchemaEvent( + $schemaIdentifiers, + $content + ) + ); + + $names = []; + $tokens = $event->getTokenValues(); + $extractedTokens = $this->extractTokens($schemaName); + foreach ($tokens as $languageCode => $tokenValues) { + $schema = $schemaName; + foreach ($extractedTokens as $extractedToken) { + $name = $this->resolveToken($extractedToken, $tokenValues, []); + $schema = str_replace($extractedToken, $name, $schema); + } + $names[$languageCode] = $schema; + } + + return $names; + } + + /** + * Convenience method for resolving name schema. + * + * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content + * @param array $fieldMap + * @param array $languageCodes + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType|null $contentType + * + * @return array + */ + public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null) + { + if ($contentType === null) { + $contentType = $this->contentTypeHandler->load($content->contentInfo->contentTypeId); + } + + $languageCodes = $languageCodes ?: $content->versionInfo->languageCodes; + + return $this->resolve( + $contentType->nameSchema, + $contentType, + $this->mergeFieldMap( + $content, + $fieldMap, + $languageCodes + ), + $languageCodes + ); + } + + /** + * Convenience method for resolving name schema. + * + * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content + * @param array $fieldMap + * @param array $languageCodes + * + * @return array + */ + protected function mergeFieldMap(Content $content, array $fieldMap, array $languageCodes) + { + if (empty($fieldMap)) { + return $content->fields; + } + + $mergedFieldMap = []; + + foreach ($content->fields as $fieldIdentifier => $fieldLanguageMap) { + foreach ($languageCodes as $languageCode) { + $mergedFieldMap[$fieldIdentifier][$languageCode] = isset($fieldMap[$fieldIdentifier][$languageCode]) + ? $fieldMap[$fieldIdentifier][$languageCode] + : $fieldLanguageMap[$languageCode]; + } + } + + return $mergedFieldMap; + } + + /** + * Returns the real name for a content name pattern. + * + * @param string $nameSchema + * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType + * @param array $fieldMap + * @param array $languageCodes + * + * @return string[] + */ + public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes) + { + list($filteredNameSchema, $groupLookupTable) = $this->filterNameSchema($nameSchema); + $tokens = $this->extractTokens($filteredNameSchema); + $schemaIdentifiers = $this->getIdentifiers($nameSchema); + + $names = []; + + foreach ($languageCodes as $languageCode) { + // Fetch titles for language code + $titles = $this->getFieldTitles($schemaIdentifiers, $contentType, $fieldMap, $languageCode); + $name = $filteredNameSchema; + + // Replace tokens with real values + foreach ($tokens as $token) { + $string = $this->resolveToken($token, $titles, $groupLookupTable); + $name = str_replace($token, $string, $name); + } + $name = $this->validateNameLength($name); + + $names[$languageCode] = $name; + } + + return $names; + } + + /** + * Fetches the list of available Field identifiers in the token and returns + * an array of their current title value. + * + * @see \Ibexa\Core\Repository\Values\ContentType\FieldType::getName() + * + * @param array $schemaIdentifiers + * @param array $fieldMap + * + * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentType + * + * @return string[] Key is the field identifier, value is the title value + */ + protected function getFieldTitles(array $schemaIdentifiers, ContentType $contentType, array $fieldMap, string $languageCode): array + { + $fieldTitles = []; + + foreach ($schemaIdentifiers as $fieldDefinitionIdentifier) { + if (isset($fieldMap[$fieldDefinitionIdentifier][$languageCode])) { + $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); + + $persistenceFieldType = $this->fieldTypeRegistry->getFieldType( + $fieldDefinition->fieldTypeIdentifier + ); + + $fieldTitles[$fieldDefinitionIdentifier] = $persistenceFieldType->getName( + $fieldMap[$fieldDefinitionIdentifier][$languageCode], + $fieldDefinition, + $languageCode + ); + } + } + + return $fieldTitles; + } + + /** + * Extract all tokens from $namePattern. + * + * Example: + * + * Text <token> more text ==> <token> + * + */ + protected function extractTokens(string $nameSchema): array + { + preg_match_all( + '|<([^>]+)>|U', + $nameSchema, + $tokenArray + ); + + return $tokenArray[0]; + } + + /** + * Looks up the value $token should be replaced with and returns this as + * a string. Meta strings denoting token groups are automatically + * inferred. + */ + protected function resolveToken(string $token, array $titles, array $groupLookupTable): string + { + $replaceString = ''; + $tokenParts = $this->tokenParts($token); + + foreach ($tokenParts as $tokenPart) { + if ($this->isTokenGroup($tokenPart)) { + $replaceString = $groupLookupTable[$tokenPart]; + $groupTokenArray = $this->extractTokens($replaceString); + + foreach ($groupTokenArray as $groupToken) { + $replaceString = str_replace( + $groupToken, + $this->resolveToken( + $groupToken, + $titles, + $groupLookupTable + ), + $replaceString + ); + } + + // We want to stop after the first matching token part / identifier is found + // if id1 has a value, id2 will not be used. + // In this case id1 or id1 is a token group. + break; + } else { + if (array_key_exists($tokenPart, $titles) && $titles[$tokenPart] !== '' && $titles[$tokenPart] !== null) { + $replaceString = $titles[$tokenPart]; + // We want to stop after the first matching token part / identifier is found + // if id1 has a value, id2 will not be used. + break; + } + } + } + + return $replaceString; + } + + /** + * Checks whether $identifier is a placeholder for a token group. + * + * @param string $identifier + * + * @return bool + */ + protected function isTokenGroup(string $identifier): bool + { + if (strpos($identifier, self::META_STRING) === false) { + return false; + } + + return true; + } + + /** + * Returns the different constituents of $token in an array. + * The normal case here is that the different identifiers within one token + * will be tokenized and returned. + * + * Example: + * + * "<title|text>" ==> array( 'title', 'text' ) + * + * + * @param string $token + * + * @return array + */ + protected function tokenParts(string $token): array + { + return preg_split('/[^\w:]+/', $token, -1, PREG_SPLIT_NO_EMPTY); + } + + /** + * Builds a lookup / translation table for groups in the $namePattern. + * The groups are referenced with a generated meta-token in the original + * name pattern. + * + * Returns intermediate name pattern where groups are replaced with meta- + * tokens. + * + * @param string $nameSchema + * + * @return array + */ + protected function filterNameSchema(string $nameSchema): array + { + $retNamePattern = ''; + $foundGroups = preg_match_all('/[<|\\|](\\(.+\\))[\\||>]/U', $nameSchema, $groupArray); + $groupLookupTable = []; + + if ($foundGroups) { + $i = 0; + foreach ($groupArray[1] as $group) { + // Create meta-token for group + $metaToken = self::META_STRING . $i; + + // Insert the group with its placeholder token + $retNamePattern = str_replace($group, $metaToken, $nameSchema); + + // Remove the pattern "(" ")" from the tokens + $group = str_replace(['(', ')'], '', $group); + + $groupLookupTable[$metaToken] = $group; + ++$i; + } + $nameSchema = $retNamePattern; + } + + return [$nameSchema, $groupLookupTable]; + } + + /** + * Returns all identifiers from all tokens in the name schema. + * + * @param string $schemaString + * + * @return array + */ + protected function getIdentifiers(string $schemaString): array + { + $allTokensPattern = '#<(.*)>#U'; + $identifiersPattern = '#([^<>]+)#'; + + $matches = []; + preg_match_all($allTokensPattern, $schemaString, $matches); + + $retArray = []; + foreach ($matches[1] as $match) { + preg_match_all($identifiersPattern, $match, $tokens); + $retArray = array_merge($retArray, $tokens[1]); + } + + return $retArray; + } + + public function validateNameLength(string $name): string + { + // Make sure length is not longer then $limit unless it's 0 + if ($this->settings['limit'] && mb_strlen($name) > $this->settings['limit']) { + $name = rtrim( + mb_substr($name, 0, $this->settings['limit'] - strlen($this->settings['sequence'])) + ) . $this->settings['sequence']; + } + + return $name; + } +} diff --git a/src/lib/Repository/Helper/SchemaIdentifierExtractor.php b/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php similarity index 57% rename from src/lib/Repository/Helper/SchemaIdentifierExtractor.php rename to src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php index 4a650a660e..2f9c0739fb 100644 --- a/src/lib/Repository/Helper/SchemaIdentifierExtractor.php +++ b/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php @@ -6,11 +6,26 @@ */ declare(strict_types=1); -namespace Ibexa\Core\Repository\Helper; +namespace Ibexa\Core\Repository\NameSchema; -class SchemaIdentifierExtractor +use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; + +final class SchemaIdentifierExtractor implements SchemaIdentifierExtractorInterface { - public function extract($schemaString) + /** + * @return array> + * + * @example + * $extractor = new SchemaIdentifierExtractor(); + * $schemaString = '--'; + * $result = $extractor->extract($schemaString); + * // $result will be: + * // [ + * // 'field' => ['foo', 'bar'], + * // 'attribute' => ['bar', 'baz'], + * // ] + */ + public function extract(string $schemaString): array { $allTokens = '/<(.*?)>/'; diff --git a/src/lib/Repository/Repository.php b/src/lib/Repository/Repository.php index 24a5a19c09..c753b66619 100644 --- a/src/lib/Repository/Repository.php +++ b/src/lib/Repository/Repository.php @@ -19,6 +19,8 @@ use Ibexa\Contracts\Core\Repository\LanguageResolver; use Ibexa\Contracts\Core\Repository\LanguageService as LanguageServiceInterface; use Ibexa\Contracts\Core\Repository\LocationService as LocationServiceInterface; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; +use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\ObjectStateService as ObjectStateServiceInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; @@ -40,9 +42,7 @@ use Ibexa\Contracts\Core\Search\Handler as SearchHandler; use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\FieldType\FieldTypeRegistry; -use Ibexa\Core\Repository\Helper\NameSchemaService; use Ibexa\Core\Repository\Helper\RelationProcessor; -use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperInterface; @@ -270,7 +270,7 @@ class Repository implements RepositoryInterface private EventDispatcherInterface $eventDispatcher; - private SchemaIdentifierExtractor $schemaIdentifierExtractor; + private SchemaIdentifierExtractorInterface $schemaIdentifierExtractor; public function __construct( PersistenceHandler $persistenceHandler, @@ -294,7 +294,7 @@ public function __construct( PasswordValidatorInterface $passwordValidator, ConfigResolverInterface $configResolver, EventDispatcherInterface $eventDispatcher, - SchemaIdentifierExtractor $schemaIdentifierExtractor, + NameSchemaServiceInterface $nameSchemaService, array $serviceSettings = [], ?LoggerInterface $logger = null ) { @@ -346,7 +346,7 @@ public function __construct( $this->passwordValidator = $passwordValidator; $this->configResolver = $configResolver; $this->eventDispatcher = $eventDispatcher; - $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; + $this->nameSchemaService = $nameSchemaService; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) @@ -745,23 +745,10 @@ public function getPermissionResolver(): PermissionResolverInterface * @internal * @private * - * @return \Ibexa\Core\Repository\Helper\NameSchemaService + * @return \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ - public function getNameSchemaService(): NameSchemaService + public function getNameSchemaService(): NameSchemaServiceInterface { - if ($this->nameSchemaService !== null) { - return $this->nameSchemaService; - } - - $this->nameSchemaService = new Helper\NameSchemaService( - $this->persistenceHandler->contentTypeHandler(), - $this->contentTypeDomainMapper, - $this->fieldTypeRegistry, - $this->schemaIdentifierExtractor, - $this->eventDispatcher, - $this->serviceSettings['nameSchema'] - ); - return $this->nameSchemaService; } diff --git a/src/lib/Repository/SiteAccessAware/Repository.php b/src/lib/Repository/SiteAccessAware/Repository.php index ec79e7a9a1..587a8f6fd7 100644 --- a/src/lib/Repository/SiteAccessAware/Repository.php +++ b/src/lib/Repository/SiteAccessAware/Repository.php @@ -14,6 +14,7 @@ use Ibexa\Contracts\Core\Repository\FieldTypeService as FieldTypeServiceInterface; use Ibexa\Contracts\Core\Repository\LanguageService as LanguageServiceInterface; use Ibexa\Contracts\Core\Repository\LocationService as LocationServiceInterface; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\ObjectStateService as ObjectStateServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver as PermissionResolverInterface; @@ -27,7 +28,6 @@ use Ibexa\Contracts\Core\Repository\URLWildcardService as URLWildcardServiceInterface; use Ibexa\Contracts\Core\Repository\UserPreferenceService as UserPreferenceServiceInterface; use Ibexa\Contracts\Core\Repository\UserService as UserServiceInterface; -use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -73,7 +73,7 @@ class Repository implements RepositoryInterface private EventDispatcherInterface $eventDispatcher; - private SchemaIdentifierExtractor $schemaIdentifierExtractor; + private NameSchemaServiceInterface $nameSchemaService; /** * Construct repository object from aggregated repository. @@ -92,7 +92,7 @@ public function __construct( LanguageService $languageService, NotificationService $notificationService, EventDispatcherInterface $eventDispatcher, - SchemaIdentifierExtractor $schemaIdentifierExtractor + NameSchemaServiceInterface $nameSchemaService ) { $this->repository = $repository; $this->contentService = $contentService; @@ -107,7 +107,7 @@ public function __construct( $this->languageService = $languageService; $this->notificationService = $notificationService; $this->eventDispatcher = $eventDispatcher; - $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; + $this->nameSchemaService = $nameSchemaService; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) @@ -225,9 +225,9 @@ public function getEventDispatcher(): EventDispatcherInterface return $this->eventDispatcher; } - public function getSchemaIdentifierExtractor(): SchemaIdentifierExtractor + public function getNameSchemaService(): NameSchemaServiceInterface { - return $this->schemaIdentifierExtractor; + return $this->nameSchemaService; } } diff --git a/src/lib/Repository/URLAliasService.php b/src/lib/Repository/URLAliasService.php index e4dd6db102..b340c8243b 100644 --- a/src/lib/Repository/URLAliasService.php +++ b/src/lib/Repository/URLAliasService.php @@ -13,6 +13,7 @@ use Ibexa\Contracts\Core\Persistence\Content\UrlAlias\Handler; use Ibexa\Contracts\Core\Repository\Exceptions\ForbiddenException; use Ibexa\Contracts\Core\Repository\LanguageResolver; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\Repository as RepositoryInterface; use Ibexa\Contracts\Core\Repository\URLAliasService as URLAliasServiceInterface; @@ -33,7 +34,7 @@ class URLAliasService implements URLAliasServiceInterface /** @var \Ibexa\Contracts\Core\Persistence\Content\UrlAlias\Handler */ protected $urlAliasHandler; - /** @var \Ibexa\Core\Repository\Helper\NameSchemaService */ + /** @var \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ protected $nameSchemaService; /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ @@ -45,7 +46,7 @@ class URLAliasService implements URLAliasServiceInterface public function __construct( RepositoryInterface $repository, Handler $urlAliasHandler, - Helper\NameSchemaService $nameSchemaService, + NameSchemaServiceInterface $nameSchemaService, PermissionResolver $permissionResolver, LanguageResolver $languageResolver ) { diff --git a/src/lib/Resources/settings/repository/inner.yml b/src/lib/Resources/settings/repository/inner.yml index 50eb0fd3ce..ee99e35d6d 100644 --- a/src/lib/Resources/settings/repository/inner.yml +++ b/src/lib/Resources/settings/repository/inner.yml @@ -35,6 +35,7 @@ services: - '@Ibexa\Core\Repository\User\PasswordValidatorInterface' - '@ibexa.config.resolver' - '%languages%' + - '@Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface' Ibexa\Core\Repository\ContentService: class: Ibexa\Core\Repository\ContentService diff --git a/src/lib/Resources/settings/repository/siteaccessaware.yml b/src/lib/Resources/settings/repository/siteaccessaware.yml index 6736d8c6e2..91dac912c8 100644 --- a/src/lib/Resources/settings/repository/siteaccessaware.yml +++ b/src/lib/Resources/settings/repository/siteaccessaware.yml @@ -21,7 +21,7 @@ services: - '@Ibexa\Core\Repository\SiteAccessAware\LanguageService' - '@Ibexa\Core\Repository\SiteAccessAware\NotificationService' - '@event_dispatcher' - - '@Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor' + - '@Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface' Ibexa\Core\Repository\SiteAccessAware\ContentService: arguments: diff --git a/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php index f869ebb1d9..1a51d006d3 100644 --- a/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php +++ b/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php @@ -8,7 +8,7 @@ namespace Ibexa\Tests\Core\Repository\Helper; -use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; +use Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor; use PHPUnit\Framework\TestCase; class SchemaIdentifierExtractorTest extends TestCase diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index 3fff2c02a3..923cc30e24 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -20,11 +20,11 @@ use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\FieldTypeService; use Ibexa\Core\Repository\Helper\RelationProcessor; -use Ibexa\Core\Repository\Helper\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Mapper\ContentDomainMapper; use Ibexa\Core\Repository\Mapper\ContentMapper; use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; use Ibexa\Core\Repository\Mapper\RoleDomainMapper; +use Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; use Ibexa\Core\Repository\Repository; From 25602ff14df394a61ae6d117dd53f58f0c92d133 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Wed, 28 Jun 2023 16:58:08 +0200 Subject: [PATCH 08/37] Defined NameSchemaService as DIC Service --- .../Core/ApiLoader/RepositoryFactory.php | 9 +------ src/bundle/Core/Resources/config/papi.yml | 2 -- src/bundle/Core/Resources/config/services.yml | 21 ---------------- .../Container/ApiLoader/RepositoryFactory.php | 9 +++---- src/lib/Event/Repository.php | 10 -------- src/lib/Repository/ContentService.php | 3 ++- .../Repository/Helper/NameSchemaService.php | 23 ------------------ src/lib/Repository/LocationService.php | 3 ++- .../NameSchema/NameSchemaService.php | 24 +++++++++++++++++++ src/lib/Repository/Repository.php | 15 +----------- .../Repository/SiteAccessAware/Repository.php | 22 +---------------- src/lib/Repository/TrashService.php | 4 ++-- src/lib/Resources/settings/events.yml | 2 ++ .../Resources/settings/repository/inner.yml | 5 +++- .../repository/inner/name_schema.yaml | 20 ++++++++++++++++ .../settings/repository/siteaccessaware.yml | 2 -- tests/lib/Repository/ContentServiceTest.php | 4 ++-- .../Repository/Service/Mock/ContentTest.php | 11 +++++---- 18 files changed, 70 insertions(+), 119 deletions(-) create mode 100644 src/lib/Resources/settings/repository/inner/name_schema.yaml diff --git a/src/bundle/Core/ApiLoader/RepositoryFactory.php b/src/bundle/Core/ApiLoader/RepositoryFactory.php index 8e4c64c4f9..2d2ac159fd 100644 --- a/src/bundle/Core/ApiLoader/RepositoryFactory.php +++ b/src/bundle/Core/ApiLoader/RepositoryFactory.php @@ -29,7 +29,6 @@ use Psr\Log\NullLogger; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class RepositoryFactory implements ContainerAwareInterface { @@ -54,23 +53,18 @@ class RepositoryFactory implements ContainerAwareInterface /** @var \Ibexa\Contracts\Core\Repository\LanguageResolver */ private $languageResolver; - private EventDispatcherInterface $eventDispatcher; - public function __construct( ConfigResolverInterface $configResolver, $repositoryClass, array $policyMap, LanguageResolver $languageResolver, - EventDispatcherInterface $eventDispatcher, LoggerInterface $logger = null ) { $this->configResolver = $configResolver; $this->repositoryClass = $repositoryClass; $this->policyMap = $policyMap; $this->languageResolver = $languageResolver; - $this->logger = null !== $logger ? $logger : new NullLogger(); - $this->eventDispatcher = $eventDispatcher; - $this->nameSchemaService = $nameSchemaService; + $this->logger = $logger ?? new NullLogger(); } /** @@ -124,7 +118,6 @@ public function buildRepository( $locationFilteringHandler, $passwordValidator, $configResolver, - $this->eventDispatcher, $nameSchemaService, [ 'role' => [ diff --git a/src/bundle/Core/Resources/config/papi.yml b/src/bundle/Core/Resources/config/papi.yml index 3e0b7fe3de..2669a3f1bd 100644 --- a/src/bundle/Core/Resources/config/papi.yml +++ b/src/bundle/Core/Resources/config/papi.yml @@ -11,13 +11,11 @@ parameters: services: # API Ibexa\Bundle\Core\ApiLoader\RepositoryFactory: - class: Ibexa\Bundle\Core\ApiLoader\RepositoryFactory arguments: - '@ibexa.config.resolver' - Ibexa\Core\Repository\Repository - '%ibexa.api.role.policy_map%' - '@Ibexa\Contracts\Core\Repository\LanguageResolver' - - '@event_dispatcher' - "@?logger" calls: - [setContainer, ["@service_container"]] diff --git a/src/bundle/Core/Resources/config/services.yml b/src/bundle/Core/Resources/config/services.yml index 09f764fa2d..c3c6cf156c 100644 --- a/src/bundle/Core/Resources/config/services.yml +++ b/src/bundle/Core/Resources/config/services.yml @@ -363,24 +363,3 @@ services: $entityManagers: '%doctrine.entity_managers%' Ibexa\Bundle\Core\Translation\Policy\PolicyTranslationDefinitionProvider: ~ - - Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor: - - Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface: - alias: 'Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor' - - Ibexa\Core\Repository\NameSchema\NameSchemaService: - lazy: true - arguments: - - '@ibexa.api.persistence_handler' - - '@Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper' - - '@Ibexa\Core\FieldType\FieldTypeRegistry' - - '@Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface' - - '@event_dispatcher' - - Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface: - alias: 'Ibexa\Core\Repository\NameSchema\NameSchemaService' - - Ibexa\Core\Repository\EventSubscriber\NameSchemaSubscriber: - tags: - - { name: kernel.event_subscriber } diff --git a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php index 57c5bba613..91de80f53a 100644 --- a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php +++ b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php @@ -10,7 +10,7 @@ use Ibexa\Contracts\Core\Persistence\Filter\Location\Handler as LocationFilteringHandler; use Ibexa\Contracts\Core\Persistence\Handler as PersistenceHandler; use Ibexa\Contracts\Core\Repository\LanguageResolver; -use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; use Ibexa\Contracts\Core\Repository\PermissionService; use Ibexa\Contracts\Core\Repository\Repository; @@ -28,7 +28,6 @@ use Ibexa\Core\Search\Common\BackgroundIndexer; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; class RepositoryFactory implements ContainerAwareInterface { @@ -85,8 +84,7 @@ public function buildRepository( LocationFilteringHandler $locationFilteringHandler, PasswordValidatorInterface $passwordValidator, ConfigResolverInterface $configResolver, - EventDispatcherInterface $eventDispatcher, - SchemaIdentifierExtractorInterface $schemaIdentifierExtractor, + NameSchemaServiceInterface $nameSchemaService, array $languages ): Repository { return new $this->repositoryClass( @@ -110,8 +108,7 @@ public function buildRepository( $locationFilteringHandler, $passwordValidator, $configResolver, - $eventDispatcher, - $schemaIdentifierExtractor, + $nameSchemaService, [ 'role' => [ 'policyMap' => $this->policyMap, diff --git a/src/lib/Event/Repository.php b/src/lib/Event/Repository.php index 2af82174e7..f02d58f5fb 100644 --- a/src/lib/Event/Repository.php +++ b/src/lib/Event/Repository.php @@ -243,16 +243,6 @@ public function getUserService(): UserServiceInterface { return $this->userService; } - - public function getEventDispatcher(): EventDispatcherInterface - { - return $this->eventDispatcher; - } - - public function getNameSchemaService(): NameSchemaServiceInterface - { - return $this->nameSchemaService; - } } class_alias(Repository::class, 'eZ\Publish\Core\Event\Repository'); diff --git a/src/lib/Repository/ContentService.php b/src/lib/Repository/ContentService.php index a8ba108e3b..70c08bda2c 100644 --- a/src/lib/Repository/ContentService.php +++ b/src/lib/Repository/ContentService.php @@ -25,6 +25,7 @@ use Ibexa\Contracts\Core\Persistence\Handler; use Ibexa\Contracts\Core\Repository\ContentService as ContentServiceInterface; use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException as APINotFoundException; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionService; use Ibexa\Contracts\Core\Repository\Repository as RepositoryInterface; use Ibexa\Contracts\Core\Repository\Validator\ContentValidator; @@ -110,7 +111,7 @@ public function __construct( Handler $handler, ContentDomainMapper $contentDomainMapper, Helper\RelationProcessor $relationProcessor, - Helper\NameSchemaService $nameSchemaService, + NameSchemaServiceInterface $nameSchemaService, FieldTypeRegistry $fieldTypeRegistry, PermissionService $permissionService, ContentMapper $contentMapper, diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index ee4531090d..71391ed6a6 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -9,29 +9,6 @@ use Ibexa\Core\Repository\NameSchema\NameSchemaService as NativeNameSchemaService; /** - * NameSchemaService is internal service for resolving content name and url alias patterns. - * This code supports content name pattern groups. - * - * Syntax: - * - * <attribute_identifier> - * <attribute_identifier> <2nd-identifier> - * User text <attribute_identifier>|(<2nd-identifier><3rd-identifier>) - * - * - * Example: - * - * <nickname|(<firstname> <lastname>)> - * - * - * Tokens are looked up from left to right. If a match is found for the - * leftmost token, the 2nd token will not be used. Tokens are representations - * of fields. So a match means that that the current field has data. - * - * Tokens are the field definition identifiers which are used in the class edit-interface. - * - * @internal Meant for internal use by Repository. - * * @deprecated inject \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface instead. * @see \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ diff --git a/src/lib/Repository/LocationService.php b/src/lib/Repository/LocationService.php index acc65b184b..89eb4b1964 100644 --- a/src/lib/Repository/LocationService.php +++ b/src/lib/Repository/LocationService.php @@ -18,6 +18,7 @@ use Ibexa\Contracts\Core\Repository\ContentTypeService; use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException as APINotFoundException; use Ibexa\Contracts\Core\Repository\LocationService as LocationServiceInterface; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionCriterionResolver; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\Repository as RepositoryInterface; @@ -100,7 +101,7 @@ public function __construct( RepositoryInterface $repository, Handler $handler, ContentDomainMapper $contentDomainMapper, - Helper\NameSchemaService $nameSchemaService, + NameSchemaServiceInterface $nameSchemaService, PermissionCriterionResolver $permissionCriterionResolver, PermissionResolver $permissionResolver, LocationFilteringHandler $locationFilteringHandler, diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index 585547d3c0..4480257b1a 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -18,6 +18,30 @@ use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +/** + * @internal Meant for internal use by Repository. + * + * NameSchemaService is internal service for resolving content name and url alias patterns. + * This code supports content name pattern groups. + * + * Syntax: + * + * <attribute_identifier> + * <attribute_identifier> <2nd-identifier> + * User text <attribute_identifier>|(<2nd-identifier><3rd-identifier>) + * + * + * Example: + * + * <nickname|(<firstname> <lastname>)> + * + * + * Tokens are looked up from left to right. If a match is found for the + * leftmost token, the 2nd token will not be used. Tokens are representations + * of fields. So a match means that the current field has data. + * + * Tokens are the field definition identifiers which are used in the class edit-interface. + */ class NameSchemaService implements NameSchemaServiceInterface { /** diff --git a/src/lib/Repository/Repository.php b/src/lib/Repository/Repository.php index c753b66619..7fb7f42541 100644 --- a/src/lib/Repository/Repository.php +++ b/src/lib/Repository/Repository.php @@ -20,7 +20,6 @@ use Ibexa\Contracts\Core\Repository\LanguageService as LanguageServiceInterface; use Ibexa\Contracts\Core\Repository\LocationService as LocationServiceInterface; use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; -use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\ObjectStateService as ObjectStateServiceInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; @@ -51,7 +50,6 @@ use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use RuntimeException; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Repository class. @@ -268,10 +266,6 @@ class Repository implements RepositoryInterface private ConfigResolverInterface $configResolver; - private EventDispatcherInterface $eventDispatcher; - - private SchemaIdentifierExtractorInterface $schemaIdentifierExtractor; - public function __construct( PersistenceHandler $persistenceHandler, SearchHandler $searchHandler, @@ -293,7 +287,6 @@ public function __construct( LocationFilteringHandler $locationFilteringHandler, PasswordValidatorInterface $passwordValidator, ConfigResolverInterface $configResolver, - EventDispatcherInterface $eventDispatcher, NameSchemaServiceInterface $nameSchemaService, array $serviceSettings = [], ?LoggerInterface $logger = null @@ -340,12 +333,11 @@ public function __construct( $this->serviceSettings['language']['languages'] = $this->serviceSettings['languages']; } - $this->logger = null !== $logger ? $logger : new NullLogger(); + $this->logger = $logger ?? new NullLogger(); $this->contentMapper = $contentMapper; $this->contentValidator = $contentValidator; $this->passwordValidator = $passwordValidator; $this->configResolver = $configResolver; - $this->eventDispatcher = $eventDispatcher; $this->nameSchemaService = $nameSchemaService; } @@ -737,11 +729,6 @@ public function getPermissionResolver(): PermissionResolverInterface } /** - * Get NameSchemaResolverService. - * - * - * @todo Move out from this & other repo instances when services becomes proper services in DIC terms using factory. - * * @internal * @private * diff --git a/src/lib/Repository/SiteAccessAware/Repository.php b/src/lib/Repository/SiteAccessAware/Repository.php index 587a8f6fd7..750ce2adcc 100644 --- a/src/lib/Repository/SiteAccessAware/Repository.php +++ b/src/lib/Repository/SiteAccessAware/Repository.php @@ -14,7 +14,6 @@ use Ibexa\Contracts\Core\Repository\FieldTypeService as FieldTypeServiceInterface; use Ibexa\Contracts\Core\Repository\LanguageService as LanguageServiceInterface; use Ibexa\Contracts\Core\Repository\LocationService as LocationServiceInterface; -use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\ObjectStateService as ObjectStateServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver as PermissionResolverInterface; @@ -28,7 +27,6 @@ use Ibexa\Contracts\Core\Repository\URLWildcardService as URLWildcardServiceInterface; use Ibexa\Contracts\Core\Repository\UserPreferenceService as UserPreferenceServiceInterface; use Ibexa\Contracts\Core\Repository\UserService as UserServiceInterface; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * Repository class. @@ -71,10 +69,6 @@ class Repository implements RepositoryInterface /** @var \Ibexa\Core\Repository\NotificationService */ protected $notificationService; - private EventDispatcherInterface $eventDispatcher; - - private NameSchemaServiceInterface $nameSchemaService; - /** * Construct repository object from aggregated repository. */ @@ -90,9 +84,7 @@ public function __construct( TrashService $trashService, LocationService $locationService, LanguageService $languageService, - NotificationService $notificationService, - EventDispatcherInterface $eventDispatcher, - NameSchemaServiceInterface $nameSchemaService + NotificationService $notificationService ) { $this->repository = $repository; $this->contentService = $contentService; @@ -106,8 +98,6 @@ public function __construct( $this->locationService = $locationService; $this->languageService = $languageService; $this->notificationService = $notificationService; - $this->eventDispatcher = $eventDispatcher; - $this->nameSchemaService = $nameSchemaService; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) @@ -219,16 +209,6 @@ public function rollback(): void { $this->repository->rollback(); } - - public function getEventDispatcher(): EventDispatcherInterface - { - return $this->eventDispatcher; - } - - public function getNameSchemaService(): NameSchemaServiceInterface - { - return $this->nameSchemaService; - } } class_alias(Repository::class, 'eZ\Publish\Core\Repository\SiteAccessAware\Repository'); diff --git a/src/lib/Repository/TrashService.php b/src/lib/Repository/TrashService.php index 15f5340d6f..81d0b7524f 100644 --- a/src/lib/Repository/TrashService.php +++ b/src/lib/Repository/TrashService.php @@ -14,6 +14,7 @@ use Ibexa\Contracts\Core\Persistence\Content\Location\Trashed; use Ibexa\Contracts\Core\Persistence\Handler; use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException as APIUnauthorizedException; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionCriterionResolver; use Ibexa\Contracts\Core\Repository\PermissionResolver; use Ibexa\Contracts\Core\Repository\Repository as RepositoryInterface; @@ -67,7 +68,6 @@ class TrashService implements TrashServiceInterface * * @param \Ibexa\Contracts\Core\Repository\Repository $repository * @param \Ibexa\Contracts\Core\Persistence\Handler $handler - * @param \Ibexa\Core\Repository\Helper\NameSchemaService $nameSchemaService * @param \Ibexa\Contracts\Core\Repository\PermissionCriterionResolver $permissionCriterionResolver * @param \Ibexa\Contracts\Core\Repository\PermissionResolver $permissionResolver * @param array $settings @@ -75,7 +75,7 @@ class TrashService implements TrashServiceInterface public function __construct( RepositoryInterface $repository, Handler $handler, - Helper\NameSchemaService $nameSchemaService, + NameSchemaServiceInterface $nameSchemaService, PermissionCriterionResolver $permissionCriterionResolver, PermissionResolver $permissionResolver, ProxyDomainMapperInterface $proxyDomainMapper, diff --git a/src/lib/Resources/settings/events.yml b/src/lib/Resources/settings/events.yml index 185f72926e..4027da9755 100644 --- a/src/lib/Resources/settings/events.yml +++ b/src/lib/Resources/settings/events.yml @@ -5,3 +5,5 @@ services: public: false Ibexa\Core\Repository\EventSubscriber\DeleteUserSubscriber: ~ + + Ibexa\Core\Repository\EventSubscriber\NameSchemaSubscriber: ~ diff --git a/src/lib/Resources/settings/repository/inner.yml b/src/lib/Resources/settings/repository/inner.yml index ee99e35d6d..f36d0b9a7e 100644 --- a/src/lib/Resources/settings/repository/inner.yml +++ b/src/lib/Resources/settings/repository/inner.yml @@ -1,3 +1,6 @@ +imports: + - { resource: inner/name_schema.yaml } + parameters: ibexa.kernel.proxy_cache_dir: 'var/cache/repository/proxy' @@ -34,8 +37,8 @@ services: - '@Ibexa\Contracts\Core\Persistence\Filter\Location\Handler' - '@Ibexa\Core\Repository\User\PasswordValidatorInterface' - '@ibexa.config.resolver' - - '%languages%' - '@Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface' + - '%languages%' Ibexa\Core\Repository\ContentService: class: Ibexa\Core\Repository\ContentService diff --git a/src/lib/Resources/settings/repository/inner/name_schema.yaml b/src/lib/Resources/settings/repository/inner/name_schema.yaml new file mode 100644 index 0000000000..45f8fcad8e --- /dev/null +++ b/src/lib/Resources/settings/repository/inner/name_schema.yaml @@ -0,0 +1,20 @@ +parameters: + ibexa.core.repository.name_schema.settings: [] + +services: + _defaults: + autowire: true + autoconfigure: true + public: false + + Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor: ~ + + Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface: + alias: 'Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor' + + Ibexa\Core\Repository\NameSchema\NameSchemaService: + arguments: + $settings: '%ibexa.core.repository.name_schema.settings%' + + Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface: + alias: 'Ibexa\Core\Repository\NameSchema\NameSchemaService' diff --git a/src/lib/Resources/settings/repository/siteaccessaware.yml b/src/lib/Resources/settings/repository/siteaccessaware.yml index 91dac912c8..b8182cf663 100644 --- a/src/lib/Resources/settings/repository/siteaccessaware.yml +++ b/src/lib/Resources/settings/repository/siteaccessaware.yml @@ -20,8 +20,6 @@ services: - '@Ibexa\Core\Repository\SiteAccessAware\LocationService' - '@Ibexa\Core\Repository\SiteAccessAware\LanguageService' - '@Ibexa\Core\Repository\SiteAccessAware\NotificationService' - - '@event_dispatcher' - - '@Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface' Ibexa\Core\Repository\SiteAccessAware\ContentService: arguments: diff --git a/tests/lib/Repository/ContentServiceTest.php b/tests/lib/Repository/ContentServiceTest.php index 93fccf69f2..ce2e14a2aa 100644 --- a/tests/lib/Repository/ContentServiceTest.php +++ b/tests/lib/Repository/ContentServiceTest.php @@ -10,13 +10,13 @@ use Ibexa\Contracts\Core\Persistence\Filter\Content\Handler as ContentFilteringHandler; use Ibexa\Contracts\Core\Persistence\Handler as PersistenceHandler; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionService; use Ibexa\Contracts\Core\Repository\Repository; use Ibexa\Contracts\Core\Repository\Validator\ContentValidator; use Ibexa\Contracts\Core\Repository\Values\Filter\Filter; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\ContentService; -use Ibexa\Core\Repository\Helper\NameSchemaService; use Ibexa\Core\Repository\Helper\RelationProcessor; use Ibexa\Core\Repository\Mapper\ContentDomainMapper; use Ibexa\Core\Repository\Mapper\ContentMapper; @@ -37,7 +37,7 @@ protected function setUp(): void $this->createMock(PersistenceHandler::class), $this->createMock(ContentDomainMapper::class), $this->createMock(RelationProcessor::class), - $this->createMock(NameSchemaService::class), + $this->createMock(NameSchemaServiceInterface::class), $this->createMock(FieldTypeRegistry::class), $this->createMock(PermissionService::class), $this->createMock(ContentMapper::class), diff --git a/tests/lib/Repository/Service/Mock/ContentTest.php b/tests/lib/Repository/Service/Mock/ContentTest.php index c60cbb92e0..7d4623f10e 100644 --- a/tests/lib/Repository/Service/Mock/ContentTest.php +++ b/tests/lib/Repository/Service/Mock/ContentTest.php @@ -27,6 +27,7 @@ use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException as APINotFoundException; use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException; use Ibexa\Contracts\Core\Repository\LocationService as APILocationService; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\Repository; use Ibexa\Contracts\Core\Repository\Values\Content\Content as APIContent; use Ibexa\Contracts\Core\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct; @@ -45,7 +46,6 @@ use Ibexa\Core\FieldType\ValidationError; use Ibexa\Core\FieldType\Value; use Ibexa\Core\Repository\ContentService; -use Ibexa\Core\Repository\Helper\NameSchemaService; use Ibexa\Core\Repository\Helper\RelationProcessor; use Ibexa\Core\Repository\Values\Content\Content; use Ibexa\Core\Repository\Values\Content\ContentCreateStruct; @@ -6215,15 +6215,16 @@ protected function getRelationProcessorMock() return $this->relationProcessorMock; } - protected $nameSchemaServiceMock; + /** @var \PHPUnit\Framework\MockObject\MockObject&\Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ + protected NameSchemaServiceInterface $nameSchemaServiceMock; /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Ibexa\Core\Repository\Helper\NameSchemaService + * @return \PHPUnit\Framework\MockObject\MockObject&\Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ - protected function getNameSchemaServiceMock() + protected function getNameSchemaServiceMock(): NameSchemaServiceInterface { if (!isset($this->nameSchemaServiceMock)) { - $this->nameSchemaServiceMock = $this->createMock(NameSchemaService::class); + $this->nameSchemaServiceMock = $this->createMock(NameSchemaServiceInterface::class); } return $this->nameSchemaServiceMock; From 8de80e4be9cdac740006fecf6fef329187e998c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Thu, 29 Jun 2023 09:54:44 +0200 Subject: [PATCH 09/37] Fixed some tests --- .../Repository/Helper/NameSchemaService.php | 2 +- .../Helper/SchemaIdentifierExtractorTest.php | 46 ------------------- tests/lib/Repository/Service/Mock/Base.php | 30 ++++++++++++ .../Service/Mock/NameSchemaTest.php | 2 + 4 files changed, 33 insertions(+), 47 deletions(-) delete mode 100644 tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index 71391ed6a6..9b24ef6e2b 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -12,7 +12,7 @@ * @deprecated inject \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface instead. * @see \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ -final class NameSchemaService extends NativeNameSchemaService +class NameSchemaService extends NativeNameSchemaService { } diff --git a/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php deleted file mode 100644 index 1a51d006d3..0000000000 --- a/tests/lib/Repository/Helper/SchemaIdentifierExtractorTest.php +++ /dev/null @@ -1,46 +0,0 @@ -||'; - $expectedResult = [ - 'field' => ['username', 'title'], - 'relation' => ['author'], - ]; - $this->assertEquals($expectedResult, $extractor->extract($schemaString)); - - // Test with an empty schema string - $schemaString = ''; - $expectedResult = []; - $this->assertEquals($expectedResult, $extractor->extract($schemaString)); - - // Test with a schema string without tokens - $schemaString = 'This is a plain text.'; - $expectedResult = []; - $this->assertEquals($expectedResult, $extractor->extract($schemaString)); - - // Test with a schema string containing invalid tokens - $schemaString = '||'; - $expectedResult = [ - 'field' => ['username'], - 'relation' => ['author'], - ]; - $this->assertEquals($expectedResult, $extractor->extract($schemaString)); - } -} diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index 923cc30e24..5804eb951c 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -10,6 +10,7 @@ use Ibexa\Contracts\Core\Persistence\Filter\Location\Handler as LocationFilteringHandler; use Ibexa\Contracts\Core\Persistence\Handler; use Ibexa\Contracts\Core\Repository\LanguageResolver; +use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; use Ibexa\Contracts\Core\Repository\PermissionService; use Ibexa\Contracts\Core\Repository\Repository as APIRepository; @@ -171,6 +172,35 @@ protected function getFieldTypeRegistryMock() return $this->fieldTypeRegistryMock; } + protected $schemaIdentifierExtractor; + + /** + * @return \PHPUnit\Framework\MockObject\MockObject|\Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface + */ + protected function getSchemaIdentifierExtractorMock() + { + if (!isset($this->schemaIdentifierExtractor)) { + $this->schemaIdentifierExtractor = $this->createMock(SchemaIdentifierExtractorInterface::class); + } + + return $this->schemaIdentifierExtractor; + } + + + protected $eventDispatcher; + + /** + * @return \PHPUnit\Framework\MockObject\MockObject|\Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface + */ + protected function getEventDispatcher() + { + if (!isset($this->eventDispatcher)) { + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); + } + + return $this->eventDispatcher; + } + /** * @return \Ibexa\Contracts\Core\Repository\Strategy\ContentThumbnail\ThumbnailStrategy|\PHPUnit\Framework\MockObject\MockObject */ diff --git a/tests/lib/Repository/Service/Mock/NameSchemaTest.php b/tests/lib/Repository/Service/Mock/NameSchemaTest.php index 47169233d1..fd484b0066 100644 --- a/tests/lib/Repository/Service/Mock/NameSchemaTest.php +++ b/tests/lib/Repository/Service/Mock/NameSchemaTest.php @@ -364,6 +364,8 @@ protected function getPartlyMockedNameSchemaService(array $methods = null, array $this->getPersistenceMock()->contentTypeHandler(), $this->getContentTypeDomainMapperMock(), $this->getFieldTypeRegistryMock(), + $this->getSchemaIdentifierExtractorMock(), + $this->getEventDispatcher(), $settings, ] ) From 0280c6dc2b55f58f015ad3d69be44b3bb454b2c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Fri, 30 Jun 2023 11:11:45 +0200 Subject: [PATCH 10/37] Fixed content name generation --- .../NameSchema/NameSchemaService.php | 25 +++++++++++++------ tests/lib/Repository/Service/Mock/Base.php | 1 - 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index 4480257b1a..e1ec7508cc 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -398,18 +398,27 @@ protected function filterNameSchema(string $nameSchema): array * * @return array */ - protected function getIdentifiers(string $schemaString): array + protected function getIdentifiers($schemaString) { - $allTokensPattern = '#<(.*)>#U'; - $identifiersPattern = '#([^<>]+)#'; + $allTokens = '#<(.*)>#U'; + $identifiers = '#\\W#'; - $matches = []; - preg_match_all($allTokensPattern, $schemaString, $matches); + $tmpArray = []; + preg_match_all($allTokens, $schemaString, $matches); - $retArray = []; foreach ($matches[1] as $match) { - preg_match_all($identifiersPattern, $match, $tokens); - $retArray = array_merge($retArray, $tokens[1]); + $tmpArray[] = preg_split($identifiers, $match, -1, PREG_SPLIT_NO_EMPTY); + } + + $retArray = []; + foreach ($tmpArray as $matchGroup) { + if (is_array($matchGroup)) { + foreach ($matchGroup as $item) { + $retArray[] = $item; + } + } else { + $retArray[] = $matchGroup; + } } return $retArray; diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index 5804eb951c..114d28e32e 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -186,7 +186,6 @@ protected function getSchemaIdentifierExtractorMock() return $this->schemaIdentifierExtractor; } - protected $eventDispatcher; /** From 9723018edf2e4ff32e98c2babdd493fc5a0b411c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Fri, 30 Jun 2023 12:27:07 +0200 Subject: [PATCH 11/37] Test fix --- .../NameSchema/NameSchemaService.php | 8 +-- tests/lib/Repository/Service/Mock/Base.php | 5 +- .../Service/Mock/NameSchemaTest.php | 71 ++++++++++--------- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index e1ec7508cc..fa59a4dffd 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -392,13 +392,9 @@ protected function filterNameSchema(string $nameSchema): array } /** - * Returns all identifiers from all tokens in the name schema. - * - * @param string $schemaString - * - * @return array + * @return array */ - protected function getIdentifiers($schemaString) + protected function getIdentifiers(string $schemaString): array { $allTokens = '#<(.*)>#U'; $identifiers = '#\\W#'; diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index 114d28e32e..c1f417a7be 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -10,6 +10,7 @@ use Ibexa\Contracts\Core\Persistence\Filter\Location\Handler as LocationFilteringHandler; use Ibexa\Contracts\Core\Persistence\Handler; use Ibexa\Contracts\Core\Repository\LanguageResolver; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; use Ibexa\Contracts\Core\Repository\PermissionService; @@ -25,7 +26,6 @@ use Ibexa\Core\Repository\Mapper\ContentMapper; use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; use Ibexa\Core\Repository\Mapper\RoleDomainMapper; -use Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Permission\LimitationService; use Ibexa\Core\Repository\ProxyFactory\ProxyDomainMapperFactoryInterface; use Ibexa\Core\Repository\Repository; @@ -129,8 +129,7 @@ protected function getRepository(array $serviceSettings = []) $this->getLocationFilteringHandlerMock(), $this->createMock(PasswordValidatorInterface::class), $this->createMock(ConfigResolverInterface::class), - $this->createMock(EventDispatcherInterface::class), - $this->createMock(SchemaIdentifierExtractor::class), + $this->createMock(NameSchemaServiceInterface::class), $serviceSettings, ); diff --git a/tests/lib/Repository/Service/Mock/NameSchemaTest.php b/tests/lib/Repository/Service/Mock/NameSchemaTest.php index fd484b0066..750b3ff0a1 100644 --- a/tests/lib/Repository/Service/Mock/NameSchemaTest.php +++ b/tests/lib/Repository/Service/Mock/NameSchemaTest.php @@ -6,6 +6,7 @@ */ namespace Ibexa\Tests\Core\Repository\Service\Mock; +use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; use Ibexa\Contracts\Core\Repository\Values\Content\Field; use Ibexa\Core\FieldType\TextLine\Value as TextLineValue; use Ibexa\Core\Repository\Helper\NameSchemaService; @@ -22,52 +23,46 @@ class NameSchemaTest extends BaseServiceMockTest { public function testResolveUrlAliasSchema() { - $serviceMock = $this->getPartlyMockedNameSchemaService(['resolve']); - $content = $this->buildTestContentObject(); $contentType = $this->buildTestContentType(); - $serviceMock->expects( - $this->once() - )->method( - 'resolve' - )->with( - '', - $this->equalTo($contentType), - $this->equalTo($content->fields), - $this->equalTo($content->versionInfo->languageCodes) - )->will( - $this->returnValue(42) - ); + $serviceMock = $this->getMockBuilder(NameSchemaService::class) + ->setConstructorArgs( + [ + $this->getPersistenceMock()->contentTypeHandler(), + $this->getContentTypeDomainMapperMock(), + $this->getFieldTypeRegistryMock(), + $this->getSchemaIdentifierExtractorMock(), + $this->getEventDispatcherMock(['field' => ''], $content, ['' => 42]), + ] + ) + ->getMock(); $result = $serviceMock->resolveUrlAliasSchema($content, $contentType); - self::assertEquals(42, $result); + self::assertEquals([], $result); } public function testResolveUrlAliasSchemaFallbackToNameSchema() { - $serviceMock = $this->getPartlyMockedNameSchemaService(['resolve']); - $content = $this->buildTestContentObject(); $contentType = $this->buildTestContentType('', ''); - $serviceMock->expects( - $this->once() - )->method( - 'resolve' - )->with( - '', - $this->equalTo($contentType), - $this->equalTo($content->fields), - $this->equalTo($content->versionInfo->languageCodes) - )->will( - $this->returnValue(42) - ); + $serviceMock = $this->getMockBuilder(NameSchemaService::class) + ->setConstructorArgs( + [ + $this->getPersistenceMock()->contentTypeHandler(), + $this->getContentTypeDomainMapperMock(), + $this->getFieldTypeRegistryMock(), + $this->getSchemaIdentifierExtractorMock(), + $this->getEventDispatcherMock(['field' => ''], $content, ['' => null]), + ] + ) + ->getMock(); $result = $serviceMock->resolveUrlAliasSchema($content, $contentType); - self::assertEquals(42, $result); + self::assertEquals([], $result); } public function testResolveNameSchema() @@ -176,9 +171,9 @@ public function testResolve( } /** - * Data provider for the @see testResolve method. + * Data provider for the @return array. * - * @return array + * @see testResolve method. */ public function resolveDataProvider() { @@ -371,6 +366,18 @@ protected function getPartlyMockedNameSchemaService(array $methods = null, array ) ->getMock(); } + + protected function getEventDispatcherMock(array $schemaIdentifiers, Content $content, array $tokenValues) + { + $event = new ResolveUrlAliasSchemaEvent($schemaIdentifiers, $content); + $event->setTokenValues($tokenValues); + + $eventDispatcherMock = parent::getEventDispatcher(); + $eventDispatcherMock->method('dispatch') + ->willReturn($event); + + return $eventDispatcherMock; + } } class_alias(NameSchemaTest::class, 'eZ\Publish\Core\Repository\Tests\Service\Mock\NameSchemaTest'); From af1ccf8a9e4399e5587efe1f08a2b2f48a29e518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Fri, 30 Jun 2023 13:04:08 +0200 Subject: [PATCH 12/37] Fix tests --- .../NameSchema/NameSchemaServiceInterface.php | 4 +++- .../NameSchema/NameSchemaService.php | 4 ++-- .../Service/Mock/NameSchemaTest.php | 20 ++++++++++--------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php index 168f95ce9e..b47ebef256 100644 --- a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php +++ b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php @@ -20,5 +20,7 @@ interface NameSchemaServiceInterface { public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array; - public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null); + public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null): array; + + public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes): array; } diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index fa59a4dffd..e89ddacf93 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -134,7 +134,7 @@ public function resolveUrlAliasSchema(Content $content, ContentType $contentType * * @return array */ - public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null) + public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null): array { if ($contentType === null) { $contentType = $this->contentTypeHandler->load($content->contentInfo->contentTypeId); @@ -192,7 +192,7 @@ protected function mergeFieldMap(Content $content, array $fieldMap, array $langu * * @return string[] */ - public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes) + public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes): array { list($filteredNameSchema, $groupLookupTable) = $this->filterNameSchema($nameSchema); $tokens = $this->extractTokens($filteredNameSchema); diff --git a/tests/lib/Repository/Service/Mock/NameSchemaTest.php b/tests/lib/Repository/Service/Mock/NameSchemaTest.php index 750b3ff0a1..17bae53715 100644 --- a/tests/lib/Repository/Service/Mock/NameSchemaTest.php +++ b/tests/lib/Repository/Service/Mock/NameSchemaTest.php @@ -9,7 +9,7 @@ use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; use Ibexa\Contracts\Core\Repository\Values\Content\Field; use Ibexa\Core\FieldType\TextLine\Value as TextLineValue; -use Ibexa\Core\Repository\Helper\NameSchemaService; +use Ibexa\Core\Repository\NameSchema\NameSchemaService; use Ibexa\Core\Repository\Values\Content\Content; use Ibexa\Core\Repository\Values\Content\VersionInfo; use Ibexa\Core\Repository\Values\ContentType\ContentType; @@ -27,13 +27,14 @@ public function testResolveUrlAliasSchema() $contentType = $this->buildTestContentType(); $serviceMock = $this->getMockBuilder(NameSchemaService::class) + ->setMethods(['resolve']) ->setConstructorArgs( [ $this->getPersistenceMock()->contentTypeHandler(), $this->getContentTypeDomainMapperMock(), $this->getFieldTypeRegistryMock(), $this->getSchemaIdentifierExtractorMock(), - $this->getEventDispatcherMock(['field' => ''], $content, ['' => 42]), + $this->getEventDispatcherMock(['field' => ''], $content, []), ] ) ->getMock(); @@ -49,13 +50,14 @@ public function testResolveUrlAliasSchemaFallbackToNameSchema() $contentType = $this->buildTestContentType('', ''); $serviceMock = $this->getMockBuilder(NameSchemaService::class) + ->setMethods(['resolve']) ->setConstructorArgs( [ $this->getPersistenceMock()->contentTypeHandler(), $this->getContentTypeDomainMapperMock(), $this->getFieldTypeRegistryMock(), $this->getSchemaIdentifierExtractorMock(), - $this->getEventDispatcherMock(['field' => ''], $content, ['' => null]), + $this->getEventDispatcherMock(['field' => ''], $content, []), ] ) ->getMock(); @@ -82,12 +84,12 @@ public function testResolveNameSchema() $this->equalTo($content->fields), $this->equalTo($content->versionInfo->languageCodes) )->will( - $this->returnValue(42) + $this->returnValue([42]) ); $result = $serviceMock->resolveNameSchema($content, [], [], $contentType); - self::assertEquals(42, $result); + self::assertEquals([42], $result); } public function testResolveNameSchemaWithFields() @@ -120,12 +122,12 @@ public function testResolveNameSchemaWithFields() $this->equalTo($mergedFields), $this->equalTo($languages) )->will( - $this->returnValue(42) + $this->returnValue([42]) ); $result = $serviceMock->resolveNameSchema($content, $fields, $languages, $contentType); - self::assertEquals(42, $result); + self::assertEquals([42], $result); } /** @@ -171,9 +173,9 @@ public function testResolve( } /** - * Data provider for the @return array. + * Data provider for the @see testResolve method. * - * @see testResolve method. + * @return array */ public function resolveDataProvider() { From 9877d1211478968fab470c28644892b2dec62094 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Fri, 30 Jun 2023 16:39:41 +0200 Subject: [PATCH 13/37] Fixed minor issues in NameSchemaSubscriber --- .../EventSubscriber/NameSchemaSubscriber.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index 3ba7f37a42..2a4c94d8f4 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -30,14 +30,16 @@ public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void $content = $event->getContent(); $identifiers = $event->getSchemaIdentifiers()['field']; - $languageCodes = $event->getContent()->versionInfo->languageCodes; + $languages = $event->getContent()->getVersionInfo()->getLanguages(); $tokenValues = $event->getTokenValues(); - foreach ($languageCodes as $languageCode) { + foreach ($languages as $language) { + $languageCode = $language->getLanguageCode(); foreach ($identifiers as $identifier) { - $tokenValues[$languageCode][$identifier] = (string) $content->getFieldValue( + $fieldValue = $content->getFieldValue( $identifier, $languageCode - ) ?? $identifier; + ); + $tokenValues[$languageCode][$identifier] = null !== $fieldValue ? (string)$fieldValue : $identifier; } } From 097cc11e5900aff093a82eee73d8d6ebacde6fe5 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Fri, 30 Jun 2023 16:40:39 +0200 Subject: [PATCH 14/37] Restored original Helper\NameSchemaService::resolveUrlAliasSchema for BC --- src/lib/Repository/Helper/NameSchemaService.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib/Repository/Helper/NameSchemaService.php b/src/lib/Repository/Helper/NameSchemaService.php index 9b24ef6e2b..47682ef83c 100644 --- a/src/lib/Repository/Helper/NameSchemaService.php +++ b/src/lib/Repository/Helper/NameSchemaService.php @@ -6,6 +6,8 @@ */ namespace Ibexa\Core\Repository\Helper; +use Ibexa\Contracts\Core\Repository\Values\Content\Content; +use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; use Ibexa\Core\Repository\NameSchema\NameSchemaService as NativeNameSchemaService; /** @@ -14,6 +16,17 @@ */ class NameSchemaService extends NativeNameSchemaService { + public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array + { + $contentType = $contentType ?? $content->getContentType(); + + return $this->resolve( + empty($contentType->urlAliasSchema) ? $contentType->nameSchema : $contentType->urlAliasSchema, + $contentType, + $content->fields, + $content->versionInfo->languageCodes + ); + } } class_alias(NameSchemaService::class, 'eZ\Publish\Core\Repository\Helper\NameSchemaService'); From 04db1ed20a92756609bc0565034c1cf029bb6889 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Fri, 30 Jun 2023 16:41:05 +0200 Subject: [PATCH 15/37] [CS] Fixed minor issue in NameSchemaService::resolve method --- src/lib/Repository/NameSchema/NameSchemaService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index e89ddacf93..b3e5b26ce0 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -194,7 +194,7 @@ protected function mergeFieldMap(Content $content, array $fieldMap, array $langu */ public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes): array { - list($filteredNameSchema, $groupLookupTable) = $this->filterNameSchema($nameSchema); + [$filteredNameSchema, $groupLookupTable] = $this->filterNameSchema($nameSchema); $tokens = $this->extractTokens($filteredNameSchema); $schemaIdentifiers = $this->getIdentifiers($nameSchema); From f8e9b241f8cb8b777c607933a605e5dabfb4345a Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Wed, 5 Jul 2023 01:00:22 +0200 Subject: [PATCH 16/37] Refactored NameSchemaService to be more lightweight --- .../NameSchema/NameSchemaService.php | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index b3e5b26ce0..6d3bb5c548 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -9,13 +9,11 @@ namespace Ibexa\Core\Repository\NameSchema; use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; -use Ibexa\Contracts\Core\Persistence\Content\Type\Handler as ContentTypeHandler; use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; use Ibexa\Contracts\Core\Repository\Values\Content\Content; use Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType; use Ibexa\Core\FieldType\FieldTypeRegistry; -use Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** @@ -51,14 +49,7 @@ class NameSchemaService implements NameSchemaServiceInterface */ public const META_STRING = 'EZMETAGROUP_'; - /** @var \Ibexa\Contracts\Core\Persistence\Content\Type\Handler */ - protected $contentTypeHandler; - - /** @var \Ibexa\Core\Repository\Mapper\ContentTypeDomainMapper */ - protected $contentTypeDomainMapper; - - /** @var \Ibexa\Core\FieldType\FieldTypeRegistry */ - protected $fieldTypeRegistry; + protected FieldTypeRegistry $fieldTypeRegistry; /** @var array */ protected $settings; @@ -68,15 +59,11 @@ class NameSchemaService implements NameSchemaServiceInterface private SchemaIdentifierExtractorInterface $schemaIdentifierExtractor; public function __construct( - ContentTypeHandler $contentTypeHandler, - ContentTypeDomainMapper $contentTypeDomainMapper, FieldTypeRegistry $fieldTypeRegistry, SchemaIdentifierExtractorInterface $schemaIdentifierExtractor, EventDispatcherInterface $eventDispatcher, array $settings = [] ) { - $this->contentTypeHandler = $contentTypeHandler; - $this->contentTypeDomainMapper = $contentTypeDomainMapper; $this->fieldTypeRegistry = $fieldTypeRegistry; // Union makes sure default settings are ignored if provided in argument $this->settings = $settings + [ @@ -88,16 +75,11 @@ public function __construct( } /** - * Convenience method for resolving URL alias schema. - * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType|null $contentType - * - * @return array + * @return array key value map of names for a language code */ public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array { - $contentType = $contentType ?? $content->getContentType(); + $contentType ??= $content->getContentType(); $schemaName = $contentType->urlAliasSchema ?: $contentType->nameSchema; $schemaIdentifiers = $this->schemaIdentifierExtractor->extract($schemaName); @@ -136,9 +118,7 @@ public function resolveUrlAliasSchema(Content $content, ContentType $contentType */ public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null): array { - if ($contentType === null) { - $contentType = $this->contentTypeHandler->load($content->contentInfo->contentTypeId); - } + $contentType ??= $content->getContentType(); $languageCodes = $languageCodes ?: $content->versionInfo->languageCodes; From 08591ee5f6b649db6284ac27a7aead28a061fa8c Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Mon, 3 Jul 2023 16:25:43 +0200 Subject: [PATCH 17/37] [Tests] Defined URLAliasService and required ibexa.kernel.root_dir param --- src/contracts/Test/IbexaKernelTestTrait.php | 6 ++++++ src/contracts/Test/IbexaTestKernel.php | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/contracts/Test/IbexaKernelTestTrait.php b/src/contracts/Test/IbexaKernelTestTrait.php index 9a2eafa096..6b75669022 100644 --- a/src/contracts/Test/IbexaKernelTestTrait.php +++ b/src/contracts/Test/IbexaKernelTestTrait.php @@ -19,6 +19,7 @@ use Ibexa\Contracts\Core\Repository\RoleService; use Ibexa\Contracts\Core\Repository\SearchService; use Ibexa\Contracts\Core\Repository\SectionService; +use Ibexa\Contracts\Core\Repository\URLAliasService; use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Contracts\Core\Test\Persistence\Fixture\FixtureImporter; use Ibexa\Core\Repository\Values\User\UserReference; @@ -163,6 +164,11 @@ protected static function getSectionService(): SectionService return self::getServiceByClassName(SectionService::class); } + protected static function getUrlAliasService(): URLAliasService + { + return self::getServiceByClassName(URLAliasService::class); + } + protected static function setAnonymousUser(): void { $anonymousUserId = 10; diff --git a/src/contracts/Test/IbexaTestKernel.php b/src/contracts/Test/IbexaTestKernel.php index b76f4348c1..0e547c3ff1 100644 --- a/src/contracts/Test/IbexaTestKernel.php +++ b/src/contracts/Test/IbexaTestKernel.php @@ -89,6 +89,7 @@ class IbexaTestKernel extends Kernel implements IbexaTestKernelInterface Repository\SectionService::class, Repository\UserService::class, Repository\TokenService::class, + Repository\URLAliasService::class, ]; /** @@ -150,6 +151,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(static function (ContainerBuilder $container): void { $container->setParameter('ibexa.core.test.resource_dir', self::getResourcesPath()); + $container->setParameter('ibexa.kernel.root_dir', dirname(__DIR__, 3)); }); $this->loadConfiguration($loader); From 2b7c600d6220f54e97e5116dcdd5e88699ff167f Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Mon, 3 Jul 2023 16:26:35 +0200 Subject: [PATCH 18/37] [Tests] Created abstract RepositoryTestCase for integration tests --- tests/integration/Core/RepositoryTestCase.php | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/integration/Core/RepositoryTestCase.php diff --git a/tests/integration/Core/RepositoryTestCase.php b/tests/integration/Core/RepositoryTestCase.php new file mode 100644 index 0000000000..6a4955b112 --- /dev/null +++ b/tests/integration/Core/RepositoryTestCase.php @@ -0,0 +1,73 @@ + $names + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + public function createFolder(array $names, int $parentLocationId = self::CONTENT_TREE_ROOT_ID): Content + { + $contentService = self::getContentService(); + $draft = $this->createFolderDraft($names, $parentLocationId); + + return $contentService->publishVersion($draft->getVersionInfo()); + } + + /** + * @param array $names + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\Exception + */ + public function createFolderDraft(array $names, int $parentLocationId = self::CONTENT_TREE_ROOT_ID): Content + { + if (empty($names)) { + throw new InvalidArgumentException(__METHOD__ . ' requires $names to be not empty'); + } + + $contentService = self::getContentService(); + $contentTypeService = self::getContentTypeService(); + $locationService = self::getLocationService(); + + $folderType = $contentTypeService->loadContentTypeByIdentifier(self::CONTENT_TYPE_FOLDER_IDENTIFIER); + $mainLanguageCode = array_keys($names)[0]; + $contentCreateStruct = $contentService->newContentCreateStruct($folderType, $mainLanguageCode); + foreach ($names as $languageCode => $name) { + $contentCreateStruct->setField('name', $name, $languageCode); + } + + return $contentService->createContent( + $contentCreateStruct, + [ + $locationService->newLocationCreateStruct($parentLocationId), + ] + ); + } +} From f8d46beb0416a37ae1805a18a78ff0571c2e3614 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Mon, 3 Jul 2023 17:22:25 +0200 Subject: [PATCH 19/37] [Tests] Unified SF EventDispatcher definition for Legacy integration --- tests/integration/Core/Resources/settings/common.yml | 11 ++++++++++- .../Core/Resources/settings/integration_legacy.yml | 9 --------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/integration/Core/Resources/settings/common.yml b/tests/integration/Core/Resources/settings/common.yml index 3858800506..d2aad48c13 100644 --- a/tests/integration/Core/Resources/settings/common.yml +++ b/tests/integration/Core/Resources/settings/common.yml @@ -12,7 +12,16 @@ services: logger: class: Psr\Log\NullLogger - Symfony\Component\EventDispatcher\EventDispatcher: ~ + Symfony\Component\EventDispatcher\EventDispatcher: + calls: + - [ 'addSubscriber', [ '@Ibexa\Core\Search\Common\EventSubscriber\ContentEventSubscriber' ] ] + - [ 'addSubscriber', [ '@Ibexa\Core\Search\Common\EventSubscriber\LocationEventSubscriber' ] ] + - [ 'addSubscriber', [ '@Ibexa\Core\Search\Common\EventSubscriber\ObjectStateEventSubscriber' ] ] + - [ 'addSubscriber', [ '@Ibexa\Core\Search\Common\EventSubscriber\SectionEventSubscriber' ] ] + - [ 'addSubscriber', [ '@Ibexa\Core\Search\Common\EventSubscriber\TrashEventSubscriber' ] ] + - [ 'addSubscriber', [ '@Ibexa\Core\Search\Common\EventSubscriber\UserEventSubscriber' ] ] + - [ 'addSubscriber', [ '@Ibexa\Core\Repository\EventSubscriber\NameSchemaSubscriber' ] ] + Symfony\Contracts\EventDispatcher\EventDispatcherInterface: '@Symfony\Component\EventDispatcher\EventDispatcher' # By default use in-memory cache for tests to avoid disk IO but still make sure we tests cache clearing works diff --git a/tests/integration/Core/Resources/settings/integration_legacy.yml b/tests/integration/Core/Resources/settings/integration_legacy.yml index f1cdd2294f..d7caf0c52d 100644 --- a/tests/integration/Core/Resources/settings/integration_legacy.yml +++ b/tests/integration/Core/Resources/settings/integration_legacy.yml @@ -27,15 +27,6 @@ services: Ibexa\DoctrineSchema\Database\DbPlatform\DbPlatformInterface: tags: [ ibexa.doctrine.db.platform ] - Symfony\Component\EventDispatcher\EventDispatcher: - calls: - - ['addSubscriber', ['@Ibexa\Core\Search\Common\EventSubscriber\ContentEventSubscriber']] - - ['addSubscriber', ['@Ibexa\Core\Search\Common\EventSubscriber\LocationEventSubscriber']] - - ['addSubscriber', ['@Ibexa\Core\Search\Common\EventSubscriber\ObjectStateEventSubscriber']] - - ['addSubscriber', ['@Ibexa\Core\Search\Common\EventSubscriber\SectionEventSubscriber']] - - ['addSubscriber', ['@Ibexa\Core\Search\Common\EventSubscriber\TrashEventSubscriber']] - - ['addSubscriber', ['@Ibexa\Core\Search\Common\EventSubscriber\UserEventSubscriber']] - Doctrine\Common\EventManager: ~ Ibexa\DoctrineSchema\Database\DbPlatform\SqliteDbPlatform: From cbb6453327101e556449f9427eb90d7020082d94 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Mon, 3 Jul 2023 17:23:37 +0200 Subject: [PATCH 20/37] [Tests] Added coverage for URLAliasService::lookup using IbexaKernelTestCase --- .../URLAliasService/UrlAliasLookupTest.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/integration/Core/Repository/URLAliasService/UrlAliasLookupTest.php diff --git a/tests/integration/Core/Repository/URLAliasService/UrlAliasLookupTest.php b/tests/integration/Core/Repository/URLAliasService/UrlAliasLookupTest.php new file mode 100644 index 0000000000..d61140be32 --- /dev/null +++ b/tests/integration/Core/Repository/URLAliasService/UrlAliasLookupTest.php @@ -0,0 +1,35 @@ +createFolder(['eng-GB' => 'Foo']); + $folderMainLocation = $folder->getVersionInfo()->getContentInfo()->getMainLocation(); + $urlAlias = $urlAliasService->lookup('/Foo'); + self::assertSame( + $folderMainLocation->id, + $urlAlias->destination + ); + $systemUrlAliasList = $urlAliasService->listLocationAliases($folderMainLocation, false); + self::assertCount(1, $systemUrlAliasList); + self::assertEquals($urlAlias, $systemUrlAliasList[0]); + } +} From ff73ed422f4008dbb0294d213145a1c3b5260445 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Tue, 4 Jul 2023 01:30:14 +0200 Subject: [PATCH 21/37] [Tests] Added coverage for SchemaIdentifierExtractor --- .../SchemaIdentifierExtractorTest.php | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php diff --git a/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php new file mode 100644 index 0000000000..daf16d61bb --- /dev/null +++ b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php @@ -0,0 +1,79 @@ +>}> + */ + public function getDataForTestExtract(): iterable + { + $schemaString = ''; + yield $schemaString => [ + $schemaString, + [ + 'field' => ['short_name', 'name'], + ], + ]; + + $schemaString = ''; + yield $schemaString => [ + $schemaString, + [ + 'custom_strategy' => ['foo'], + 'field' => ['bar'], + ], + ]; + + $schemaString = ''; + yield $schemaString => [ + $schemaString, + [ + 'custom_strategy' => ['foo'], + 'field' => ['bar'], + ], + ]; + + $schemaString = '-'; + yield $schemaString => [ + $schemaString, + [ + 'custom_strategy' => ['foo'], + 'field' => ['bar'], + ], + ]; + } + + protected function setUp(): void + { + $this->extractor = new SchemaIdentifierExtractor(); + } + + /** + * @dataProvider getDataForTestExtract + * + * @param array> $expectedStrategyIdentifierMap + */ + public function testExtract(string $schemaString, array $expectedStrategyIdentifierMap): void + { + $this->markTestIncomplete('Requires fixing SchemaIdentifierExtractor behavior'); + + self::assertSame($expectedStrategyIdentifierMap, $this->extractor->extract($schemaString)); + } +} From 2524a58bf187c05fb3004cbe2722010e059b5ac9 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Tue, 4 Jul 2023 13:01:15 +0200 Subject: [PATCH 22/37] [Tests] Refactored tests to avoid comparing proxies --- .../Core/Repository/LocationServiceTest.php | 58 ++++++++++--------- .../Core/Repository/UserServiceTest.php | 44 +++++++------- 2 files changed, 55 insertions(+), 47 deletions(-) diff --git a/tests/integration/Core/Repository/LocationServiceTest.php b/tests/integration/Core/Repository/LocationServiceTest.php index f7619d4faf..b27cbf169e 100644 --- a/tests/integration/Core/Repository/LocationServiceTest.php +++ b/tests/integration/Core/Repository/LocationServiceTest.php @@ -1728,14 +1728,14 @@ public function testSwapLocationForMainAndSecondaryLocation(): array $folder2 = $this->createFolder(['eng-GB' => 'Folder2'], 2); $folder3 = $this->createFolder(['eng-GB' => 'Folder3'], 2); - $primaryLocation = $locationService->loadLocation($folder1->contentInfo->mainLocationId); - $parentLocation = $locationService->loadLocation($folder2->contentInfo->mainLocationId); + $primaryLocation = $folder1->getVersionInfo()->getContentInfo()->getMainLocation(); + $parentLocation = $folder2->getVersionInfo()->getContentInfo()->getMainLocation(); $secondaryLocation = $locationService->createLocation( - $folder1->contentInfo, + $folder1->getVersionInfo()->getContentInfo(), $locationService->newLocationCreateStruct($parentLocation->id) ); - $targetLocation = $locationService->loadLocation($folder3->contentInfo->mainLocationId); + $targetLocation = $folder3->getVersionInfo()->getContentInfo()->getMainLocation(); // perform sanity checks $this->assertContentHasExpectedLocations([$primaryLocation, $secondaryLocation], $folder1); @@ -1748,26 +1748,26 @@ public function testSwapLocationForMainAndSecondaryLocation(): array $secondaryLocation = $locationService->loadLocation($secondaryLocation->id); $targetLocation = $locationService->loadLocation($targetLocation->id); - self::assertEquals($folder1->id, $primaryLocation->contentInfo->id); - self::assertEquals($folder1->id, $targetLocation->contentInfo->id); - self::assertEquals($folder3->id, $secondaryLocation->contentInfo->id); + self::assertEquals($folder1->id, $primaryLocation->getContentInfo()->getId()); + self::assertEquals($folder1->id, $targetLocation->getContentInfo()->getId()); + self::assertEquals($folder3->id, $secondaryLocation->getContentInfo()->getId()); $this->assertContentHasExpectedLocations([$primaryLocation, $targetLocation], $folder1); self::assertEquals( - $folder1, - $contentService->loadContent($folder1->id, Language::ALL) + $primaryLocation->id, + $contentService->loadContent($folder1->id)->getVersionInfo()->getContentInfo()->getMainLocationId() ); self::assertEquals( - $folder2, - $contentService->loadContent($folder2->id, Language::ALL) + $parentLocation->id, + $contentService->loadContent($folder2->id)->getVersionInfo()->getContentInfo()->getMainLocationId() ); // only in case of Folder 3, main location id changed due to swap self::assertEquals( $secondaryLocation->id, - $contentService->loadContent($folder3->id)->contentInfo->mainLocationId + $contentService->loadContent($folder3->id)->getVersionInfo()->getContentInfo()->getMainLocation()->id ); return [$folder1, $folder2, $folder3]; @@ -1781,7 +1781,7 @@ public function testSwapLocationForMainAndSecondaryLocation(): array * * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException */ - private function assertContentHasExpectedLocations(array $expectedLocations, Content $content) + private function assertContentHasExpectedLocations(array $expectedLocations, Content $content): void { $repository = $this->getRepository(false); $locationService = $repository->getLocationService(); @@ -1871,7 +1871,7 @@ static function (SearchHit $searchHit) { * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException */ - public function testSwapLocationForSecondaryLocations() + public function testSwapLocationForSecondaryLocations(): void { $repository = $this->getRepository(); $locationService = $repository->getLocationService(); @@ -1882,14 +1882,14 @@ public function testSwapLocationForSecondaryLocations() $parentFolder1 = $this->createFolder(['eng-GB' => 'Parent1'], 2); $parentFolder2 = $this->createFolder(['eng-GB' => 'Parent2'], 2); - $parentLocation1 = $locationService->loadLocation($parentFolder1->contentInfo->mainLocationId); - $parentLocation2 = $locationService->loadLocation($parentFolder2->contentInfo->mainLocationId); + $parentLocation1 = $parentFolder1->getVersionInfo()->getContentInfo()->getMainLocation(); + $parentLocation2 = $parentFolder2->getVersionInfo()->getContentInfo()->getMainLocation(); $secondaryLocation1 = $locationService->createLocation( - $folder1->contentInfo, + $folder1->getVersionInfo()->getContentInfo(), $locationService->newLocationCreateStruct($parentLocation1->id) ); $secondaryLocation2 = $locationService->createLocation( - $folder2->contentInfo, + $folder2->getVersionInfo()->getContentInfo(), $locationService->newLocationCreateStruct($parentLocation2->id) ); @@ -1900,17 +1900,23 @@ public function testSwapLocationForSecondaryLocations() $secondaryLocation1 = $locationService->loadLocation($secondaryLocation1->id); $secondaryLocation2 = $locationService->loadLocation($secondaryLocation2->id); - self::assertEquals($folder2->id, $secondaryLocation1->contentInfo->id); - self::assertEquals($folder1->id, $secondaryLocation2->contentInfo->id); + self::assertEquals($folder2->id, $secondaryLocation1->getContentInfo()->getId()); + self::assertEquals($folder1->id, $secondaryLocation2->getContentInfo()->getId()); - self::assertEquals( - $folder1, - $contentService->loadContent($folder1->id, Language::ALL) + self::assertEqualsCanonicalizing( + [$folder1->getVersionInfo()->getContentInfo()->getMainLocationId(), $secondaryLocation2->id], + array_map( + static fn (Location $location): int => $location->id, + $locationService->loadLocations($folder1->getVersionInfo()->getContentInfo()) + ) ); - self::assertEquals( - $folder2, - $contentService->loadContent($folder2->id, Language::ALL) + self::assertEqualsCanonicalizing( + [$folder2->getVersionInfo()->getContentInfo()->getMainLocationId(), $secondaryLocation1->id], + array_map( + static fn (Location $location): int => $location->id, + $locationService->loadLocations($folder2->getVersionInfo()->getContentInfo()) + ) ); } diff --git a/tests/integration/Core/Repository/UserServiceTest.php b/tests/integration/Core/Repository/UserServiceTest.php index 61fd46d42c..fa83e7daaa 100644 --- a/tests/integration/Core/Repository/UserServiceTest.php +++ b/tests/integration/Core/Repository/UserServiceTest.php @@ -918,10 +918,7 @@ public function testNewUserCreateStructWithFifthParameter() $this->assertSame($userType, $userCreate->contentType); } - /** - * Test for creating user with Active Directory login name. - */ - public function testNewUserWithDomainName() + public function testNewUserWithDomainName(): void { $repository = $this->getRepository(); $userService = $repository->getUserService(); @@ -931,7 +928,7 @@ public function testNewUserWithDomainName() ); $loadedUser = $userService->loadUserByLogin('ibexa-user-Domain\username-by-login', Language::ALL); - $this->assertEquals($createdUser, $loadedUser); + $this->assertIsSameUser($createdUser, $loadedUser); } /** @@ -1334,12 +1331,13 @@ public function testCreateUserWithStrongPassword() } /** - * Test for the loadUser() method. + * @covers \Ibexa\Contracts\Core\Repository\UserService::loadUser() * - * @covers \Ibexa\Contracts\Core\Repository\UserService::loadUser() * @depends testCreateUser + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException */ - public function testLoadUser() + public function testLoadUser(): void { $repository = $this->getRepository(); @@ -1352,11 +1350,11 @@ public function testLoadUser() $userReloaded = $userService->loadUser($user->id, Language::ALL); /* END: Use Case */ - $this->assertEquals($user, $userReloaded); + $this->assertIsSameUser($user, $userReloaded); // User happens to also be a Content; isUser() should be true and isUserGroup() should be false - $this->assertTrue($userService->isUser($user), 'isUser() => false on a user'); - $this->assertFalse($userService->isUserGroup($user), 'isUserGroup() => true on a user group'); + self::assertTrue($userService->isUser($user), 'isUser() => false on a user'); + self::assertFalse($userService->isUserGroup($user), 'isUserGroup() => true on a user group'); } /** @@ -1541,25 +1539,23 @@ public function testLoadUserByLoginThrowsNotFoundExceptionForUnknownLoginByEmail } /** - * Test for the loadUsersByEmail() method. - * * @covers \Ibexa\Contracts\Core\Repository\UserService::loadUsersByEmail() + * * @depends testCreateUser */ - public function testLoadUserByEmail() + public function testLoadUserByEmail(): void { $repository = $this->getRepository(); $userService = $repository->getUserService(); - /* BEGIN: Use Case */ $user = $this->createUserVersion1(); // Load the newly created user $usersReloaded = $userService->loadUsersByEmail('user@example.com', Language::ALL); - /* END: Use Case */ - $this->assertEquals([$user], $usersReloaded); + self::assertCount(1, $usersReloaded); + $this->assertIsSameUser($user, $usersReloaded[0]); } /** @@ -2944,11 +2940,9 @@ public function testCreateUserWithDefaultPasswordHashTypeWhenHashTypeIsUnsupport } /** - * Test loading User by Token. - * * @covers \Ibexa\Contracts\Core\Repository\UserService::loadUserByToken */ - public function testLoadUserByToken() + public function testLoadUserByToken(): string { $repository = $this->getRepository(); $userService = $repository->getUserService(); @@ -2962,7 +2956,7 @@ public function testLoadUserByToken() $userService->updateUserToken($user, $userTokenUpdateStruct); $loadedUser = $userService->loadUserByToken($userTokenUpdateStruct->hashKey, Language::ALL); - self::assertEquals($user, $loadedUser); + $this->assertIsSameUser($user, $loadedUser); return $userTokenUpdateStruct->hashKey; } @@ -3445,6 +3439,14 @@ protected function updateRawPasswordHash(int $userId, int $newHashType): void $queryBuilder->execute(); } + + private function assertIsSameUser(User $expectedUser, User $actualUser): void + { + self::assertSame($expectedUser->getUserId(), $actualUser->getUserId()); + self::assertSame($expectedUser->getName(), $actualUser->getName()); + self::assertSame($expectedUser->login, $actualUser->login); + self::assertSame($expectedUser->email, $actualUser->email); + } } class_alias(UserServiceTest::class, 'eZ\Publish\API\Repository\Tests\UserServiceTest'); From d8321a967be04c86efd72302e49ae077859c63e0 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Wed, 5 Jul 2023 02:09:48 +0200 Subject: [PATCH 23/37] [Tests] Revamped NameSchemaService unit test --- .../NameSchemaServiceTest.php} | 123 +++++++++--------- tests/lib/Repository/Service/Mock/Base.php | 20 +-- 2 files changed, 66 insertions(+), 77 deletions(-) rename tests/lib/Repository/{Service/Mock/NameSchemaTest.php => NameSchema/NameSchemaServiceTest.php} (77%) diff --git a/tests/lib/Repository/Service/Mock/NameSchemaTest.php b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php similarity index 77% rename from tests/lib/Repository/Service/Mock/NameSchemaTest.php rename to tests/lib/Repository/NameSchema/NameSchemaServiceTest.php index 17bae53715..7e205ba882 100644 --- a/tests/lib/Repository/Service/Mock/NameSchemaTest.php +++ b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php @@ -4,67 +4,58 @@ * @copyright Copyright (C) Ibexa AS. All rights reserved. * @license For full copyright and license information view LICENSE file distributed with this source code. */ -namespace Ibexa\Tests\Core\Repository\Service\Mock; +declare(strict_types=1); + +namespace Ibexa\Tests\Core\Repository\NameSchema; use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; +use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\Values\Content\Field; use Ibexa\Core\FieldType\TextLine\Value as TextLineValue; use Ibexa\Core\Repository\NameSchema\NameSchemaService; +use Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor; use Ibexa\Core\Repository\Values\Content\Content; use Ibexa\Core\Repository\Values\Content\VersionInfo; use Ibexa\Core\Repository\Values\ContentType\ContentType; use Ibexa\Core\Repository\Values\ContentType\FieldDefinition; use Ibexa\Tests\Core\Repository\Service\Mock\Base as BaseServiceMockTest; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** - * @covers \Ibexa\Core\Repository\Helper\NameSchemaService + * @covers \Ibexa\Core\Repository\NameSchema\NameSchemaService */ -class NameSchemaTest extends BaseServiceMockTest +class NameSchemaServiceTest extends BaseServiceMockTest { - public function testResolveUrlAliasSchema() + public function testResolveUrlAliasSchema(): void { $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentType(); + $contentType = $this->buildTestContentTypeStub(); - $serviceMock = $this->getMockBuilder(NameSchemaService::class) - ->setMethods(['resolve']) - ->setConstructorArgs( - [ - $this->getPersistenceMock()->contentTypeHandler(), - $this->getContentTypeDomainMapperMock(), - $this->getFieldTypeRegistryMock(), - $this->getSchemaIdentifierExtractorMock(), - $this->getEventDispatcherMock(['field' => ''], $content, []), - ] - ) - ->getMock(); + $nameSchemaService = $this->buildNameSchemaService( + ['field' => ['']], + $content, + ['eng-GB' => ['url_alias_schema' => 'foo']] + ); - $result = $serviceMock->resolveUrlAliasSchema($content, $contentType); + $result = $nameSchemaService->resolveUrlAliasSchema($content, $contentType); - self::assertEquals([], $result); + self::assertEquals(['eng-GB' => 'foo'], $result); } - public function testResolveUrlAliasSchemaFallbackToNameSchema() + public function testResolveUrlAliasSchemaFallbackToNameSchema(): void { $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentType('', ''); + $contentType = $this->buildTestContentTypeStub('', ''); - $serviceMock = $this->getMockBuilder(NameSchemaService::class) - ->setMethods(['resolve']) - ->setConstructorArgs( - [ - $this->getPersistenceMock()->contentTypeHandler(), - $this->getContentTypeDomainMapperMock(), - $this->getFieldTypeRegistryMock(), - $this->getSchemaIdentifierExtractorMock(), - $this->getEventDispatcherMock(['field' => ''], $content, []), - ] - ) - ->getMock(); + $nameSchemaService = $this->buildNameSchemaService( + ['field' => ['']], + $content, + ['eng-GB' => ['name_schema' => 'bar']] + ); - $result = $serviceMock->resolveUrlAliasSchema($content, $contentType); + $result = $nameSchemaService->resolveUrlAliasSchema($content, $contentType); - self::assertEquals([], $result); + self::assertEquals(['eng-GB' => 'bar'], $result); } public function testResolveNameSchema() @@ -72,7 +63,7 @@ public function testResolveNameSchema() $serviceMock = $this->getPartlyMockedNameSchemaService(['resolve']); $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentType(); + $contentType = $this->buildTestContentTypeStub(); $serviceMock->expects( $this->once() @@ -97,7 +88,7 @@ public function testResolveNameSchemaWithFields() $serviceMock = $this->getPartlyMockedNameSchemaService(['resolve']); $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentType(); + $contentType = $this->buildTestContentTypeStub(); $fields = []; $fields['text3']['cro-HR'] = new TextLineValue('tri'); @@ -149,7 +140,7 @@ public function testResolve( $serviceMock = $this->getPartlyMockedNameSchemaService(['getFieldTitles'], $settings); $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentType(); + $contentType = $this->buildTestContentTypeStub(); $index = 0; foreach ($languageFieldValues as $languageCode => $fieldValue) { @@ -173,9 +164,9 @@ public function testResolve( } /** - * Data provider for the @see testResolve method. + * Data provider for the @return array. * - * @return array + * @see testResolve method. */ public function resolveDataProvider() { @@ -323,16 +314,10 @@ protected function buildTestContentObject() ); } - /** - * Build ContentType stub for testing purpose. - * - * @param string $nameSchema - * @param string $urlAliasSchema - * - * @return \Ibexa\Core\Repository\Values\ContentType\ContentType - */ - protected function buildTestContentType($nameSchema = '', $urlAliasSchema = '') - { + protected function buildTestContentTypeStub( + string $nameSchema = '', + string $urlAliasSchema = '' + ): ContentType { return new ContentType( [ 'nameSchema' => $nameSchema, @@ -350,18 +335,16 @@ protected function buildTestContentType($nameSchema = '', $urlAlias * @param string[] $methods * @param array $settings * - * @return \Ibexa\Core\Repository\Helper\NameSchemaService|\PHPUnit\Framework\MockObject\MockObject + * @return \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface&\PHPUnit\Framework\MockObject\MockObject */ - protected function getPartlyMockedNameSchemaService(array $methods = null, array $settings = []) + protected function getPartlyMockedNameSchemaService(array $methods = null, array $settings = []): NameSchemaServiceInterface { return $this->getMockBuilder(NameSchemaService::class) ->setMethods($methods) ->setConstructorArgs( [ - $this->getPersistenceMock()->contentTypeHandler(), - $this->getContentTypeDomainMapperMock(), $this->getFieldTypeRegistryMock(), - $this->getSchemaIdentifierExtractorMock(), + new SchemaIdentifierExtractor(), $this->getEventDispatcher(), $settings, ] @@ -369,17 +352,37 @@ protected function getPartlyMockedNameSchemaService(array $methods = null, array ->getMock(); } - protected function getEventDispatcherMock(array $schemaIdentifiers, Content $content, array $tokenValues) - { + /** + * @param array> $schemaIdentifiers + */ + protected function getEventDispatcherMock( + array $schemaIdentifiers, + Content $content, + array $tokenValues + ): EventDispatcherInterface { $event = new ResolveUrlAliasSchemaEvent($schemaIdentifiers, $content); $event->setTokenValues($tokenValues); - $eventDispatcherMock = parent::getEventDispatcher(); + $eventDispatcherMock = $this->getEventDispatcher(); $eventDispatcherMock->method('dispatch') ->willReturn($event); return $eventDispatcherMock; } -} -class_alias(NameSchemaTest::class, 'eZ\Publish\Core\Repository\Tests\Service\Mock\NameSchemaTest'); + /** + * @param array> $schemaIdentifiers + * @param array> $tokenValues + */ + private function buildNameSchemaService( + array $schemaIdentifiers, + Content $content, + array $tokenValues + ): NameSchemaService { + return new NameSchemaService( + $this->getFieldTypeRegistryMock(), + new SchemaIdentifierExtractor(), + $this->getEventDispatcherMock($schemaIdentifiers, $content, $tokenValues), + ); + } +} diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index c1f417a7be..29042bb991 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -171,26 +171,12 @@ protected function getFieldTypeRegistryMock() return $this->fieldTypeRegistryMock; } - protected $schemaIdentifierExtractor; + protected EventDispatcherInterface $eventDispatcher; /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface + * @return \Symfony\Contracts\EventDispatcher\EventDispatcherInterface&\PHPUnit\Framework\MockObject\MockObject */ - protected function getSchemaIdentifierExtractorMock() - { - if (!isset($this->schemaIdentifierExtractor)) { - $this->schemaIdentifierExtractor = $this->createMock(SchemaIdentifierExtractorInterface::class); - } - - return $this->schemaIdentifierExtractor; - } - - protected $eventDispatcher; - - /** - * @return \PHPUnit\Framework\MockObject\MockObject|\Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface - */ - protected function getEventDispatcher() + protected function getEventDispatcher(): EventDispatcherInterface { if (!isset($this->eventDispatcher)) { $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); From 8009aca3cdd2278c5a5f13a6be9a9e5ca05798b4 Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Wed, 5 Jul 2023 02:19:01 +0200 Subject: [PATCH 24/37] fixup! [Tests] Revamped NameSchemaService unit test --- tests/lib/Repository/Service/Mock/Base.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index 29042bb991..dfda628e4f 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -11,7 +11,6 @@ use Ibexa\Contracts\Core\Persistence\Handler; use Ibexa\Contracts\Core\Repository\LanguageResolver; use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; -use Ibexa\Contracts\Core\Repository\NameSchema\SchemaIdentifierExtractorInterface; use Ibexa\Contracts\Core\Repository\PasswordHashService; use Ibexa\Contracts\Core\Repository\PermissionService; use Ibexa\Contracts\Core\Repository\Repository as APIRepository; From 2992a36e3c89c544fabce5c77af7d6591c3d3e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Wed, 5 Jul 2023 14:10:28 +0200 Subject: [PATCH 25/37] Fixed field name generator --- .../EventSubscriber/NameSchemaSubscriber.php | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index 2a4c94d8f4..a8e7aa3368 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -9,10 +9,18 @@ namespace Ibexa\Core\Repository\EventSubscriber; use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; +use Ibexa\Core\FieldType\FieldTypeRegistry; use Symfony\Component\EventDispatcher\EventSubscriberInterface; final class NameSchemaSubscriber implements EventSubscriberInterface { + private FieldTypeRegistry $fieldTypeRegistry; + + public function __construct(FieldTypeRegistry $fieldTypeRegistry) + { + $this->fieldTypeRegistry = $fieldTypeRegistry; + } + public static function getSubscribedEvents(): array { return [ @@ -22,6 +30,11 @@ public static function getSubscribedEvents(): array ]; } + /** + * Resolves the URL alias schema by setting token values for specified field identifiers and languages. + * + * @param \Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent $event The event object containing the schema identifiers, content, languages, and token values. + */ public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void { if (!array_key_exists('field', $event->getSchemaIdentifiers())) { @@ -32,14 +45,22 @@ public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void $identifiers = $event->getSchemaIdentifiers()['field']; $languages = $event->getContent()->getVersionInfo()->getLanguages(); $tokenValues = $event->getTokenValues(); + + $contentType = $content->getContentType(); foreach ($languages as $language) { $languageCode = $language->getLanguageCode(); foreach ($identifiers as $identifier) { - $fieldValue = $content->getFieldValue( - $identifier, + $fieldDefinition = $contentType->getFieldDefinition($identifier); + $persistenceFieldType = $this->fieldTypeRegistry->getFieldType($fieldDefinition->fieldTypeIdentifier); + + $fieldValue = $content->getFieldValue($identifier, $languageCode); + $fieldValue = $persistenceFieldType->getName( + $fieldValue, + $fieldDefinition, $languageCode ); - $tokenValues[$languageCode][$identifier] = null !== $fieldValue ? (string)$fieldValue : $identifier; + + $tokenValues[$languageCode][$identifier] = $fieldValue; } } From e066bd336dd34fbda1531690c114c62ae53dd9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Thu, 6 Jul 2023 11:51:50 +0200 Subject: [PATCH 26/37] Fixed codesmells --- .../NameSchema/NameSchemaServiceInterface.php | 7 +++- .../EventSubscriber/NameSchemaSubscriber.php | 2 +- .../NameSchema/NameSchemaService.php | 37 ++++++++++++------- .../Core/Repository/LocationServiceTest.php | 1 - .../NameSchema/NameSchemaServiceTest.php | 26 +++++++++---- .../Repository/Service/Mock/ContentTest.php | 8 +++- 6 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php index b47ebef256..2dc88d5428 100644 --- a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php +++ b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php @@ -20,7 +20,12 @@ interface NameSchemaServiceInterface { public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array; - public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null): array; + public function resolveNameSchema( + Content $content, + array $fieldMap = [], + array $languageCodes = [], + ContentType $contentType = null + ): array; public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes): array; } diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index a8e7aa3368..78a895ee42 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -33,7 +33,7 @@ public static function getSubscribedEvents(): array /** * Resolves the URL alias schema by setting token values for specified field identifiers and languages. * - * @param \Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent $event The event object containing the schema identifiers, content, languages, and token values. + * @param \Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent $event */ public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void { diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index 6d3bb5c548..8418b28ed6 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -67,9 +67,9 @@ public function __construct( $this->fieldTypeRegistry = $fieldTypeRegistry; // Union makes sure default settings are ignored if provided in argument $this->settings = $settings + [ - 'limit' => 150, - 'sequence' => '...', - ]; + 'limit' => 150, + 'sequence' => '...', + ]; $this->eventDispatcher = $eventDispatcher; $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; } @@ -109,15 +109,17 @@ public function resolveUrlAliasSchema(Content $content, ContentType $contentType /** * Convenience method for resolving name schema. * - * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content * @param array $fieldMap * @param array $languageCodes - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType|null $contentType * * @return array */ - public function resolveNameSchema(Content $content, array $fieldMap = [], array $languageCodes = [], ContentType $contentType = null): array - { + public function resolveNameSchema( + Content $content, + array $fieldMap = [], + array $languageCodes = [], + ContentType $contentType = null + ): array { $contentType ??= $content->getContentType(); $languageCodes = $languageCodes ?: $content->versionInfo->languageCodes; @@ -202,17 +204,21 @@ public function resolve(string $nameSchema, ContentType $contentType, array $fie * Fetches the list of available Field identifiers in the token and returns * an array of their current title value. * - * @see \Ibexa\Core\Repository\Values\ContentType\FieldType::getName() - * * @param array $schemaIdentifiers * @param array $fieldMap * + * @return string[] Key is the field identifier, value is the title value + * * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentType * - * @return string[] Key is the field identifier, value is the title value + * @see \Ibexa\Core\Repository\Values\ContentType\FieldType::getName() */ - protected function getFieldTitles(array $schemaIdentifiers, ContentType $contentType, array $fieldMap, string $languageCode): array - { + protected function getFieldTitles( + array $schemaIdentifiers, + ContentType $contentType, + array $fieldMap, + string $languageCode + ): array { $fieldTitles = []; foreach ($schemaIdentifiers as $fieldDefinitionIdentifier) { @@ -285,7 +291,10 @@ protected function resolveToken(string $token, array $titles, array $groupLookup // In this case id1 or id1 is a token group. break; } else { - if (array_key_exists($tokenPart, $titles) && $titles[$tokenPart] !== '' && $titles[$tokenPart] !== null) { + if (array_key_exists( + $tokenPart, + $titles + ) && $titles[$tokenPart] !== '' && $titles[$tokenPart] !== null) { $replaceString = $titles[$tokenPart]; // We want to stop after the first matching token part / identifier is found // if id1 has a value, id2 will not be used. @@ -347,7 +356,7 @@ protected function tokenParts(string $token): array protected function filterNameSchema(string $nameSchema): array { $retNamePattern = ''; - $foundGroups = preg_match_all('/[<|\\|](\\(.+\\))[\\||>]/U', $nameSchema, $groupArray); + $foundGroups = preg_match_all('/\((.+)\)/U', $nameSchema, $groupArray); $groupLookupTable = []; if ($foundGroups) { diff --git a/tests/integration/Core/Repository/LocationServiceTest.php b/tests/integration/Core/Repository/LocationServiceTest.php index b27cbf169e..350347bda4 100644 --- a/tests/integration/Core/Repository/LocationServiceTest.php +++ b/tests/integration/Core/Repository/LocationServiceTest.php @@ -1875,7 +1875,6 @@ public function testSwapLocationForSecondaryLocations(): void { $repository = $this->getRepository(); $locationService = $repository->getLocationService(); - $contentService = $repository->getContentService(); $folder1 = $this->createFolder(['eng-GB' => 'Folder1'], 2); $folder2 = $this->createFolder(['eng-GB' => 'Folder2'], 2); diff --git a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php index 7e205ba882..240133d002 100644 --- a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php +++ b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php @@ -26,6 +26,8 @@ */ class NameSchemaServiceTest extends BaseServiceMockTest { + private const NAME_SCHEMA = ''; + public function testResolveUrlAliasSchema(): void { $content = $this->buildTestContentObject(); @@ -45,10 +47,10 @@ public function testResolveUrlAliasSchema(): void public function testResolveUrlAliasSchemaFallbackToNameSchema(): void { $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentTypeStub('', ''); + $contentType = $this->buildTestContentTypeStub(self::NAME_SCHEMA, ''); $nameSchemaService = $this->buildNameSchemaService( - ['field' => ['']], + ['field' => [self::NAME_SCHEMA]], $content, ['eng-GB' => ['name_schema' => 'bar']] ); @@ -70,7 +72,7 @@ public function testResolveNameSchema() )->method( 'resolve' )->with( - '', + self::NAME_SCHEMA, $this->equalTo($contentType), $this->equalTo($content->fields), $this->equalTo($content->versionInfo->languageCodes) @@ -108,7 +110,7 @@ public function testResolveNameSchemaWithFields() )->method( 'resolve' )->with( - '', + self::NAME_SCHEMA, $this->equalTo($contentType), $this->equalTo($mergedFields), $this->equalTo($languages) @@ -158,7 +160,12 @@ public function testResolve( ); } - $result = $serviceMock->resolve($nameSchema, $contentType, $content->fields, $content->versionInfo->languageCodes); + $result = $serviceMock->resolve( + $nameSchema, + $contentType, + $content->fields, + $content->versionInfo->languageCodes + ); self::assertEquals($languageFieldValues, $result); } @@ -335,10 +342,13 @@ protected function buildTestContentTypeStub( * @param string[] $methods * @param array $settings * - * @return \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface&\PHPUnit\Framework\MockObject\MockObject + * @return \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface + * &\PHPUnit\Framework\MockObject\MockObject */ - protected function getPartlyMockedNameSchemaService(array $methods = null, array $settings = []): NameSchemaServiceInterface - { + protected function getPartlyMockedNameSchemaService( + array $methods = null, + array $settings = [] + ): NameSchemaServiceInterface { return $this->getMockBuilder(NameSchemaService::class) ->setMethods($methods) ->setConstructorArgs( diff --git a/tests/lib/Repository/Service/Mock/ContentTest.php b/tests/lib/Repository/Service/Mock/ContentTest.php index 7d4623f10e..5f6a4558c7 100644 --- a/tests/lib/Repository/Service/Mock/ContentTest.php +++ b/tests/lib/Repository/Service/Mock/ContentTest.php @@ -6215,11 +6215,15 @@ protected function getRelationProcessorMock() return $this->relationProcessorMock; } - /** @var \PHPUnit\Framework\MockObject\MockObject&\Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ + /** + * @var \PHPUnit\Framework\MockObject\MockObject + * &\Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface + */ protected NameSchemaServiceInterface $nameSchemaServiceMock; /** - * @return \PHPUnit\Framework\MockObject\MockObject&\Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface + * @return \PHPUnit\Framework\MockObject\MockObject + * &\Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ protected function getNameSchemaServiceMock(): NameSchemaServiceInterface { From c31d9f9d1818b23c066ef32ab99c37fd0fe23d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Thu, 6 Jul 2023 14:43:59 +0200 Subject: [PATCH 27/37] Fixed SchemaIdentifierExtractor --- .../NameSchema/SchemaIdentifierExtractor.php | 23 +++++++++++-------- .../SchemaIdentifierExtractorTest.php | 15 ++++++++---- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php b/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php index 2f9c0739fb..803c6c814d 100644 --- a/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php +++ b/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php @@ -27,7 +27,7 @@ final class SchemaIdentifierExtractor implements SchemaIdentifierExtractorInterf */ public function extract(string $schemaString): array { - $allTokens = '/<(.*?)>/'; + $allTokens = '/<([^>]+)>/'; if (false === preg_match_all($allTokens, $schemaString, $matches)) { return []; @@ -35,15 +35,20 @@ public function extract(string $schemaString): array $strategyIdentifiers = []; foreach ($matches[1] as $tokenExpression) { - $strategyToken = explode(':', $tokenExpression, 2); - if (count($strategyToken) === 2) { - [$strategy, $token] = $strategyToken; - } else { - $token = $strategyToken[0]; - $strategy = 'field'; - } + $tokens = explode('|', $tokenExpression); + + foreach ($tokens as $token) { + $strategyToken = explode(':', $token, 2); - $strategyIdentifiers[$strategy] = array_merge($strategyIdentifiers[$strategy] ?? [], explode('|', $token)); + if (count($strategyToken) === 2) { + [$strategy, $token] = $strategyToken; + } else { + $token = $strategyToken[0]; + $strategy = 'field'; + } + + $strategyIdentifiers[$strategy][] = $token; + } } return $strategyIdentifiers; diff --git a/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php index daf16d61bb..76a4d2dc97 100644 --- a/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php +++ b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php @@ -45,8 +45,8 @@ public function getDataForTestExtract(): iterable yield $schemaString => [ $schemaString, [ - 'custom_strategy' => ['foo'], - 'field' => ['bar'], + 'custom_strategy' => ['bar'], + 'field' => ['baz'], ], ]; @@ -58,6 +58,15 @@ public function getDataForTestExtract(): iterable 'field' => ['bar'], ], ]; + + $schemaString = '-'; + yield $schemaString => [ + $schemaString, + [ + 'custom_strategy' => ['foo', 'bar'], + 'field' => ['bar', 'baz'], + ], + ]; } protected function setUp(): void @@ -72,8 +81,6 @@ protected function setUp(): void */ public function testExtract(string $schemaString, array $expectedStrategyIdentifierMap): void { - $this->markTestIncomplete('Requires fixing SchemaIdentifierExtractor behavior'); - self::assertSame($expectedStrategyIdentifierMap, $this->extractor->extract($schemaString)); } } From 651c58041a57fb678f79cc579942f387201757a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Fri, 7 Jul 2023 10:18:15 +0200 Subject: [PATCH 28/37] Fixed the issue when fielddefinition not found --- src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index 78a895ee42..dd3c1e203c 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -51,6 +51,9 @@ public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void $languageCode = $language->getLanguageCode(); foreach ($identifiers as $identifier) { $fieldDefinition = $contentType->getFieldDefinition($identifier); + if (null === $fieldDefinition) { + continue; + } $persistenceFieldType = $this->fieldTypeRegistry->getFieldType($fieldDefinition->fieldTypeIdentifier); $fieldValue = $content->getFieldValue($identifier, $languageCode); From 1503ec976025ecafffda9f1428377986a409a5f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Mon, 10 Jul 2023 11:14:28 +0200 Subject: [PATCH 29/37] Applied CR --- .../Event/ResolveUrlAliasSchemaEvent.php | 3 +++ .../NameSchema/NameSchemaServiceInterface.php | 16 ++++++++++++++++ src/lib/Repository/ContentService.php | 3 +-- .../Repository/NameSchema/NameSchemaService.php | 16 +++++++++------- src/lib/Repository/Repository.php | 9 +-------- src/lib/Repository/TrashService.php | 3 +-- src/lib/Repository/URLAliasService.php | 3 +-- .../Core/Repository/UserServiceTest.php | 2 +- .../NameSchema/NameSchemaServiceTest.php | 2 +- 9 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php index 3500817bb4..3f75eb3463 100644 --- a/src/contracts/Event/ResolveUrlAliasSchemaEvent.php +++ b/src/contracts/Event/ResolveUrlAliasSchemaEvent.php @@ -18,6 +18,9 @@ final class ResolveUrlAliasSchemaEvent extends Event private Content $content; + /** + * @var array> + */ private array $names = []; public function __construct( diff --git a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php index 2dc88d5428..3c7dd789c4 100644 --- a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php +++ b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php @@ -20,6 +20,12 @@ interface NameSchemaServiceInterface { public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array; + /** + * @param array> $fieldMap + * @param array $languageCodes + * + * @return array + */ public function resolveNameSchema( Content $content, array $fieldMap = [], @@ -27,5 +33,15 @@ public function resolveNameSchema( ContentType $contentType = null ): array; + /** + * Returns the real name for a content name pattern. + * + * @param array> $fieldMap + * @param array $languageCodes + * + * @return array + * + * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException + */ public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes): array; } diff --git a/src/lib/Repository/ContentService.php b/src/lib/Repository/ContentService.php index 70c08bda2c..c3009df5d0 100644 --- a/src/lib/Repository/ContentService.php +++ b/src/lib/Repository/ContentService.php @@ -88,8 +88,7 @@ class ContentService implements ContentServiceInterface /** @var \Ibexa\Core\Repository\Helper\RelationProcessor */ protected $relationProcessor; - /** @var \Ibexa\Core\Repository\Helper\NameSchemaService */ - protected $nameSchemaService; + protected NameSchemaServiceInterface $nameSchemaService; /** @var \Ibexa\Core\FieldType\FieldTypeRegistry */ protected $fieldTypeRegistry; diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index 8418b28ed6..43fe0a18d5 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -51,8 +51,10 @@ class NameSchemaService implements NameSchemaServiceInterface protected FieldTypeRegistry $fieldTypeRegistry; - /** @var array */ - protected $settings; + /** + * @param array{limit?: integer, sequence?: string} $settings + */ + protected array $settings; private EventDispatcherInterface $eventDispatcher; @@ -145,7 +147,7 @@ public function resolveNameSchema( * * @return array */ - protected function mergeFieldMap(Content $content, array $fieldMap, array $languageCodes) + protected function mergeFieldMap(Content $content, array $fieldMap, array $languageCodes): array { if (empty($fieldMap)) { return $content->fields; @@ -155,9 +157,8 @@ protected function mergeFieldMap(Content $content, array $fieldMap, array $langu foreach ($content->fields as $fieldIdentifier => $fieldLanguageMap) { foreach ($languageCodes as $languageCode) { - $mergedFieldMap[$fieldIdentifier][$languageCode] = isset($fieldMap[$fieldIdentifier][$languageCode]) - ? $fieldMap[$fieldIdentifier][$languageCode] - : $fieldLanguageMap[$languageCode]; + $mergedFieldMap[$fieldIdentifier][$languageCode] + = $fieldMap[$fieldIdentifier][$languageCode] ?? $fieldLanguageMap[$languageCode]; } } @@ -366,6 +367,7 @@ protected function filterNameSchema(string $nameSchema): array $metaToken = self::META_STRING . $i; // Insert the group with its placeholder token + /** @var string $retNamePattern */ $retNamePattern = str_replace($group, $metaToken, $nameSchema); // Remove the pattern "(" ")" from the tokens @@ -411,7 +413,7 @@ protected function getIdentifiers(string $schemaString): array public function validateNameLength(string $name): string { - // Make sure length is not longer then $limit unless it's 0 + // Make sure length is not longer than $limit unless it's 0 if ($this->settings['limit'] && mb_strlen($name) > $this->settings['limit']) { $name = rtrim( mb_substr($name, 0, $this->settings['limit'] - strlen($this->settings['sequence'])) diff --git a/src/lib/Repository/Repository.php b/src/lib/Repository/Repository.php index 7fb7f42541..1b087379ac 100644 --- a/src/lib/Repository/Repository.php +++ b/src/lib/Repository/Repository.php @@ -150,12 +150,7 @@ class Repository implements RepositoryInterface /** @var \Ibexa\Core\FieldType\FieldTypeRegistry */ private $fieldTypeRegistry; - /** - * Instance of name schema resolver service. - * - * @var \Ibexa\Core\Repository\Helper\NameSchemaService - */ - protected $nameSchemaService; + protected NameSchemaServiceInterface $nameSchemaService; /** * Instance of relation processor service. @@ -731,8 +726,6 @@ public function getPermissionResolver(): PermissionResolverInterface /** * @internal * @private - * - * @return \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ public function getNameSchemaService(): NameSchemaServiceInterface { diff --git a/src/lib/Repository/TrashService.php b/src/lib/Repository/TrashService.php index 81d0b7524f..b88b705111 100644 --- a/src/lib/Repository/TrashService.php +++ b/src/lib/Repository/TrashService.php @@ -51,8 +51,7 @@ class TrashService implements TrashServiceInterface /** @var array */ protected $settings; - /** @var \Ibexa\Core\Repository\Helper\NameSchemaService */ - protected $nameSchemaService; + protected NameSchemaServiceInterface $nameSchemaService; /** @var \Ibexa\Contracts\Core\Repository\PermissionCriterionResolver */ private $permissionCriterionResolver; diff --git a/src/lib/Repository/URLAliasService.php b/src/lib/Repository/URLAliasService.php index b340c8243b..fb342f58cf 100644 --- a/src/lib/Repository/URLAliasService.php +++ b/src/lib/Repository/URLAliasService.php @@ -34,8 +34,7 @@ class URLAliasService implements URLAliasServiceInterface /** @var \Ibexa\Contracts\Core\Persistence\Content\UrlAlias\Handler */ protected $urlAliasHandler; - /** @var \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface */ - protected $nameSchemaService; + protected NameSchemaServiceInterface $nameSchemaService; /** @var \Ibexa\Contracts\Core\Repository\PermissionResolver */ private $permissionResolver; diff --git a/tests/integration/Core/Repository/UserServiceTest.php b/tests/integration/Core/Repository/UserServiceTest.php index fa83e7daaa..e33f4a24e4 100644 --- a/tests/integration/Core/Repository/UserServiceTest.php +++ b/tests/integration/Core/Repository/UserServiceTest.php @@ -1331,7 +1331,7 @@ public function testCreateUserWithStrongPassword() } /** - * @covers \Ibexa\Contracts\Core\Repository\UserService::loadUser() + * @covers \Ibexa\Contracts\Core\Repository\UserService::loadUser() * * @depends testCreateUser * diff --git a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php index 240133d002..664753cc24 100644 --- a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php +++ b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php @@ -24,7 +24,7 @@ /** * @covers \Ibexa\Core\Repository\NameSchema\NameSchemaService */ -class NameSchemaServiceTest extends BaseServiceMockTest +final class NameSchemaServiceTest extends BaseServiceMockTest { private const NAME_SCHEMA = ''; From 8c576815c4fdb51428d4d447def618f861f9a50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Tue, 11 Jul 2023 09:15:18 +0200 Subject: [PATCH 30/37] Added missing return type --- src/lib/Repository/NameSchema/NameSchemaService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index 43fe0a18d5..1cf8164be5 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -352,7 +352,7 @@ protected function tokenParts(string $token): array * * @param string $nameSchema * - * @return array + * @return array{string, array} */ protected function filterNameSchema(string $nameSchema): array { From e7e5f734c0d74423c4e6016c4898078cc2e83c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Tue, 11 Jul 2023 09:21:18 +0200 Subject: [PATCH 31/37] Improved NameSchemaService test --- .../NameSchema/NameSchemaServiceTest.php | 280 ++++++++---------- 1 file changed, 120 insertions(+), 160 deletions(-) diff --git a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php index 664753cc24..1f25ed7af9 100644 --- a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php +++ b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php @@ -9,8 +9,9 @@ namespace Ibexa\Tests\Core\Repository\NameSchema; use Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent; -use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\Values\Content\Field; +use Ibexa\Contracts\Core\Repository\Values\ContentType\FieldDefinitionCollection as APIFieldDefinitionCollection; +use Ibexa\Core\FieldType\TextLine\Type as TextLineFieldType; use Ibexa\Core\FieldType\TextLine\Value as TextLineValue; use Ibexa\Core\Repository\NameSchema\NameSchemaService; use Ibexa\Core\Repository\NameSchema\SchemaIdentifierExtractor; @@ -18,13 +19,14 @@ use Ibexa\Core\Repository\Values\Content\VersionInfo; use Ibexa\Core\Repository\Values\ContentType\ContentType; use Ibexa\Core\Repository\Values\ContentType\FieldDefinition; +use Ibexa\Core\Repository\Values\ContentType\FieldDefinitionCollection; use Ibexa\Tests\Core\Repository\Service\Mock\Base as BaseServiceMockTest; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; /** * @covers \Ibexa\Core\Repository\NameSchema\NameSchemaService */ -final class NameSchemaServiceTest extends BaseServiceMockTest +class NameSchemaServiceTest extends BaseServiceMockTest { private const NAME_SCHEMA = ''; @@ -60,122 +62,66 @@ public function testResolveUrlAliasSchemaFallbackToNameSchema(): void self::assertEquals(['eng-GB' => 'bar'], $result); } - public function testResolveNameSchema() - { - $serviceMock = $this->getPartlyMockedNameSchemaService(['resolve']); - - $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentTypeStub(); - - $serviceMock->expects( - $this->once() - )->method( - 'resolve' - )->with( - self::NAME_SCHEMA, - $this->equalTo($contentType), - $this->equalTo($content->fields), - $this->equalTo($content->versionInfo->languageCodes) - )->will( - $this->returnValue([42]) - ); - - $result = $serviceMock->resolveNameSchema($content, [], [], $contentType); - - self::assertEquals([42], $result); - } - - public function testResolveNameSchemaWithFields() + /** + * @return iterable>, array, array> + */ + public static function getDataForTestResolveNameSchema(): iterable { - $serviceMock = $this->getPartlyMockedNameSchemaService(['resolve']); - - $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentTypeStub(); - - $fields = []; - $fields['text3']['cro-HR'] = new TextLineValue('tri'); - $fields['text1']['ger-DE'] = new TextLineValue('ein'); - $fields['text2']['ger-DE'] = new TextLineValue('zwei'); - $fields['text3']['ger-DE'] = new TextLineValue('drei'); - $mergedFields = $fields; - $mergedFields['text1']['cro-HR'] = new TextLineValue('jedan'); - $mergedFields['text2']['cro-HR'] = new TextLineValue('dva'); - $mergedFields['text1']['eng-GB'] = new TextLineValue('one'); - $mergedFields['text2']['eng-GB'] = new TextLineValue('two'); - $mergedFields['text3']['eng-GB'] = new TextLineValue(''); - $languages = ['eng-GB', 'cro-HR', 'ger-DE']; - - $serviceMock->expects( - $this->once() - )->method( - 'resolve' - )->with( - self::NAME_SCHEMA, - $this->equalTo($contentType), - $this->equalTo($mergedFields), - $this->equalTo($languages) - )->will( - $this->returnValue([42]) - ); - - $result = $serviceMock->resolveNameSchema($content, $fields, $languages, $contentType); + yield 'Default: Field Map and Languages taken from Content Version' => [ + [], + [], + [ + 'eng-GB' => 'two', + 'cro-HR' => 'dva', + ], + ]; - self::assertEquals([42], $result); + yield 'Field Map and Languages for update' => [ + [ + 'text1' => ['cro-HR' => new TextLineValue('jedan'), 'eng-GB' => new TextLineValue('one')], + 'text2' => ['cro-HR' => new TextLineValue('Dva'), 'eng-GB' => new TextLineValue('two')], + 'text3' => ['eng-GB' => new TextLineValue('three')], + ], + ['eng-GB', 'cro-HR'], + [ + 'eng-GB' => 'three', + 'cro-HR' => 'Dva', + ], + ]; } /** - * @dataProvider resolveDataProvider + * @dataProvider getDataForTestResolveNameSchema * - * @param string[] $schemaIdentifiers - * @param string $nameSchema - * @param string[] $languageFieldValues field value translations - * @param string[] $fieldTitles [language => [field_identifier => title]] - * @param array $settings NameSchemaService settings + * @param array> $fieldMap A map of Field Definition Identifier and Language Code to Field Value + * @param array $languageCodes + * @param array $expectedNames */ - public function testResolve( - array $schemaIdentifiers, - $nameSchema, - $languageFieldValues, - $fieldTitles, - $settings = [] - ) { - $serviceMock = $this->getPartlyMockedNameSchemaService(['getFieldTitles'], $settings); - + public function testResolveNameSchema(array $fieldMap, array $languageCodes, array $expectedNames): void + { $content = $this->buildTestContentObject(); - $contentType = $this->buildTestContentTypeStub(); + $nameSchema = ''; + $nameSchemaService = $this->buildNameSchemaService( + ['field' => [$nameSchema]], + $content, + [] + ); + $contentType = $this->buildTestContentTypeStub($nameSchema, $nameSchema); - $index = 0; - foreach ($languageFieldValues as $languageCode => $fieldValue) { - $serviceMock->expects( - $this->at($index++) - )->method( - 'getFieldTitles' - )->with( - $schemaIdentifiers, - $contentType, - $content->fields, - $languageCode - )->will( - $this->returnValue($fieldTitles[$languageCode]) - ); - } + $result = $nameSchemaService->resolveNameSchema($content, $fieldMap, $languageCodes, $contentType); - $result = $serviceMock->resolve( - $nameSchema, - $contentType, - $content->fields, - $content->versionInfo->languageCodes + self::assertEquals( + $expectedNames, + $result ); - - self::assertEquals($languageFieldValues, $result); } /** - * Data provider for the @return array. + * Data provider for the testResolve method. * - * @see testResolve method. + * @see testResolve */ - public function resolveDataProvider() + public static function getDataForTestResolve(): array { return [ [ @@ -221,10 +167,44 @@ public function resolveDataProvider() ]; } + /** + * @dataProvider getDataForTestResolve + * + * @param string[] $schemaIdentifiers + * @param string[] $languageFieldValues field value translations + * @param string[] $fieldTitles [language => [field_identifier => title]] + * @param array $settings NameSchemaService settings + */ + public function testResolve( + array $schemaIdentifiers, + string $nameSchema, + array $languageFieldValues, + array $fieldTitles, + array $settings = [] + ): void { + $content = $this->buildTestContentObject(); + $nameSchemaService = $this->buildNameSchemaService( + ['field' => [$nameSchema]], + $content, + [], + $settings + ); + $contentType = $this->buildTestContentTypeStub($nameSchema, $nameSchema); + + $result = $nameSchemaService->resolve( + $nameSchema, + $contentType, + $content->fields, + $content->versionInfo->languageCodes + ); + + self::assertEquals($languageFieldValues, $result); + } + /** * @return \Ibexa\Contracts\Core\Repository\Values\Content\Field[] */ - protected function getFields() + protected function getFields(): array { return [ new Field( @@ -272,34 +252,33 @@ protected function getFields() ]; } - /** - * @return \Ibexa\Core\Repository\Values\ContentType\FieldDefinition[] - */ - protected function getFieldDefinitions() + protected function getFieldDefinitions(): APIFieldDefinitionCollection { - return [ - new FieldDefinition( - [ - 'id' => '1', - 'identifier' => 'text1', - 'fieldTypeIdentifier' => 'ezstring', - ] - ), - new FieldDefinition( - [ - 'id' => '2', - 'identifier' => 'text2', - 'fieldTypeIdentifier' => 'ezstring', - ] - ), - new FieldDefinition( - [ - 'id' => '3', - 'identifier' => 'text3', - 'fieldTypeIdentifier' => 'ezstring', - ] - ), - ]; + return new FieldDefinitionCollection( + [ + new FieldDefinition( + [ + 'id' => '1', + 'identifier' => 'text1', + 'fieldTypeIdentifier' => 'ezstring', + ] + ), + new FieldDefinition( + [ + 'id' => '2', + 'identifier' => 'text2', + 'fieldTypeIdentifier' => 'ezstring', + ] + ), + new FieldDefinition( + [ + 'id' => '3', + 'identifier' => 'text3', + 'fieldTypeIdentifier' => 'ezstring', + ] + ), + ] + ); } /** @@ -334,34 +313,6 @@ protected function buildTestContentTypeStub( ); } - /** - * Returns the content service to test with $methods mocked. - * - * Injected Repository comes from {@see getRepositoryMock()} - * - * @param string[] $methods - * @param array $settings - * - * @return \Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface - * &\PHPUnit\Framework\MockObject\MockObject - */ - protected function getPartlyMockedNameSchemaService( - array $methods = null, - array $settings = [] - ): NameSchemaServiceInterface { - return $this->getMockBuilder(NameSchemaService::class) - ->setMethods($methods) - ->setConstructorArgs( - [ - $this->getFieldTypeRegistryMock(), - new SchemaIdentifierExtractor(), - $this->getEventDispatcher(), - $settings, - ] - ) - ->getMock(); - } - /** * @param array> $schemaIdentifiers */ @@ -383,16 +334,25 @@ protected function getEventDispatcherMock( /** * @param array> $schemaIdentifiers * @param array> $tokenValues + * @param array{limit?: integer, sequence?: string} $settings */ private function buildNameSchemaService( array $schemaIdentifiers, Content $content, - array $tokenValues + array $tokenValues, + array $settings = [] ): NameSchemaService { + $fieldTypeRegistryMock = $this->getFieldTypeRegistryMock(); + $fieldTypeRegistryMock + ->method('getFieldType') + ->with('ezstring') + ->willReturn(new TextLineFieldType()); + return new NameSchemaService( - $this->getFieldTypeRegistryMock(), + $fieldTypeRegistryMock, new SchemaIdentifierExtractor(), $this->getEventDispatcherMock($schemaIdentifiers, $content, $tokenValues), + $settings ); } } From 9da853264acf3800a768a52439947c88319488db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Tue, 11 Jul 2023 09:42:59 +0200 Subject: [PATCH 32/37] Fixes Applied --- .../NameSchema/NameSchemaServiceInterface.php | 3 + .../NameSchema/NameSchemaService.php | 87 +++++++------------ 2 files changed, 32 insertions(+), 58 deletions(-) diff --git a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php index 3c7dd789c4..1d27bb149e 100644 --- a/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php +++ b/src/contracts/Repository/NameSchema/NameSchemaServiceInterface.php @@ -18,6 +18,9 @@ */ interface NameSchemaServiceInterface { + /** + * @return array key value map of names for a language code + */ public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array; /** diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index 1cf8164be5..8554f24f1a 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -60,6 +60,9 @@ class NameSchemaService implements NameSchemaServiceInterface private SchemaIdentifierExtractorInterface $schemaIdentifierExtractor; + /** + * @param array{limit?: integer, sequence?: string} $settings + */ public function __construct( FieldTypeRegistry $fieldTypeRegistry, SchemaIdentifierExtractorInterface $schemaIdentifierExtractor, @@ -76,9 +79,6 @@ public function __construct( $this->schemaIdentifierExtractor = $schemaIdentifierExtractor; } - /** - * @return array key value map of names for a language code - */ public function resolveUrlAliasSchema(Content $content, ContentType $contentType = null): array { $contentType ??= $content->getContentType(); @@ -108,14 +108,6 @@ public function resolveUrlAliasSchema(Content $content, ContentType $contentType return $names; } - /** - * Convenience method for resolving name schema. - * - * @param array $fieldMap - * @param array $languageCodes - * - * @return array - */ public function resolveNameSchema( Content $content, array $fieldMap = [], @@ -165,16 +157,6 @@ protected function mergeFieldMap(Content $content, array $fieldMap, array $langu return $mergedFieldMap; } - /** - * Returns the real name for a content name pattern. - * - * @param string $nameSchema - * @param \Ibexa\Contracts\Core\Repository\Values\ContentType\ContentType $contentType - * @param array $fieldMap - * @param array $languageCodes - * - * @return string[] - */ public function resolve(string $nameSchema, ContentType $contentType, array $fieldMap, array $languageCodes): array { [$filteredNameSchema, $groupLookupTable] = $this->filterNameSchema($nameSchema); @@ -206,12 +188,10 @@ public function resolve(string $nameSchema, ContentType $contentType, array $fie * an array of their current title value. * * @param array $schemaIdentifiers - * @param array $fieldMap + * @param array> $fieldMap * * @return string[] Key is the field identifier, value is the title value * - * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentType - * * @see \Ibexa\Core\Repository\Values\ContentType\FieldType::getName() */ protected function getFieldTitles( @@ -223,19 +203,20 @@ protected function getFieldTitles( $fieldTitles = []; foreach ($schemaIdentifiers as $fieldDefinitionIdentifier) { - if (isset($fieldMap[$fieldDefinitionIdentifier][$languageCode])) { - $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); - - $persistenceFieldType = $this->fieldTypeRegistry->getFieldType( - $fieldDefinition->fieldTypeIdentifier - ); - - $fieldTitles[$fieldDefinitionIdentifier] = $persistenceFieldType->getName( - $fieldMap[$fieldDefinitionIdentifier][$languageCode], - $fieldDefinition, - $languageCode - ); + if (!isset($fieldMap[$fieldDefinitionIdentifier][$languageCode])) { + continue; } + + $fieldDefinition = $contentType->getFieldDefinition($fieldDefinitionIdentifier); + $persistenceFieldType = $this->fieldTypeRegistry->getFieldType( + $fieldDefinition->fieldTypeIdentifier + ); + + $fieldTitles[$fieldDefinitionIdentifier] = $persistenceFieldType->getName( + $fieldMap[$fieldDefinitionIdentifier][$languageCode], + $fieldDefinition, + $languageCode + ); } return $fieldTitles; @@ -246,7 +227,7 @@ protected function getFieldTitles( * * Example: * - * Text <token> more text ==> <token> + * Text more text ==> * */ protected function extractTokens(string $nameSchema): array @@ -291,16 +272,15 @@ protected function resolveToken(string $token, array $titles, array $groupLookup // if id1 has a value, id2 will not be used. // In this case id1 or id1 is a token group. break; - } else { - if (array_key_exists( - $tokenPart, - $titles - ) && $titles[$tokenPart] !== '' && $titles[$tokenPart] !== null) { - $replaceString = $titles[$tokenPart]; - // We want to stop after the first matching token part / identifier is found - // if id1 has a value, id2 will not be used. - break; - } + } + if (array_key_exists($tokenPart, $titles) + && $titles[$tokenPart] !== '' + && $titles[$tokenPart] !== null + ) { + $replaceString = $titles[$tokenPart]; + // We want to stop after the first matching token part / identifier is found + // if id1 has a value, id2 will not be used. + break; } } @@ -309,18 +289,10 @@ protected function resolveToken(string $token, array $titles, array $groupLookup /** * Checks whether $identifier is a placeholder for a token group. - * - * @param string $identifier - * - * @return bool */ protected function isTokenGroup(string $identifier): bool { - if (strpos($identifier, self::META_STRING) === false) { - return false; - } - - return true; + return strpos($identifier, self::META_STRING) !== false; } /** @@ -347,8 +319,7 @@ protected function tokenParts(string $token): array * The groups are referenced with a generated meta-token in the original * name pattern. * - * Returns intermediate name pattern where groups are replaced with meta- - * tokens. + * Returns intermediate name pattern where groups are replaced with meta-tokens. * * @param string $nameSchema * From 595532bb37574cca6cae2dde84d7758b9bb36522 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Tue, 11 Jul 2023 09:48:08 +0200 Subject: [PATCH 33/37] Made NameSchemaServiceTest class final --- tests/lib/Repository/NameSchema/NameSchemaServiceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php index 1f25ed7af9..37f0d64697 100644 --- a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php +++ b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php @@ -26,7 +26,7 @@ /** * @covers \Ibexa\Core\Repository\NameSchema\NameSchemaService */ -class NameSchemaServiceTest extends BaseServiceMockTest +final class NameSchemaServiceTest extends BaseServiceMockTest { private const NAME_SCHEMA = ''; From 3fb07610d6c7ec3fd48d2163441133242525355b Mon Sep 17 00:00:00 2001 From: Andrew Longosz Date: Tue, 11 Jul 2023 12:26:55 +0200 Subject: [PATCH 34/37] [Tests] Refactored NameSchemaServiceTest to avoid code duplication --- .../NameSchema/NameSchemaServiceTest.php | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php index 37f0d64697..a1c6bdfa03 100644 --- a/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php +++ b/tests/lib/Repository/NameSchema/NameSchemaServiceTest.php @@ -22,6 +22,7 @@ use Ibexa\Core\Repository\Values\ContentType\FieldDefinitionCollection; use Ibexa\Tests\Core\Repository\Service\Mock\Base as BaseServiceMockTest; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Traversable; /** * @covers \Ibexa\Core\Repository\NameSchema\NameSchemaService @@ -202,54 +203,35 @@ public function testResolve( } /** - * @return \Ibexa\Contracts\Core\Repository\Values\Content\Field[] + * @return \Traversable<\Ibexa\Contracts\Core\Repository\Values\Content\Field> */ - protected function getFields(): array + protected function getFields(): Traversable { - return [ - new Field( - [ - 'languageCode' => 'eng-GB', - 'fieldDefIdentifier' => 'text1', - 'value' => new TextLineValue('one'), - ] - ), - new Field( - [ - 'languageCode' => 'eng-GB', - 'fieldDefIdentifier' => 'text2', - 'value' => new TextLineValue('two'), - ] - ), - new Field( - [ - 'languageCode' => 'eng-GB', - 'fieldDefIdentifier' => 'text3', - 'value' => new TextLineValue(''), - ] - ), - new Field( - [ - 'languageCode' => 'cro-HR', - 'fieldDefIdentifier' => 'text1', - 'value' => new TextLineValue('jedan'), - ] - ), - new Field( - [ - 'languageCode' => 'cro-HR', - 'fieldDefIdentifier' => 'text2', - 'value' => new TextLineValue('dva'), - ] - ), - new Field( - [ - 'languageCode' => 'cro-HR', - 'fieldDefIdentifier' => 'text3', - 'value' => new TextLineValue(''), - ] - ), + $translatedFieldValueMap = [ + 'eng-GB' => [ + 'text1' => 'one', + 'text2' => 'two', + 'text3' => '', + ], + 'cro-HR' => [ + 'text1' => 'jedan', + 'text2' => 'dva', + 'text3' => '', + ], ]; + + foreach ($translatedFieldValueMap as $languageCode => $fieldValues) { + foreach ($fieldValues as $fieldDefinitionIdentifier => $textValue) { + yield new Field( + [ + 'languageCode' => $languageCode, + 'fieldDefIdentifier' => $fieldDefinitionIdentifier, + 'value' => new TextLineValue($textValue), + 'fieldTypeIdentifier' => 'ezstring', + ] + ); + } + } } protected function getFieldDefinitions(): APIFieldDefinitionCollection @@ -290,7 +272,7 @@ protected function buildTestContentObject() { return new Content( [ - 'internalFields' => $this->getFields(), + 'internalFields' => iterator_to_array($this->getFields()), 'versionInfo' => new VersionInfo( [ 'languageCodes' => ['eng-GB', 'cro-HR'], From 4375c0d82c11d9c4460944761e12bfc9eb2731e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Wed, 12 Jul 2023 13:04:18 +0200 Subject: [PATCH 35/37] Fixed CR issues --- src/lib/Event/Repository.php | 12 +----------- .../EventSubscriber/NameSchemaSubscriber.php | 3 +++ src/lib/Repository/LocationService.php | 3 +-- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/lib/Event/Repository.php b/src/lib/Event/Repository.php index f02d58f5fb..c8164d2eb7 100644 --- a/src/lib/Event/Repository.php +++ b/src/lib/Event/Repository.php @@ -14,7 +14,6 @@ use Ibexa\Contracts\Core\Repository\FieldTypeService as FieldTypeServiceInterface; use Ibexa\Contracts\Core\Repository\LanguageService as LanguageServiceInterface; use Ibexa\Contracts\Core\Repository\LocationService as LocationServiceInterface; -use Ibexa\Contracts\Core\Repository\NameSchema\NameSchemaServiceInterface; use Ibexa\Contracts\Core\Repository\NotificationService as NotificationServiceInterface; use Ibexa\Contracts\Core\Repository\ObjectStateService as ObjectStateServiceInterface; use Ibexa\Contracts\Core\Repository\PermissionResolver as PermissionResolverInterface; @@ -28,7 +27,6 @@ use Ibexa\Contracts\Core\Repository\URLWildcardService as URLWildcardServiceInterface; use Ibexa\Contracts\Core\Repository\UserPreferenceService as UserPreferenceServiceInterface; use Ibexa\Contracts\Core\Repository\UserService as UserServiceInterface; -use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; final class Repository implements RepositoryInterface { @@ -86,10 +84,6 @@ final class Repository implements RepositoryInterface /** @var \Ibexa\Contracts\Core\Repository\UserService */ private $userService; - private EventDispatcherInterface $eventDispatcher; - - private NameSchemaServiceInterface $nameSchemaService; - public function __construct( RepositoryInterface $repository, BookmarkServiceInterface $bookmarkService, @@ -108,9 +102,7 @@ public function __construct( URLServiceInterface $urlService, URLWildcardServiceInterface $urlWildcardService, UserPreferenceServiceInterface $userPreferenceService, - UserServiceInterface $userService, - EventDispatcherInterface $eventDispatcher, - NameSchemaServiceInterface $nameSchemaService + UserServiceInterface $userService ) { $this->repository = $repository; $this->bookmarkService = $bookmarkService; @@ -130,8 +122,6 @@ public function __construct( $this->urlWildcardService = $urlWildcardService; $this->userPreferenceService = $userPreferenceService; $this->userService = $userService; - $this->eventDispatcher = $eventDispatcher; - $this->nameSchemaService = $nameSchemaService; } public function sudo(callable $callback, ?RepositoryInterface $outerRepository = null) diff --git a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php index dd3c1e203c..89bbfc8615 100644 --- a/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php +++ b/src/lib/Repository/EventSubscriber/NameSchemaSubscriber.php @@ -12,6 +12,9 @@ use Ibexa\Core\FieldType\FieldTypeRegistry; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +/** + * @internal + */ final class NameSchemaSubscriber implements EventSubscriberInterface { private FieldTypeRegistry $fieldTypeRegistry; diff --git a/src/lib/Repository/LocationService.php b/src/lib/Repository/LocationService.php index 89eb4b1964..6c71114051 100644 --- a/src/lib/Repository/LocationService.php +++ b/src/lib/Repository/LocationService.php @@ -67,8 +67,7 @@ class LocationService implements LocationServiceInterface /** @var \Ibexa\Core\Repository\Mapper\ContentDomainMapper */ protected $contentDomainMapper; - /** @var \Ibexa\Core\Repository\Helper\NameSchemaService */ - protected $nameSchemaService; + protected NameSchemaServiceInterface $nameSchemaService; /** @var \Ibexa\Contracts\Core\Repository\PermissionCriterionResolver */ protected $permissionCriterionResolver; From ac18a715bb58fded0ead2956007db3cefb7ffb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Fri, 14 Jul 2023 09:17:51 +0200 Subject: [PATCH 36/37] Fixed an issue with grouping --- .../Repository/NameSchema/SchemaIdentifierExtractor.php | 6 ++++-- .../NameSchema/SchemaIdentifierExtractorTest.php | 9 +++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php b/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php index 803c6c814d..cb7d76c51f 100644 --- a/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php +++ b/src/lib/Repository/NameSchema/SchemaIdentifierExtractor.php @@ -27,7 +27,7 @@ final class SchemaIdentifierExtractor implements SchemaIdentifierExtractorInterf */ public function extract(string $schemaString): array { - $allTokens = '/<([^>]+)>/'; + $allTokens = '/<([^>-]+)>/'; if (false === preg_match_all($allTokens, $schemaString, $matches)) { return []; @@ -36,7 +36,6 @@ public function extract(string $schemaString): array $strategyIdentifiers = []; foreach ($matches[1] as $tokenExpression) { $tokens = explode('|', $tokenExpression); - foreach ($tokens as $token) { $strategyToken = explode(':', $token, 2); @@ -47,8 +46,11 @@ public function extract(string $schemaString): array $strategy = 'field'; } + $token = preg_replace('/[()<>\[\]]/', '', $token); $strategyIdentifiers[$strategy][] = $token; } + + $strategyIdentifiers[$strategy] = array_unique($strategyIdentifiers[$strategy]); } return $strategyIdentifiers; diff --git a/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php index 76a4d2dc97..bf60625f60 100644 --- a/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php +++ b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php @@ -67,6 +67,15 @@ public function getDataForTestExtract(): iterable 'field' => ['bar', 'baz'], ], ]; + + $schemaString = ' )--'; + yield $schemaString => [ + $schemaString, + [ + 'field' => ['specification', 'name', 'image1', 'baz', 'bar'], + 'custom' => ['bar'], + ], + ]; } protected function setUp(): void From 96020b17d198f10b0e2157babc15a8ce535597e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20S=C5=82omka?= Date: Fri, 14 Jul 2023 13:59:14 +0200 Subject: [PATCH 37/37] Fixed issue with grouping --- .../NameSchema/NameSchemaService.php | 20 ++++++++++--------- .../SchemaIdentifierExtractorTest.php | 9 +++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/lib/Repository/NameSchema/NameSchemaService.php b/src/lib/Repository/NameSchema/NameSchemaService.php index 8554f24f1a..2e02747ffe 100644 --- a/src/lib/Repository/NameSchema/NameSchemaService.php +++ b/src/lib/Repository/NameSchema/NameSchemaService.php @@ -83,7 +83,9 @@ public function resolveUrlAliasSchema(Content $content, ContentType $contentType { $contentType ??= $content->getContentType(); $schemaName = $contentType->urlAliasSchema ?: $contentType->nameSchema; + [$filteredNameSchema, $groupLookupTable] = $this->filterNameSchema($schemaName); $schemaIdentifiers = $this->schemaIdentifierExtractor->extract($schemaName); + $tokens = $this->extractTokens($filteredNameSchema); /** @var \Ibexa\Contracts\Core\Event\ResolveUrlAliasSchemaEvent $event */ $event = $this->eventDispatcher->dispatch( @@ -92,17 +94,17 @@ public function resolveUrlAliasSchema(Content $content, ContentType $contentType $content ) ); - $names = []; - $tokens = $event->getTokenValues(); - $extractedTokens = $this->extractTokens($schemaName); - foreach ($tokens as $languageCode => $tokenValues) { - $schema = $schemaName; - foreach ($extractedTokens as $extractedToken) { - $name = $this->resolveToken($extractedToken, $tokenValues, []); - $schema = str_replace($extractedToken, $name, $schema); + $tokenValues = $event->getTokenValues(); + foreach ($tokenValues as $languageCode => $tokenValue) { + $name = $filteredNameSchema; + foreach ($tokens as $token) { + $string = $this->resolveToken($token, $tokenValue, $groupLookupTable); + $name = str_replace($token, $string, $name); } - $names[$languageCode] = $schema; + $name = $this->validateNameLength($name); + + $names[$languageCode] = $name; } return $names; diff --git a/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php index bf60625f60..e40378ba0e 100644 --- a/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php +++ b/tests/lib/Repository/NameSchema/SchemaIdentifierExtractorTest.php @@ -76,6 +76,15 @@ public function getDataForTestExtract(): iterable 'custom' => ['bar'], ], ]; + + $schemaString = ' )-()-'; + yield $schemaString => [ + $schemaString, + [ + 'field' => ['specification', 'name', 'image1', 'baz', 'bar'], + 'custom' => ['bar'], + ], + ]; } protected function setUp(): void