vendor/shopware/core/Content/Media/Subscriber/CustomFieldsUnusedMediaSubscriber.php line 34

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Content\Media\Subscriber;
  3. use Doctrine\DBAL\ArrayParameterType;
  4. use Doctrine\DBAL\Connection;
  5. use Shopware\Core\Content\Media\Event\UnusedMediaSearchEvent;
  6. use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
  7. use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
  8. use Shopware\Core\Framework\Log\Package;
  9. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  10. /**
  11.  * @internal
  12.  *
  13.  *  * @codeCoverageIgnore This would be useless as a unit test. It is integration tested here: \Shopware\Tests\Integration\Core\Content\Media\Subscriber\CustomFieldsUnusedMediaSubscriberTest
  14.  */
  15. #[Package('core')]
  16. class CustomFieldsUnusedMediaSubscriber implements EventSubscriberInterface
  17. {
  18.     public function __construct(
  19.         private Connection $connection,
  20.         private DefinitionInstanceRegistry $definitionRegistry
  21.     ) {
  22.     }
  23.     public static function getSubscribedEvents(): array
  24.     {
  25.         return [
  26.             UnusedMediaSearchEvent::class => 'removeUsedMedia',
  27.         ];
  28.     }
  29.     public function removeUsedMedia(UnusedMediaSearchEvent $event): void
  30.     {
  31.         $this->findMediaIds($event);
  32.         $this->findMediaIdsWithEntitySelect($event);
  33.         $this->findMediaIdsWithEntityMultiSelect($event);
  34.     }
  35.     private function findMediaIds(UnusedMediaSearchEvent $event): void
  36.     {
  37.         /** @var list<array{id: string, name: string, entity_name: string}> $customMediaFields */
  38.         $customMediaFields $this->connection->fetchAllAssociative(
  39.             <<<SQL
  40.             SELECT f.id, f.name, fsr.entity_name
  41.             FROM custom_field f
  42.             INNER JOIN custom_field_set fs ON (f.set_id = fs.id)
  43.             INNER JOIN custom_field_set_relation fsr ON (fs.id = fsr.set_id)
  44.             WHERE JSON_UNQUOTE(JSON_EXTRACT(f.config, '$.customFieldType')) = 'media'
  45.             SQL
  46.         );
  47.         $fieldsPerEntity $this->groupFieldsPerEntity($customMediaFields);
  48.         $statements = [];
  49.         foreach ($fieldsPerEntity as $entity => $fields) {
  50.             $table $this->getTableName((string) $entity);
  51.             foreach ($fields as $field) {
  52.                 $statements[] = "SELECT JSON_UNQUOTE(JSON_EXTRACT({$table}.custom_fields, '$.{$field}')) as media_id FROM {$table} WHERE JSON_UNQUOTE(JSON_EXTRACT({$table}.custom_fields, '$.{$field}')) IN (?)";
  53.             }
  54.         }
  55.         if (\count($statements) === 0) {
  56.             return;
  57.         }
  58.         foreach ($statements as $statement) {
  59.             $usedMediaIds $this->connection->fetchFirstColumn(
  60.                 $statement,
  61.                 [$event->getUnusedIds()],
  62.                 [ArrayParameterType::STRING]
  63.             );
  64.             $event->markAsUsed($usedMediaIds);
  65.         }
  66.     }
  67.     /**
  68.      * @return list<array{id: string, name: string, entity_name: string}>
  69.      */
  70.     private function findCustomFieldsWithEntitySelect(string $componentType): array
  71.     {
  72.         /** @var list<array{id: string, name: string, entity_name: string}> $results */
  73.         $results $this->connection->fetchAllAssociative(
  74.             <<<SQL
  75.             SELECT f.id, f.name, fsr.entity_name
  76.             FROM custom_field f
  77.             INNER JOIN custom_field_set fs ON (f.set_id = fs.id)
  78.             INNER JOIN custom_field_set_relation fsr ON (fs.id = fsr.set_id)
  79.             WHERE f.type = 'select' AND JSON_UNQUOTE(JSON_EXTRACT(f.config, '$.entity')) = 'media' AND JSON_UNQUOTE(JSON_EXTRACT(f.config, '$.componentName')) = '{$componentType}'
  80.             SQL
  81.         );
  82.         return $results;
  83.     }
  84.     private function findMediaIdsWithEntitySelect(UnusedMediaSearchEvent $event): void
  85.     {
  86.         $fieldsPerEntity $this->groupFieldsPerEntity(
  87.             $this->findCustomFieldsWithEntitySelect('sw-entity-single-select')
  88.         );
  89.         $statements = [];
  90.         foreach ($fieldsPerEntity as $entity => $fields) {
  91.             $table $this->getTableName((string) $entity);
  92.             foreach ($fields as $field) {
  93.                 $statements[] = "SELECT JSON_UNQUOTE(JSON_EXTRACT({$table}.custom_fields, '$.{$field}')) as media_id FROM {$table} WHERE JSON_UNQUOTE(JSON_EXTRACT({$table}.custom_fields, '$.{$field}')) IN (?)";
  94.             }
  95.         }
  96.         if (\count($statements) === 0) {
  97.             return;
  98.         }
  99.         foreach ($statements as $statement) {
  100.             $usedMediaIds $this->connection->fetchFirstColumn(
  101.                 $statement,
  102.                 [$event->getUnusedIds()],
  103.                 [ArrayParameterType::STRING]
  104.             );
  105.             $event->markAsUsed($usedMediaIds);
  106.         }
  107.     }
  108.     private function findMediaIdsWithEntityMultiSelect(UnusedMediaSearchEvent $event): void
  109.     {
  110.         $fieldsPerEntity $this->groupFieldsPerEntity(
  111.             $this->findCustomFieldsWithEntitySelect('sw-entity-multi-id-select')
  112.         );
  113.         $statements = [];
  114.         foreach ($fieldsPerEntity as $entity => $fields) {
  115.             $table $this->getTableName((string) $entity);
  116.             foreach ($fields as $field) {
  117.                 $statements[] = <<<SQL
  118.                 SELECT JSON_EXTRACT(custom_fields, "$.{$field}") as mediaIds FROM {$table}
  119.                 WHERE JSON_OVERLAPS(
  120.                     JSON_EXTRACT(custom_fields, "$.{$field}"),
  121.                     JSON_ARRAY(?)
  122.                 );
  123.                 SQL;
  124.             }
  125.         }
  126.         if (\count($statements) === 0) {
  127.             return;
  128.         }
  129.         foreach ($statements as $statement) {
  130.             $usedMediaIds $this->connection->fetchFirstColumn(
  131.                 $statement,
  132.                 [$event->getUnusedIds()],
  133.                 [ArrayParameterType::STRING]
  134.             );
  135.             $event->markAsUsed(
  136.                 array_merge(
  137.                     ...array_map(fn (string $ids) => json_decode($idstrue\JSON_THROW_ON_ERROR), $usedMediaIds)
  138.                 )
  139.             );
  140.         }
  141.     }
  142.     private function getTableName(string $entity): string
  143.     {
  144.         $definition $this->definitionRegistry->getByEntityName($entity);
  145.         $customFields $definition->getField('customFields');
  146.         $table $definition->getEntityName();
  147.         if ($customFields instanceof TranslatedField) {
  148.             $table $definition->getTranslationDefinition()?->getEntityName() ?? $table;
  149.         }
  150.         return $table;
  151.     }
  152.     /**
  153.      * @param list<array{id: string, name: string, entity_name: string}> $customMediaFields
  154.      *
  155.      * @return array<string, array<string>>
  156.      */
  157.     private function groupFieldsPerEntity(array $customMediaFields): array
  158.     {
  159.         $fieldsPerEntity = [];
  160.         foreach ($customMediaFields as $field) {
  161.             if (!isset($fieldsPerEntity[$field['entity_name']])) {
  162.                 $fieldsPerEntity[$field['entity_name']] = [];
  163.             }
  164.             $fieldsPerEntity[$field['entity_name']][] = $field['name'];
  165.         }
  166.         return $fieldsPerEntity;
  167.     }
  168. }