vendor/ibexa/product-catalog/src/lib/Event/NameSchemaSubscriber.php line 72

Open in your IDE?
  1. <?php
  2. /**
  3. * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4. * @license For full copyright and license information view LICENSE file distributed with this source code.
  5. */
  6. declare(strict_types=1);
  7. namespace Ibexa\ProductCatalog\Event;
  8. use Exception;
  9. use Ibexa\Contracts\Core\Event\NameSchema\AbstractNameSchemaEvent;
  10. use Ibexa\Contracts\Core\Event\NameSchema\AbstractSchemaEvent;
  11. use Ibexa\Contracts\Core\Event\NameSchema\ResolveContentNameSchemaEvent;
  12. use Ibexa\Contracts\Core\Event\NameSchema\ResolveNameSchemaEvent;
  13. use Ibexa\Contracts\Core\Event\NameSchema\ResolveUrlAliasSchemaEvent;
  14. use Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException;
  15. use Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException;
  16. use Ibexa\Contracts\Core\Repository\Values\Content\Content;
  17. use Ibexa\Contracts\Core\Repository\Values\Content\Language;
  18. use Ibexa\Contracts\ProductCatalog\AttributeDefinitionServiceInterface;
  19. use Ibexa\Contracts\ProductCatalog\NameSchema\NameSchemaStrategyInterface;
  20. use Ibexa\ProductCatalog\FieldType\ProductSpecification\Type;
  21. use Ibexa\ProductCatalog\FieldType\ProductSpecification\Value;
  22. use Psr\Log\LoggerInterface;
  23. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  24. /**
  25. * @internal
  26. */
  27. final class NameSchemaSubscriber implements EventSubscriberInterface
  28. {
  29. public const ATTRIBUTE_KEY = 'attribute';
  30. public const SEPARATOR = ':';
  31. private AttributeDefinitionServiceInterface $definitionService;
  32. private NameSchemaStrategyInterface $nameSchemaStrategy;
  33. private LoggerInterface $logger;
  34. public function __construct(
  35. AttributeDefinitionServiceInterface $definitionService,
  36. NameSchemaStrategyInterface $nameSchemaStrategy,
  37. LoggerInterface $logger
  38. ) {
  39. $this->definitionService = $definitionService;
  40. $this->nameSchemaStrategy = $nameSchemaStrategy;
  41. $this->logger = $logger;
  42. }
  43. public static function getSubscribedEvents(): array
  44. {
  45. return [
  46. ResolveNameSchemaEvent::class => [
  47. 'onResolveNameSchema',
  48. ],
  49. ResolveContentNameSchemaEvent::class => [
  50. 'onResolveContentNameSchema',
  51. ],
  52. ResolveUrlAliasSchemaEvent::class => [
  53. 'onResolveUrlAliasSchema',
  54. ],
  55. ];
  56. }
  57. public function onResolveNameSchema(ResolveNameSchemaEvent $event): void
  58. {
  59. $this->processSchema($event);
  60. }
  61. public function onResolveContentNameSchema(ResolveContentNameSchemaEvent $event): void
  62. {
  63. $this->processSchema($event);
  64. }
  65. public function onResolveUrlAliasSchema(ResolveUrlAliasSchemaEvent $event): void
  66. {
  67. if (!$this->isEventValid($event)) {
  68. return;
  69. }
  70. $content = $event->getContent();
  71. $productSpecificationField = $this->getProductSpecificationField($content);
  72. if ($productSpecificationField === null) {
  73. return;
  74. }
  75. $event->setTokenValues(
  76. $this->processTokens(
  77. $event->getSchemaIdentifiers()[self::ATTRIBUTE_KEY],
  78. array_map(
  79. static function (Language $language): string {
  80. return $language->getLanguageCode();
  81. },
  82. (array) $content->getVersionInfo()->getLanguages()
  83. ),
  84. $productSpecificationField->getAttributes(),
  85. $event->getTokenValues()
  86. )
  87. );
  88. }
  89. private function processSchema(AbstractNameSchemaEvent $event): void
  90. {
  91. if (!$this->isEventValid($event)) {
  92. return;
  93. }
  94. $contentType = $event->getContentType();
  95. /** @var \Ibexa\Core\Repository\Values\ContentType\FieldDefinition $specificationDefinition */
  96. $specificationDefinition = $contentType->getFirstFieldDefinitionOfType(Type::FIELD_TYPE_IDENTIFIER);
  97. $productSpecificationField = $this->getProductSpecificationFieldFromEvent(
  98. $event,
  99. $specificationDefinition->identifier
  100. );
  101. if ($productSpecificationField === null) {
  102. return;
  103. }
  104. $tokenValues = $this->processTokens(
  105. $event->getSchemaIdentifiers()[self::ATTRIBUTE_KEY],
  106. $event->getLanguageCodes(),
  107. $productSpecificationField->getAttributes(),
  108. $event->getTokenValues()
  109. );
  110. $tokenValues = array_merge($event->getTokenValues(), $tokenValues);
  111. $event->setTokenValues($tokenValues);
  112. }
  113. private function getProductSpecificationFieldFromEvent(AbstractNameSchemaEvent $event, string $identifier): ?Value
  114. {
  115. /** @var array<\Ibexa\ProductCatalog\FieldType\ProductSpecification\Value> $productSpecificationField */
  116. $productSpecificationField = $event->getFieldMap()[$identifier] ?? [];
  117. return reset($productSpecificationField) ?: null;
  118. }
  119. /**
  120. * @param array<int, mixed> $attributes
  121. * @param array<string, mixed> $tokenValues
  122. */
  123. private function processToken(string $token, string $languageCode, array $attributes, array &$tokenValues): void
  124. {
  125. try {
  126. $attributeDefinition = $this->definitionService->getAttributeDefinition($token);
  127. } catch (NotFoundException|UnauthorizedException $e) {
  128. $this->handleAttributeDefinitionException($e, $token, $languageCode, $tokenValues);
  129. return;
  130. }
  131. $attributeId = $attributeDefinition->getId();
  132. if (isset($attributes[$attributeId])) {
  133. $key = self::ATTRIBUTE_KEY . self::SEPARATOR . $token;
  134. $value = $this->nameSchemaStrategy->resolve(
  135. $attributeDefinition,
  136. $attributes[$attributeId],
  137. $languageCode
  138. );
  139. $tokenValues[$languageCode][$key] = $value;
  140. } else {
  141. $this->logger->warning(
  142. 'Attribute value does not exist for given attribute definition id',
  143. [
  144. 'attributes' => $attributes,
  145. 'attribute_definition_id' => $attributeDefinition->getIdentifier(),
  146. ]
  147. );
  148. }
  149. }
  150. /**
  151. * @param array<string, mixed> $tokenValues
  152. */
  153. private function handleAttributeDefinitionException(
  154. Exception $exception,
  155. string $token,
  156. string $languageCode,
  157. array &$tokenValues
  158. ): void {
  159. $this->logger->warning('Attribute definition does not exist for the given identifier', [
  160. 'exception' => $exception,
  161. ]);
  162. $key = self::ATTRIBUTE_KEY . $token;
  163. $tokenValues[$languageCode][$key] = $token;
  164. }
  165. private function getProductSpecificationField(Content $content): ?Value
  166. {
  167. $specificationDefinition = $content->getContentType()
  168. ->getFirstFieldDefinitionOfType(Type::FIELD_TYPE_IDENTIFIER);
  169. if ($specificationDefinition === null) {
  170. return null;
  171. }
  172. /** @var \Ibexa\ProductCatalog\FieldType\ProductSpecification\Value $field */
  173. $field = $content->getFieldValue($specificationDefinition->identifier);
  174. return $field;
  175. }
  176. private function isEventValid(AbstractSchemaEvent $event): bool
  177. {
  178. return array_key_exists(self::ATTRIBUTE_KEY, $event->getSchemaIdentifiers());
  179. }
  180. /**
  181. * @param array<string> $tokens
  182. * @param array<string> $languageCodes
  183. * @param array<int, mixed> $attributes
  184. * @param array<string, array<string>> $tokenValues
  185. *
  186. * @return array<string, array<string>>
  187. */
  188. public function processTokens(
  189. array $tokens,
  190. array $languageCodes,
  191. array $attributes,
  192. array $tokenValues
  193. ): array {
  194. foreach ($languageCodes as $languageCode) {
  195. foreach ($tokens as $token) {
  196. $this->processToken($token, $languageCode, $attributes, $tokenValues);
  197. }
  198. }
  199. return $tokenValues;
  200. }
  201. }