Passed
Push — trunk ( abfe56...9decf0 )
by Christian
10:31 queued 13s
created

AbstractCmsElementResolver   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 200
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 100
c 1
b 0
f 0
dl 0
loc 200
rs 9.44
wmc 37

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getKeyByOneToMany() 0 16 2
A getKeyByManyToMany() 0 15 2
A resolveDefinitionField() 0 26 5
A resolveCriteriaForLazyLoadedRelations() 0 33 6
C resolveEntityValue() 0 58 14
A resolveEntityValues() 0 16 2
A resolveEntityValueToString() 0 18 6
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Core\Content\Cms\DataResolver\Element;
4
5
use Shopware\Core\Content\Cms\DataResolver\FieldConfig;
6
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\EntityResolverContext;
7
use Shopware\Core\Framework\DataAbstractionLayer\Entity;
8
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
9
use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
10
use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
11
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToManyAssociationField;
12
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
13
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField;
14
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
15
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
16
use Shopware\Core\Framework\Log\Package;
17
use Shopware\Core\Framework\Struct\Struct;
18
19
#[Package('content')]
20
abstract class AbstractCmsElementResolver implements CmsElementResolverInterface
21
{
22
    /**
23
     * @return mixed|Entity|Struct|null
24
     */
25
    protected function resolveEntityValue(?Entity $entity, string $path)
26
    {
27
        if ($entity === null) {
28
            return null;
29
        }
30
31
        $value = $entity;
32
        $parts = explode('.', $path);
33
34
        // if property does not exist, try to omit the first key as it may contains the entity name.
35
        // E.g. `product.description` does not exist, but will be found if the first part is omitted.
36
        $smartDetect = true;
37
38
        while (\count($parts) > 0) {
39
            $part = array_shift($parts);
40
41
            if ($value === null) {
42
                break;
43
            }
44
45
            try {
46
                switch (true) {
47
                    case \is_array($value):
0 ignored issues
show
Unused Code introduced by
is_array($value) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
48
                        $value = \array_key_exists($part, $value) ? $value[$part] : null;
49
50
                        break;
51
                    case $value instanceof Entity:
52
                        $value = $value->get($part);
53
54
                        break;
55
                    case $value instanceof Struct:
56
                        $value = $value->getVars();
57
                        $value = \array_key_exists($part, $value) ? $value[$part] : null;
58
59
                        break;
60
                    default:
61
                        $value = null;
62
                }
63
64
                // if we are at the destination entity and it does not have a value for the field
65
                // on it's on, then try to get the translation fallback
66
                if ($value === null) {
67
                    $value = $entity->getTranslation($part);
68
                }
69
            } catch (\InvalidArgumentException $ex) {
70
                if (!$smartDetect) {
71
                    throw $ex;
72
                }
73
            }
74
75
            if ($value === null && !$smartDetect) {
76
                break;
77
            }
78
79
            $smartDetect = false;
80
        }
81
82
        return $value;
83
    }
84
85
    protected function resolveEntityValueToString(?Entity $entity, string $path, EntityResolverContext $resolverContext): string
86
    {
87
        $content = $this->resolveEntityValue($entity, $path);
88
89
        if ($content instanceof \DateTimeInterface) {
90
            $dateFormatter = new \IntlDateFormatter(
91
                $resolverContext->getRequest()->getLocale(),
92
                \IntlDateFormatter::MEDIUM,
93
                \IntlDateFormatter::MEDIUM
94
            );
95
            $content = $dateFormatter->format($content);
96
        }
97
98
        if ($content === null || \is_scalar($content) || (\is_object($content) && \method_exists($content, '__toString'))) {
99
            return (string) $content;
100
        }
101
102
        return $path;
103
    }
104
105
    protected function resolveDefinitionField(EntityDefinition $definition, string $path): ?Field
106
    {
107
        $value = null;
108
        $parts = explode('.', $path);
109
        $fields = $definition->getFields();
110
111
        // if property does not exist, try to omit the first key as it may contains the entity name.
112
        // E.g. `product.description` does not exist, but will be found if the first part is omitted.
113
        $smartDetect = true;
114
115
        while (\count($parts) > 0) {
116
            $part = array_shift($parts);
117
            $value = $fields->get($part);
118
119
            if ($value === null && !$smartDetect) {
120
                break;
121
            }
122
123
            $smartDetect = false;
124
125
            if ($value instanceof AssociationField) {
126
                $fields = $value->getReferenceDefinition()->getFields();
127
            }
128
        }
129
130
        return $value;
131
    }
132
133
    protected function resolveCriteriaForLazyLoadedRelations(
134
        EntityResolverContext $resolverContext,
135
        FieldConfig $config
136
    ): ?Criteria {
137
        $field = $this->resolveDefinitionField($resolverContext->getDefinition(), $config->getStringValue());
138
        if ($field === null) {
139
            return null;
140
        }
141
142
        $key = null;
143
        $refDef = null;
144
145
        // resolve reverse side to fetch data afterwards
146
        if ($field instanceof ManyToManyAssociationField) {
147
            $key = $this->getKeyByManyToMany($field);
148
            $refDef = $field->getToManyReferenceDefinition();
149
        } elseif ($field instanceof OneToManyAssociationField) {
150
            $key = $this->getKeyByOneToMany($field);
151
            $refDef = $field->getReferenceDefinition();
152
        }
153
154
        if (!$key || !$refDef) {
155
            return null;
156
        }
157
158
        $key = $refDef->getEntityName() . '.' . $key;
159
160
        $criteria = new Criteria();
161
        $criteria->addFilter(
162
            new EqualsFilter($key, $resolverContext->getEntity()->getUniqueIdentifier())
163
        );
164
165
        return $criteria;
166
    }
167
168
    protected function resolveEntityValues(EntityResolverContext $resolverContext, string $content): ?string
169
    {
170
        // https://regex101.com/r/idIfbk/1
171
        $content = preg_replace_callback(
172
            '/{{\s*(?<property>[\w.\d]+)\s*}}/',
173
            function ($matches) use ($resolverContext) {
174
                try {
175
                    return $this->resolveEntityValueToString($resolverContext->getEntity(), $matches['property'], $resolverContext);
176
                } catch (\InvalidArgumentException) {
177
                    return $matches[0];
178
                }
179
            },
180
            $content
181
        );
182
183
        return $content;
184
    }
185
186
    private function getKeyByManyToMany(ManyToManyAssociationField $field): ?string
187
    {
188
        $referenceDefinition = $field->getReferenceDefinition();
189
190
        /** @var ManyToManyAssociationField|null $manyToMany */
191
        $manyToMany = $field->getToManyReferenceDefinition()->getFields()
192
            ->filterInstance(ManyToManyAssociationField::class)
193
            ->filter(static fn (ManyToManyAssociationField $field) => $field->getReferenceDefinition() === $referenceDefinition)
194
            ->first();
195
196
        if (!$manyToMany) {
197
            return null;
198
        }
199
200
        return $manyToMany->getPropertyName() . '.' . $manyToMany->getReferenceField();
201
    }
202
203
    private function getKeyByOneToMany(OneToManyAssociationField $field): ?string
204
    {
205
        $referenceDefinition = $field->getReferenceDefinition();
206
207
        /** @var ManyToOneAssociationField|null $manyToOne */
208
        $manyToOne = $field->getReferenceDefinition()->getFields()
209
            ->filterInstance(ManyToOneAssociationField::class)
210
            ->filter(static fn (ManyToOneAssociationField $field) => $field->getReferenceDefinition() === $referenceDefinition)
211
            ->first()
212
        ;
213
214
        if (!$manyToOne) {
215
            return null;
216
        }
217
218
        return $manyToOne->getPropertyName() . '.' . $manyToOne->getReferenceField();
219
    }
220
}
221