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): |
|
|
|
|
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
|
|
|
|
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
orexit
statements that have been added for debug purposes.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.