Passed
Push — trunk ( 87d13d...17ce15 )
by Christian
14:38 queued 13s
created

AdministrationController::getCustomerByEmail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 4
dl 0
loc 24
rs 9.7998
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Administration\Controller;
4
5
use Doctrine\DBAL\Connection;
6
use Shopware\Administration\Events\PreResetExcludedSearchTermEvent;
7
use Shopware\Administration\Framework\Routing\KnownIps\KnownIpsCollectorInterface;
8
use Shopware\Administration\Snippet\SnippetFinderInterface;
9
use Shopware\Core\Checkout\Customer\CustomerEntity;
0 ignored issues
show
Bug introduced by
The type Shopware\Core\Checkout\Customer\CustomerEntity 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...
10
use Shopware\Core\Defaults;
11
use Shopware\Core\DevOps\Environment\EnvironmentHelper;
12
use Shopware\Core\Framework\Adapter\Twig\TemplateFinder;
13
use Shopware\Core\Framework\Context;
14
use Shopware\Core\Framework\DataAbstractionLayer\DefinitionInstanceRegistry;
15
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
16
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\AllowHtml;
17
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
18
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
19
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
20
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
21
use Shopware\Core\Framework\Feature;
22
use Shopware\Core\Framework\Log\Package;
23
use Shopware\Core\Framework\Routing\Exception\InvalidRequestParameterException;
24
use Shopware\Core\Framework\Routing\Exception\LanguageNotFoundException;
25
use Shopware\Core\Framework\Store\Services\FirstRunWizardService;
26
use Shopware\Core\Framework\Util\HtmlSanitizer;
27
use Shopware\Core\Framework\Uuid\Uuid;
28
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
29
use Shopware\Core\PlatformRequest;
30
use Shopware\Core\System\Currency\CurrencyEntity;
0 ignored issues
show
Bug introduced by
The type Shopware\Core\System\Currency\CurrencyEntity 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...
31
use Shopware\Core\System\SystemConfig\SystemConfigService;
32
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
33
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
34
use Symfony\Component\HttpFoundation\JsonResponse;
35
use Symfony\Component\HttpFoundation\Request;
36
use Symfony\Component\HttpFoundation\Response;
37
use Symfony\Component\Routing\Annotation\Route;
38
use Symfony\Component\Validator\ConstraintViolation;
39
use Symfony\Component\Validator\ConstraintViolationList;
40
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
41
use function version_compare;
42
43
#[Route(defaults: ['_routeScope' => ['administration']])]
44
#[Package('administration')]
45
class AdministrationController extends AbstractController
46
{
47
    private readonly bool $esAdministrationEnabled;
48
49
    /**
50
     * @internal
51
     *
52
     * @param array<int, int> $supportedApiVersions
53
     */
54
    public function __construct(
55
        private readonly TemplateFinder $finder,
56
        private readonly FirstRunWizardService $firstRunWizardService,
57
        private readonly SnippetFinderInterface $snippetFinder,
58
        private readonly array $supportedApiVersions,
59
        private readonly KnownIpsCollectorInterface $knownIpsCollector,
60
        private readonly Connection $connection,
61
        private readonly EventDispatcherInterface $eventDispatcher,
62
        private readonly string $shopwareCoreDir,
63
        private readonly EntityRepository $customerRepo,
64
        private readonly EntityRepository $currencyRepository,
65
        private readonly HtmlSanitizer $htmlSanitizer,
66
        private readonly DefinitionInstanceRegistry $definitionInstanceRegistry,
67
        ParameterBagInterface $params,
68
        private readonly SystemConfigService $systemConfigService
69
    ) {
70
        // param is only available if the elasticsearch bundle is enabled
71
        $this->esAdministrationEnabled = $params->has('elasticsearch.administration.enabled')
0 ignored issues
show
Bug introduced by
The property esAdministrationEnabled is declared read-only in Shopware\Administration\...dministrationController.
Loading history...
72
            ? $params->get('elasticsearch.administration.enabled')
73
            : false;
74
    }
75
76
    #[Route(path: '/%shopware_administration.path_name%', name: 'administration.index', defaults: ['auth_required' => false], methods: ['GET'])]
77
    public function index(Request $request, Context $context): Response
78
    {
79
        $template = $this->finder->find('@Administration/administration/index.html.twig');
80
81
        /** @var CurrencyEntity $defaultCurrency */
82
        $defaultCurrency = $this->currencyRepository->search(new Criteria([Defaults::CURRENCY]), $context)->first();
83
84
        return $this->render($template, [
85
            'features' => Feature::getAll(),
86
            'systemLanguageId' => Defaults::LANGUAGE_SYSTEM,
87
            'defaultLanguageIds' => [Defaults::LANGUAGE_SYSTEM],
88
            'systemCurrencyId' => Defaults::CURRENCY,
89
            'disableExtensions' => EnvironmentHelper::getVariable('DISABLE_EXTENSIONS', false),
90
            'systemCurrencyISOCode' => $defaultCurrency->getIsoCode(),
91
            'liveVersionId' => Defaults::LIVE_VERSION,
92
            'firstRunWizard' => $this->firstRunWizardService->frwShouldRun(),
93
            'apiVersion' => $this->getLatestApiVersion(),
94
            'cspNonce' => $request->attributes->get(PlatformRequest::ATTRIBUTE_CSP_NONCE),
95
            'adminEsEnable' => $this->esAdministrationEnabled,
96
        ]);
97
    }
98
99
    #[Route(path: '/api/_admin/snippets', name: 'api.admin.snippets', methods: ['GET'])]
100
    public function snippets(Request $request): Response
101
    {
102
        $snippets = [];
103
        $locale = $request->query->get('locale', 'en-GB');
104
        $snippets[$locale] = $this->snippetFinder->findSnippets((string) $locale);
105
106
        if ($locale !== 'en-GB') {
107
            $snippets['en-GB'] = $this->snippetFinder->findSnippets('en-GB');
108
        }
109
110
        return new JsonResponse($snippets);
111
    }
112
113
    #[Route(path: '/api/_admin/known-ips', name: 'api.admin.known-ips', methods: ['GET'])]
114
    public function knownIps(Request $request): Response
115
    {
116
        $ips = [];
117
118
        foreach ($this->knownIpsCollector->collectIps($request) as $ip => $name) {
119
            $ips[] = [
120
                'name' => $name,
121
                'value' => $ip,
122
            ];
123
        }
124
125
        return new JsonResponse(['ips' => $ips]);
126
    }
127
128
    #[Route(path: '/api/_admin/reset-excluded-search-term', name: 'api.admin.reset-excluded-search-term', defaults: ['_acl' => ['system_config:update', 'system_config:create', 'system_config:delete']], methods: ['POST'])]
129
    public function resetExcludedSearchTerm(Context $context): JsonResponse
130
    {
131
        $searchConfigId = $this->connection->fetchOne('SELECT id FROM product_search_config WHERE language_id = :language_id', ['language_id' => Uuid::fromHexToBytes($context->getLanguageId())]);
132
133
        if ($searchConfigId === false) {
134
            throw new LanguageNotFoundException($context->getLanguageId());
135
        }
136
137
        $deLanguageId = $this->fetchLanguageIdByName('de-DE', $this->connection);
138
        $enLanguageId = $this->fetchLanguageIdByName('en-GB', $this->connection);
139
140
        switch ($context->getLanguageId()) {
141
            case $deLanguageId:
142
                $defaultExcludedTerm = require $this->shopwareCoreDir . '/Migration/Fixtures/stopwords/de.php';
143
144
                break;
145
            case $enLanguageId:
146
                $defaultExcludedTerm = require $this->shopwareCoreDir . '/Migration/Fixtures/stopwords/en.php';
147
148
                break;
149
            default:
150
                /** @var PreResetExcludedSearchTermEvent $preResetExcludedSearchTermEvent */
151
                $preResetExcludedSearchTermEvent = $this->eventDispatcher->dispatch(new PreResetExcludedSearchTermEvent($searchConfigId, [], $context));
152
                $defaultExcludedTerm = $preResetExcludedSearchTermEvent->getExcludedTerms();
153
        }
154
155
        $this->connection->executeStatement(
156
            'UPDATE `product_search_config` SET `excluded_terms` = :excludedTerms WHERE `id` = :id',
157
            [
158
                'excludedTerms' => json_encode($defaultExcludedTerm, \JSON_THROW_ON_ERROR),
159
                'id' => $searchConfigId,
160
            ]
161
        );
162
163
        return new JsonResponse([
164
            'success' => true,
165
        ]);
166
    }
167
168
    #[Route(path: '/api/_admin/check-customer-email-valid', name: 'api.admin.check-customer-email-valid', methods: ['POST'])]
169
    public function checkCustomerEmailValid(Request $request, Context $context): JsonResponse
170
    {
171
        $params = [];
172
        if (!$request->request->has('email')) {
173
            throw new \InvalidArgumentException('Parameter "email" is missing.');
174
        }
175
176
        $email = (string) $request->request->get('email');
177
        $isCustomerBoundSalesChannel = $this->systemConfigService->get('core.systemWideLoginRegistration.isCustomerBoundToSalesChannel');
178
        $boundSalesChannelId = null;
179
        if ($isCustomerBoundSalesChannel) {
180
            $boundSalesChannelId = $request->request->get('boundSalesChannelId');
181
            if ($boundSalesChannelId !== null && !\is_string($boundSalesChannelId)) {
182
                throw new InvalidRequestParameterException('boundSalesChannelId');
183
            }
184
        }
185
186
        $customer = $this->getCustomerByEmail((string) $request->request->get('id'), $email, $context, $boundSalesChannelId);
187
        if (!$customer) {
188
            return new JsonResponse(
189
                ['isValid' => true]
190
            );
191
        }
192
193
        $message = 'The email address {{ email }} is already in use';
194
        $params['{{ email }}'] = $email;
195
196
        if ($customer->getBoundSalesChannel()) {
197
            $message .= ' in the Sales Channel {{ salesChannel }}';
198
            $params['{{ salesChannel }}'] = $customer->getBoundSalesChannel()->getName();
199
        }
200
201
        $violations = new ConstraintViolationList();
202
        $violations->add(new ConstraintViolation(
203
            str_replace(array_keys($params), array_values($params), $message),
204
            $message,
205
            $params,
206
            null,
207
            null,
208
            $email,
209
            null,
210
            '79d30fe0-febf-421e-ac9b-1bfd5c9007f7'
211
        ));
212
213
        throw new ConstraintViolationException($violations, $request->request->all());
214
    }
215
216
    #[Route(path: '/api/_admin/sanitize-html', name: 'api.admin.sanitize-html', methods: ['POST'])]
217
    public function sanitizeHtml(Request $request, Context $context): JsonResponse
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed. ( Ignorable by Annotation )

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

217
    public function sanitizeHtml(Request $request, /** @scrutinizer ignore-unused */ Context $context): JsonResponse

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
218
    {
219
        if (!$request->request->has('html')) {
220
            throw new \InvalidArgumentException('Parameter "html" is missing.');
221
        }
222
223
        $html = (string) $request->request->get('html');
224
        $field = (string) $request->request->get('field');
225
226
        if ($field === '') {
227
            return new JsonResponse(
228
                ['preview' => $this->htmlSanitizer->sanitize($html)]
229
            );
230
        }
231
232
        [$entityName, $propertyName] = explode('.', $field);
233
        $property = $this->definitionInstanceRegistry->getByEntityName($entityName)->getField($propertyName);
234
235
        if ($property === null) {
236
            throw new \InvalidArgumentException('Invalid field property provided.');
237
        }
238
239
        $flag = $property->getFlag(AllowHtml::class);
240
241
        if ($flag === null) {
242
            return new JsonResponse(
243
                ['preview' => strip_tags($html)]
244
            );
245
        }
246
247
        if ($flag instanceof AllowHtml && !$flag->isSanitized()) {
248
            return new JsonResponse(
249
                ['preview' => $html]
250
            );
251
        }
252
253
        return new JsonResponse(
254
            ['preview' => $this->htmlSanitizer->sanitize($html, [], false, $field)]
255
        );
256
    }
257
258
    private function fetchLanguageIdByName(string $isoCode, Connection $connection): ?string
259
    {
260
        $languageId = $connection->fetchOne(
261
            '
262
            SELECT `language`.id FROM `language`
263
            INNER JOIN locale ON language.translation_code_id = locale.id
264
            WHERE `code` = :code',
265
            ['code' => $isoCode]
266
        );
267
268
        return $languageId === false ? null : Uuid::fromBytesToHex($languageId);
269
    }
270
271
    private function getLatestApiVersion(): ?int
272
    {
273
        $sortedSupportedApiVersions = array_values($this->supportedApiVersions);
274
275
        usort($sortedSupportedApiVersions, fn (int $version1, int $version2) => version_compare((string) $version1, (string) $version2));
276
277
        return array_pop($sortedSupportedApiVersions);
278
    }
279
280
    private function getCustomerByEmail(string $customerId, string $email, Context $context, ?string $boundSalesChannelId): ?CustomerEntity
281
    {
282
        $criteria = new Criteria();
283
        $criteria->setLimit(1);
284
        if ($boundSalesChannelId) {
285
            $criteria->addAssociation('boundSalesChannel');
286
        }
287
288
        $criteria->addFilter(new EqualsFilter('email', $email));
289
        $criteria->addFilter(new EqualsFilter('guest', false));
290
        $criteria->addFilter(new NotFilter(
291
            NotFilter::CONNECTION_AND,
292
            [new EqualsFilter('id', $customerId)]
293
        ));
294
295
        $criteria->addFilter(new MultiFilter(MultiFilter::CONNECTION_OR, [
296
            new EqualsFilter('boundSalesChannelId', null),
297
            new EqualsFilter('boundSalesChannelId', $boundSalesChannelId),
298
        ]));
299
300
        /** @var ?CustomerEntity $customer */
301
        $customer = $this->customerRepo->search($criteria, $context)->first();
302
303
        return $customer;
304
    }
305
}
306