Passed
Push — master ( 527243...f676e6 )
by Christian
13:39 queued 11s
created

AbstractCmsElementResolver::resolveEntityValues()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 1
nop 2
dl 0
loc 16
rs 9.9666
c 0
b 0
f 0
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
17
abstract class AbstractCmsElementResolver implements CmsElementResolverInterface
18
{
19
    protected function resolveEntityValue(?Entity $entity, string $path)
20
    {
21
        if ($entity === null) {
22
            return null;
23
        }
24
25
        $value = $entity;
26
        $parts = explode('.', $path);
27
28
        // if property does not exist, try to omit the first key as it may contains the entity name.
29
        // E.g. `product.description` does not exist, but will be found if the first part is omitted.
30
        $smartDetect = true;
31
32
        while (\count($parts) > 0) {
33
            $part = array_shift($parts);
34
35
            if ($value === null) {
36
                break;
37
            }
38
39
            try {
40
                $value = $value->get($part);
41
42
                // if we are at the destination entity and it does not have a value for the field
43
                // on it's on, then try to get the translation fallback
44
                if ($value === null) {
45
                    $value = $entity->getTranslation((string) $part);
46
                }
47
            } catch (\InvalidArgumentException $ex) {
48
                if (!$smartDetect) {
49
                    throw $ex;
50
                }
51
            }
52
53
            if ($value === null && !$smartDetect) {
54
                break;
55
            }
56
57
            $smartDetect = false;
58
        }
59
60
        return $value;
61
    }
62
63
    protected function resolveDefinitionField(EntityDefinition $definition, string $path): ?Field
64
    {
65
        $value = null;
66
        $parts = explode('.', $path);
67
        $fields = $definition->getFields();
68
69
        // if property does not exist, try to omit the first key as it may contains the entity name.
70
        // E.g. `product.description` does not exist, but will be found if the first part is omitted.
71
        $smartDetect = true;
72
73
        while (\count($parts) > 0) {
74
            $part = array_shift($parts);
75
            $value = $fields->get($part);
76
77
            if ($value === null && !$smartDetect) {
78
                break;
79
            }
80
81
            $smartDetect = false;
82
83
            if ($value instanceof AssociationField) {
84
                $fields = $value->getReferenceDefinition()->getFields();
85
            }
86
        }
87
88
        return $value;
89
    }
90
91
    protected function resolveCriteriaForLazyLoadedRelations(
92
        EntityResolverContext $resolverContext,
93
        FieldConfig $config
94
    ): ?Criteria {
95
        if (!$field = $this->resolveDefinitionField($resolverContext->getDefinition(), $config->getValue())) {
96
            return null;
97
        }
98
99
        $key = null;
100
        $refDef = null;
101
102
        // resolve reverse side to fetch data afterwards
103
        if ($field instanceof ManyToManyAssociationField) {
104
            $key = $this->getKeyByManyToMany($field);
105
            $refDef = $field->getToManyReferenceDefinition();
106
        } elseif ($field instanceof OneToManyAssociationField) {
107
            $key = $this->getKeyByOneToMany($field);
108
            $refDef = $field->getReferenceDefinition();
109
        }
110
111
        if (!$key || !$refDef) {
112
            return null;
113
        }
114
115
        $key = $refDef->getEntityName() . '.' . $key;
116
117
        $criteria = new Criteria();
118
        $criteria->addFilter(
119
            new EqualsFilter($key, $resolverContext->getEntity()->getUniqueIdentifier())
120
        );
121
122
        return $criteria;
123
    }
124
125
    protected function resolveEntityValues(EntityResolverContext $resolverContext, string $content): ?string
126
    {
127
        // https://regex101.com/r/idIfbk/1
128
        $content = preg_replace_callback(
129
            '/{{\s*(?<property>[\w.\d]+)\s*}}/',
130
            function ($matches) use ($resolverContext) {
131
                try {
132
                    return $this->resolveEntityValue($resolverContext->getEntity(), $matches['property']);
133
                } catch (\InvalidArgumentException $e) {
134
                    return $matches[0];
135
                }
136
            },
137
            $content
138
        );
139
140
        return $content;
141
    }
142
143
    private function getKeyByManyToMany(ManyToManyAssociationField $field): ?string
144
    {
145
        $referenceDefinition = $field->getReferenceDefinition();
146
147
        /** @var ManyToManyAssociationField|null $manyToMany */
148
        $manyToMany = $field->getToManyReferenceDefinition()->getFields()
149
            ->filterInstance(ManyToManyAssociationField::class)
150
            ->filter(static function (ManyToManyAssociationField $field) use ($referenceDefinition) {
151
                return $field->getReferenceDefinition() === $referenceDefinition;
152
            })
153
            ->first();
154
155
        if (!$manyToMany) {
0 ignored issues
show
introduced by
$manyToMany is of type Shopware\Core\Framework\...yToManyAssociationField, thus it always evaluated to true.
Loading history...
156
            return null;
157
        }
158
159
        return $manyToMany->getPropertyName() . '.' . $manyToMany->getReferenceField();
160
    }
161
162
    private function getKeyByOneToMany(OneToManyAssociationField $field): ?string
163
    {
164
        $referenceDefinition = $field->getReferenceDefinition();
165
166
        /** @var ManyToOneAssociationField|null $manyToOne */
167
        $manyToOne = $field->getReferenceDefinition()->getFields()
168
            ->filterInstance(ManyToOneAssociationField::class)
169
            ->filter(static function (ManyToOneAssociationField $field) use ($referenceDefinition) {
170
                return $field->getReferenceDefinition() === $referenceDefinition;
171
            })
172
            ->first()
173
        ;
174
175
        if (!$manyToOne) {
0 ignored issues
show
introduced by
$manyToOne is of type Shopware\Core\Framework\...nyToOneAssociationField, thus it always evaluated to true.
Loading history...
176
            return null;
177
        }
178
179
        return $manyToOne->getPropertyName() . '.' . $manyToOne->getReferenceField();
180
    }
181
}
182