Passed
Push — trunk ( 6d115e...2fe92c )
by Christian
11:00 queued 15s
created

findMediaIdsWithEntityMultiSelect()   A

Complexity

Conditions 5
Paths 9

Size

Total Lines 35
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 20
nc 9
nop 1
dl 0
loc 35
rs 9.2888
c 1
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Content\Media\Subscriber;
4
5
use Doctrine\DBAL\ArrayParameterType;
6
use Doctrine\DBAL\Connection;
7
use Shopware\Core\Content\Media\Event\UnusedMediaSearchEvent;
8
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
9
use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
10
use Shopware\Core\Framework\Log\Package;
11
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
12
13
/**
14
 * @internal
15
 *
16
 *  * @codeCoverageIgnore This would be useless as a unit test. It is integration tested here: \Shopware\Tests\Integration\Core\Content\Media\Subscriber\CustomFieldsUnusedMediaSubscriberTest
17
 */
18
#[Package('core')]
19
class CustomFieldsUnusedMediaSubscriber implements EventSubscriberInterface
20
{
21
    public function __construct(
22
        private Connection $connection,
23
        private DefinitionInstanceRegistry $definitionRegistry
24
    ) {
25
    }
26
27
    public static function getSubscribedEvents(): array
28
    {
29
        return [
30
            UnusedMediaSearchEvent::class => 'removeUsedMedia',
31
        ];
32
    }
33
34
    public function removeUsedMedia(UnusedMediaSearchEvent $event): void
35
    {
36
        $this->findMediaIds($event);
37
        $this->findMediaIdsWithEntitySelect($event);
38
        $this->findMediaIdsWithEntityMultiSelect($event);
39
    }
40
41
    private function findMediaIds(UnusedMediaSearchEvent $event): void
42
    {
43
        /** @var list<array{id: string, name: string, entity_name: string}> $customMediaFields */
44
        $customMediaFields = $this->connection->fetchAllAssociative(
45
            <<<SQL
46
            SELECT f.id, f.name, fsr.entity_name
47
            FROM custom_field f
48
            INNER JOIN custom_field_set fs ON (f.set_id = fs.id)
49
            INNER JOIN custom_field_set_relation fsr ON (fs.id = fsr.set_id)
50
            WHERE JSON_UNQUOTE(JSON_EXTRACT(f.config, '$.customFieldType')) = 'media'
51
            SQL
52
        );
53
54
        $fieldsPerEntity = $this->groupFieldsPerEntity($customMediaFields);
0 ignored issues
show
Bug introduced by
$customMediaFields of type Shopware\Core\Content\Media\Subscriber\list is incompatible with the type array expected by parameter $customMediaFields of Shopware\Core\Content\Me...:groupFieldsPerEntity(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

54
        $fieldsPerEntity = $this->groupFieldsPerEntity(/** @scrutinizer ignore-type */ $customMediaFields);
Loading history...
55
56
        $statements = [];
57
        foreach ($fieldsPerEntity as $entity => $fields) {
58
            $table = $this->getTableName((string) $entity);
59
60
            foreach ($fields as $field) {
61
                $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 (?)";
62
            }
63
        }
64
65
        if (\count($statements) === 0) {
66
            return;
67
        }
68
69
        foreach ($statements as $statement) {
70
            $usedMediaIds = $this->connection->fetchFirstColumn(
71
                $statement,
72
                [$event->getUnusedIds()],
73
                [ArrayParameterType::STRING]
74
            );
75
76
            $event->markAsUsed($usedMediaIds);
77
        }
78
    }
79
80
    /**
81
     * @return list<array{id: string, name: string, entity_name: string}>
0 ignored issues
show
Bug introduced by
The type Shopware\Core\Content\Media\Subscriber\list was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
82
     */
83
    private function findCustomFieldsWithEntitySelect(string $componentType): array
84
    {
85
        /** @var list<array{id: string, name: string, entity_name: string}> $results */
86
        $results = $this->connection->fetchAllAssociative(
87
            <<<SQL
88
            SELECT f.id, f.name, fsr.entity_name
89
            FROM custom_field f
90
            INNER JOIN custom_field_set fs ON (f.set_id = fs.id)
91
            INNER JOIN custom_field_set_relation fsr ON (fs.id = fsr.set_id)
92
            WHERE f.type = 'select' AND JSON_UNQUOTE(JSON_EXTRACT(f.config, '$.entity')) = 'media' AND JSON_UNQUOTE(JSON_EXTRACT(f.config, '$.componentName')) = '{$componentType}'
93
            SQL
94
        );
95
96
        return $results;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $results returns the type Shopware\Core\Content\Media\Subscriber\list which is incompatible with the type-hinted return array.
Loading history...
97
    }
98
99
    private function findMediaIdsWithEntitySelect(UnusedMediaSearchEvent $event): void
100
    {
101
        $fieldsPerEntity = $this->groupFieldsPerEntity(
102
            $this->findCustomFieldsWithEntitySelect('sw-entity-single-select')
103
        );
104
105
        $statements = [];
106
        foreach ($fieldsPerEntity as $entity => $fields) {
107
            $table = $this->getTableName((string) $entity);
108
109
            foreach ($fields as $field) {
110
                $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 (?)";
111
            }
112
        }
113
114
        if (\count($statements) === 0) {
115
            return;
116
        }
117
118
        foreach ($statements as $statement) {
119
            $usedMediaIds = $this->connection->fetchFirstColumn(
120
                $statement,
121
                [$event->getUnusedIds()],
122
                [ArrayParameterType::STRING]
123
            );
124
125
            $event->markAsUsed($usedMediaIds);
126
        }
127
    }
128
129
    private function findMediaIdsWithEntityMultiSelect(UnusedMediaSearchEvent $event): void
130
    {
131
        $fieldsPerEntity = $this->groupFieldsPerEntity(
132
            $this->findCustomFieldsWithEntitySelect('sw-entity-multi-id-select')
133
        );
134
135
        $statements = [];
136
        foreach ($fieldsPerEntity as $entity => $fields) {
137
            $table = $this->getTableName((string) $entity);
138
139
            foreach ($fields as $field) {
140
                $statements[] = <<<SQL
141
                SELECT JSON_EXTRACT(custom_fields, "$.{$field}") as mediaIds FROM {$table}
142
                WHERE JSON_OVERLAPS(
143
                    JSON_EXTRACT(custom_fields, "$.{$field}"),
144
                    JSON_ARRAY(?)
145
                );
146
                SQL;
147
            }
148
        }
149
150
        if (\count($statements) === 0) {
151
            return;
152
        }
153
154
        foreach ($statements as $statement) {
155
            $usedMediaIds = $this->connection->fetchFirstColumn(
156
                $statement,
157
                [$event->getUnusedIds()],
158
                [ArrayParameterType::STRING]
159
            );
160
161
            $event->markAsUsed(
162
                array_merge(
163
                    ...array_map(fn (string $ids) => json_decode($ids, true, \JSON_THROW_ON_ERROR), $usedMediaIds)
164
                )
165
            );
166
        }
167
    }
168
169
    private function getTableName(string $entity): string
170
    {
171
        $definition = $this->definitionRegistry->getByEntityName($entity);
172
173
        $customFields = $definition->getField('customFields');
174
175
        $table = $definition->getEntityName();
176
177
        if ($customFields instanceof TranslatedField) {
178
            $table = $definition->getTranslationDefinition()?->getEntityName() ?? $table;
179
        }
180
181
        return $table;
182
    }
183
184
    /**
185
     * @param list<array{id: string, name: string, entity_name: string}> $customMediaFields
186
     *
187
     * @return array<string, array<string>>
188
     */
189
    private function groupFieldsPerEntity(array $customMediaFields): array
190
    {
191
        $fieldsPerEntity = [];
192
        foreach ($customMediaFields as $field) {
193
            if (!isset($fieldsPerEntity[$field['entity_name']])) {
194
                $fieldsPerEntity[$field['entity_name']] = [];
195
            }
196
            $fieldsPerEntity[$field['entity_name']][] = $field['name'];
197
        }
198
199
        return $fieldsPerEntity;
200
    }
201
}
202