diff --git a/src/bundle/Core/ApiLoader/RepositoryFactory.php b/src/bundle/Core/ApiLoader/RepositoryFactory.php index ebbccf16f8..45df9b0283 100644 --- a/src/bundle/Core/ApiLoader/RepositoryFactory.php +++ b/src/bundle/Core/ApiLoader/RepositoryFactory.php @@ -90,7 +90,8 @@ public function buildRepository( PermissionService $permissionService, ContentFilteringHandler $contentFilteringHandler, LocationFilteringHandler $locationFilteringHandler, - PasswordValidatorInterface $passwordValidator + PasswordValidatorInterface $passwordValidator, + ConfigResolverInterface $configResolver ): Repository { $config = $this->container->get(\Ibexa\Bundle\Core\ApiLoader\RepositoryConfigurationProvider::class)->getRepositoryConfig(); @@ -114,6 +115,7 @@ public function buildRepository( $contentFilteringHandler, $locationFilteringHandler, $passwordValidator, + $configResolver, [ 'role' => [ 'policyMap' => $this->policyMap, diff --git a/src/bundle/Core/DependencyInjection/Configuration/Parser/UserContentTypeIdentifier.php b/src/bundle/Core/DependencyInjection/Configuration/Parser/UserContentTypeIdentifier.php new file mode 100644 index 0000000000..34f043e355 --- /dev/null +++ b/src/bundle/Core/DependencyInjection/Configuration/Parser/UserContentTypeIdentifier.php @@ -0,0 +1,61 @@ + + */ + public function addSemanticConfig(NodeBuilder $nodeBuilder) + { + $nodeBuilder + ->arrayNode('user_content_type_identifier') + ->info('User Content Type identifier configuration.') + ->example(['user', 'my_custom_user_identifier']) + ->requiresAtLeastOneElement() + ->prototype('scalar')->end() + ->end(); + } + + public function mapConfig(array &$scopeSettings, $currentScope, ContextualizerInterface $contextualizer): void + { + if (empty($scopeSettings['user_content_type_identifier'])) { + return; + } + + $contextualizer->setContextualParameter( + 'user_content_type_identifier', + $currentScope, + $scopeSettings['user_content_type_identifier'] + ); + } +} + +class_alias(UserContentTypeIdentifier::class, 'EzSystems\EzPlatformAdminUiBundle\DependencyInjection\Configuration\Parser\UserIdentifier'); +class_alias(UserContentTypeIdentifier::class, 'Ibexa\Bundle\AdminUi\DependencyInjection\Configuration\Parser\UserIdentifier'); diff --git a/src/bundle/Core/IbexaCoreBundle.php b/src/bundle/Core/IbexaCoreBundle.php index 2998947145..e52094cd08 100644 --- a/src/bundle/Core/IbexaCoreBundle.php +++ b/src/bundle/Core/IbexaCoreBundle.php @@ -124,6 +124,7 @@ public function getContainerExtension() new ConfigParser\IO(new ComplexSettingParser()), new ConfigParser\UrlChecker(), new ConfigParser\TwigVariablesParser(), + new ConfigParser\UserContentTypeIdentifier(), ], [ new RepositoryConfigParser\Storage(), diff --git a/src/bundle/Core/Resources/config/default_settings.yml b/src/bundle/Core/Resources/config/default_settings.yml index 8db7dbe7da..4ad50ca8db 100644 --- a/src/bundle/Core/Resources/config/default_settings.yml +++ b/src/bundle/Core/Resources/config/default_settings.yml @@ -95,6 +95,7 @@ parameters: ibexa.site_access.config.default.storage_dir: "storage" # Where to place new files for storage, it's relative to var directory ibexa.site_access.config.default.binary_dir: "original" ibexa.site_access.config.default.anonymous_user_id: 10 # The ID of the user to be used for everyone who is not logged in + ibexa.site_access.config.default.user_content_type_identifier: ['user'] ibexa.site_access.config.default.api_keys: { google_maps: ~ } # Google Maps APIs v3 key (https://developers.google.com/maps/documentation/javascript/get-api-key) # IO diff --git a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php index 963f1027b8..c3703ee72b 100644 --- a/src/lib/Base/Container/ApiLoader/RepositoryFactory.php +++ b/src/lib/Base/Container/ApiLoader/RepositoryFactory.php @@ -16,6 +16,7 @@ use Ibexa\Contracts\Core\Repository\Strategy\ContentThumbnail\ThumbnailStrategy; use Ibexa\Contracts\Core\Repository\Validator\ContentValidator; use Ibexa\Contracts\Core\Search\Handler as SearchHandler; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Base\Exceptions\InvalidArgumentException; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\Helper\RelationProcessor; @@ -81,6 +82,7 @@ public function buildRepository( ContentFilteringHandler $contentFilteringHandler, LocationFilteringHandler $locationFilteringHandler, PasswordValidatorInterface $passwordValidator, + ConfigResolverInterface $configResolver, array $languages ): Repository { return new $this->repositoryClass( @@ -103,6 +105,7 @@ public function buildRepository( $contentFilteringHandler, $locationFilteringHandler, $passwordValidator, + $configResolver, [ 'role' => [ 'policyMap' => $this->policyMap, diff --git a/src/lib/Repository/Repository.php b/src/lib/Repository/Repository.php index a3efae8dae..2df1b5b973 100644 --- a/src/lib/Repository/Repository.php +++ b/src/lib/Repository/Repository.php @@ -38,6 +38,7 @@ use Ibexa\Contracts\Core\Repository\UserService as UserServiceInterface; use Ibexa\Contracts\Core\Repository\Validator\ContentValidator; 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; @@ -263,6 +264,8 @@ class Repository implements RepositoryInterface /** @var \Ibexa\Core\Repository\User\PasswordValidatorInterface */ private $passwordValidator; + private ConfigResolverInterface $configResolver; + public function __construct( PersistenceHandler $persistenceHandler, SearchHandler $searchHandler, @@ -283,6 +286,7 @@ public function __construct( ContentFilteringHandler $contentFilteringHandler, LocationFilteringHandler $locationFilteringHandler, PasswordValidatorInterface $passwordValidator, + ConfigResolverInterface $configResolver, array $serviceSettings = [], ?LoggerInterface $logger = null ) { @@ -332,6 +336,7 @@ public function __construct( $this->contentMapper = $contentMapper; $this->contentValidator = $contentValidator; $this->passwordValidator = $passwordValidator; + $this->configResolver = $configResolver; } /** @@ -523,6 +528,7 @@ public function getUserService(): UserServiceInterface $this->persistenceHandler->locationHandler(), $this->passwordHashService, $this->passwordValidator, + $this->configResolver, $this->serviceSettings['user'] ); diff --git a/src/lib/Repository/UserService.php b/src/lib/Repository/UserService.php index 63a17343a1..6785abda2c 100644 --- a/src/lib/Repository/UserService.php +++ b/src/lib/Repository/UserService.php @@ -26,6 +26,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\Location; use Ibexa\Contracts\Core\Repository\Values\Content\LocationQuery; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\ContentTypeId as CriterionContentTypeId; +use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\ContentTypeIdentifier as CriterionContentTypeIdentifier; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\LocationId as CriterionLocationId; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\LogicalAnd as CriterionLogicalAnd; use Ibexa\Contracts\Core\Repository\Values\Content\Query\Criterion\ParentLocationId as CriterionParentLocationId; @@ -41,6 +42,7 @@ use Ibexa\Contracts\Core\Repository\Values\User\UserGroupUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\User\UserTokenUpdateStruct; use Ibexa\Contracts\Core\Repository\Values\User\UserUpdateStruct; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Base\Exceptions\BadStateException; use Ibexa\Core\Base\Exceptions\ContentFieldValidationException; use Ibexa\Core\Base\Exceptions\InvalidArgumentException; @@ -90,6 +92,8 @@ class UserService implements UserServiceInterface /** @var \Ibexa\Core\Repository\User\PasswordValidatorInterface */ private $passwordValidator; + private ConfigResolverInterface $configResolver; + public function setLogger(LoggerInterface $logger = null) { $this->logger = $logger; @@ -105,6 +109,7 @@ public function __construct( LocationHandler $locationHandler, PasswordHashService $passwordHashGenerator, PasswordValidatorInterface $passwordValidator, + ConfigResolverInterface $configResolver, array $settings = [] ) { $this->repository = $repository; @@ -114,13 +119,14 @@ public function __construct( // Union makes sure default settings are ignored if provided in argument $this->settings = $settings + [ 'defaultUserPlacement' => 12, - 'userClassID' => 4, // @todo Rename this settings to swap out "Class" for "Type" + 'userClassID' => 4, // @deprecated, use `user_content_type_identifier` configuration instead 'userGroupClassID' => 3, 'hashType' => $passwordHashGenerator->getDefaultHashType(), 'siteName' => 'ibexa.co', ]; $this->passwordHashService = $passwordHashGenerator; $this->passwordValidator = $passwordValidator; + $this->configResolver = $configResolver; } /** @@ -1045,7 +1051,7 @@ public function loadUsersOfUserGroup( $searchQuery->filter = new CriterionLogicalAnd( [ - new CriterionContentTypeId($this->settings['userClassID']), + new CriterionContentTypeIdentifier($this->getUserContentTypeIdentifiers()), new CriterionParentLocationId($mainGroupLocation->id), ] ); @@ -1077,7 +1083,11 @@ public function loadUsersOfUserGroup( public function isUser(APIContent $content): bool { // First check against config for fast check - if ($this->settings['userClassID'] == $content->getVersionInfo()->getContentInfo()->contentTypeId) { + if (in_array( + $content->getVersionInfo()->getContentInfo()->getContentType()->identifier, + $this->getUserContentTypeIdentifiers(), + true + )) { return true; } @@ -1114,9 +1124,9 @@ public function isUserGroup(APIContent $content): bool public function newUserCreateStruct(string $login, string $email, string $password, string $mainLanguageCode, ?ContentType $contentType = null): APIUserCreateStruct { if ($contentType === null) { - $contentType = $this->repository->getContentTypeService()->loadContentType( - $this->settings['userClassID'] - ); + $userContentTypeIdentifiers = $this->getUserContentTypeIdentifiers(); + $defaultIdentifier = reset($userContentTypeIdentifiers); + $contentType = $this->repository->getContentTypeService()->loadContentTypeByIdentifier($defaultIdentifier); } $fieldDefIdentifier = ''; @@ -1206,10 +1216,9 @@ public function validatePassword(string $password, PasswordValidationContext $co $errors = []; if ($context === null) { - $contentType = $this->repository->getContentTypeService()->loadContentType( - $this->settings['userClassID'] - ); - + $userContentTypeIdentifiers = $this->getUserContentTypeIdentifiers(); + $defaultIdentifier = reset($userContentTypeIdentifiers); + $contentType = $this->repository->getContentTypeService()->loadContentTypeByIdentifier($defaultIdentifier); $context = new PasswordValidationContext([ 'contentType' => $contentType, ]); @@ -1404,6 +1413,14 @@ private function getDateTime(?int $timestamp): ?DateTimeInterface return null; } + + /** + * @return string[] + */ + private function getUserContentTypeIdentifiers(): array + { + return $this->configResolver->getParameter('user_content_type_identifier'); + } } class_alias(UserService::class, 'eZ\Publish\Core\Repository\UserService'); diff --git a/src/lib/Resources/settings/repository/inner.yml b/src/lib/Resources/settings/repository/inner.yml index a0a0face1c..279cf1f09d 100644 --- a/src/lib/Resources/settings/repository/inner.yml +++ b/src/lib/Resources/settings/repository/inner.yml @@ -33,6 +33,7 @@ services: - '@Ibexa\Contracts\Core\Persistence\Filter\Content\Handler' - '@Ibexa\Contracts\Core\Persistence\Filter\Location\Handler' - '@Ibexa\Core\Repository\User\PasswordValidatorInterface' + - '@ibexa.config.resolver' - '%languages%' Ibexa\Core\Repository\ContentService: diff --git a/tests/integration/Core/Resources/settings/integration_legacy.yml b/tests/integration/Core/Resources/settings/integration_legacy.yml index 1e9fc1ea11..f1cdd2294f 100644 --- a/tests/integration/Core/Resources/settings/integration_legacy.yml +++ b/tests/integration/Core/Resources/settings/integration_legacy.yml @@ -12,6 +12,8 @@ parameters: name_field_identifier: name parent_location_id: 51 + ibexa.site_access.config.default.user_content_type_identifier: ['user'] + services: Ibexa\Core\FieldType\ImageAsset\AssetMapper: arguments: diff --git a/tests/lib/Repository/Service/Mock/Base.php b/tests/lib/Repository/Service/Mock/Base.php index ff284fe6cf..a2df11176d 100644 --- a/tests/lib/Repository/Service/Mock/Base.php +++ b/tests/lib/Repository/Service/Mock/Base.php @@ -16,6 +16,7 @@ use Ibexa\Contracts\Core\Repository\Strategy\ContentThumbnail\ThumbnailStrategy; use Ibexa\Contracts\Core\Repository\Validator\ContentValidator; use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\FieldType\FieldTypeRegistry; use Ibexa\Core\Repository\FieldTypeService; use Ibexa\Core\Repository\Helper\RelationProcessor; @@ -124,6 +125,7 @@ protected function getRepository(array $serviceSettings = []) $this->getContentFilteringHandlerMock(), $this->getLocationFilteringHandlerMock(), $this->createMock(PasswordValidatorInterface::class), + $this->createMock(ConfigResolverInterface::class), $serviceSettings, ); diff --git a/tests/lib/Repository/Service/Mock/UserTest.php b/tests/lib/Repository/Service/Mock/UserTest.php index 4b1f4792d5..6143b47297 100644 --- a/tests/lib/Repository/Service/Mock/UserTest.php +++ b/tests/lib/Repository/Service/Mock/UserTest.php @@ -11,6 +11,7 @@ use Ibexa\Contracts\Core\Repository\Values\Content\ContentInfo as APIContentInfo; use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo as APIVersionInfo; use Ibexa\Contracts\Core\Repository\Values\User\User as APIUser; +use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface; use Ibexa\Core\Repository\User\PasswordValidatorInterface; use Ibexa\Core\Repository\UserService; use Ibexa\Tests\Core\Repository\Service\Mock\Base as BaseServiceMockTest; @@ -155,6 +156,7 @@ protected function getPartlyMockedUserService(array $methods = null) $this->getPersistenceMock()->locationHandler(), $this->createMock(PasswordHashService::class), $this->createMock(PasswordValidatorInterface::class), + $this->createMock(ConfigResolverInterface::class), ] ) ->getMock();