AdministrationController   A
last analyzed

Complexity

Total Complexity 31

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 139
dl 0
loc 267
rs 9.92
c 0
b 0
f 0
wmc 31

10 Methods

Rating   Name   Duplication   Size   Complexity  
A knownIps() 0 13 2
A __construct() 0 23 3
A resetExcludedSearchTerm() 0 37 4
A snippets() 0 12 2
A index() 0 21 1
A getLatestApiVersion() 0 7 1
A fetchLanguageIdByName() 0 11 2
A getCustomerByEmail() 0 24 2
B sanitizeHtml() 0 39 7
B checkCustomerEmailValid() 0 46 7
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\RoutingException;
24
use Shopware\Core\Framework\Store\Services\FirstRunWizardService;
25
use Shopware\Core\Framework\Util\HtmlSanitizer;
26
use Shopware\Core\Framework\Uuid\Uuid;
27
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
28
use Shopware\Core\PlatformRequest;
29
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...
30
use Shopware\Core\System\SystemConfig\SystemConfigService;
31
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
32
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
33
use Symfony\Component\HttpFoundation\JsonResponse;
34
use Symfony\Component\HttpFoundation\Request;
35
use Symfony\Component\HttpFoundation\Response;
36
use Symfony\Component\Routing\Annotation\Route;
37
use Symfony\Component\Validator\ConstraintViolation;
38
use Symfony\Component\Validator\ConstraintViolationList;
39
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
40
41
#[Route(defaults: ['_routeScope' => ['administration']])]
42
#[Package('administration')]
43
class AdministrationController extends AbstractController
44
{
45
    private readonly bool $esAdministrationEnabled;
46
47
    private readonly bool $esStorefrontEnabled;
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
        $this->esStorefrontEnabled = $params->has('elasticsearch.enabled')
0 ignored issues
show
Bug introduced by
The property esStorefrontEnabled is declared read-only in Shopware\Administration\...dministrationController.
Loading history...
75
            ? $params->get('elasticsearch.enabled')
76
            : false;
77
    }
78
79
    #[Route(path: '/%shopware_administration.path_name%', name: 'administration.index', defaults: ['auth_required' => false], methods: ['GET'])]
80
    public function index(Request $request, Context $context): Response
81
    {
82
        $template = $this->finder->find('@Administration/administration/index.html.twig');
83
84
        /** @var CurrencyEntity $defaultCurrency */
85
        $defaultCurrency = $this->currencyRepository->search(new Criteria([Defaults::CURRENCY]), $context)->first();
86
87
        return $this->render($template, [
88
            'features' => Feature::getAll(),
89
            'systemLanguageId' => Defaults::LANGUAGE_SYSTEM,
90
            'defaultLanguageIds' => [Defaults::LANGUAGE_SYSTEM],
91
            'systemCurrencyId' => Defaults::CURRENCY,
92
            'disableExtensions' => EnvironmentHelper::getVariable('DISABLE_EXTENSIONS', false),
93
            'systemCurrencyISOCode' => $defaultCurrency->getIsoCode(),
94
            'liveVersionId' => Defaults::LIVE_VERSION,
95
            'firstRunWizard' => $this->firstRunWizardService->frwShouldRun(),
96
            'apiVersion' => $this->getLatestApiVersion(),
97
            'cspNonce' => $request->attributes->get(PlatformRequest::ATTRIBUTE_CSP_NONCE),
98
            'adminEsEnable' => $this->esAdministrationEnabled,
99
            'storefrontEsEnable' => $this->esStorefrontEnabled,
100
        ]);
101
    }
102
103
    #[Route(path: '/api/_admin/snippets', name: 'api.admin.snippets', methods: ['GET'])]
104
    public function snippets(Request $request): Response
105
    {
106
        $snippets = [];
107
        $locale = $request->query->get('locale', 'en-GB');
108
        $snippets[$locale] = $this->snippetFinder->findSnippets((string) $locale);
109
110
        if ($locale !== 'en-GB') {
111
            $snippets['en-GB'] = $this->snippetFinder->findSnippets('en-GB');
112
        }
113
114
        return new JsonResponse($snippets);
115
    }
116
117
    #[Route(path: '/api/_admin/known-ips', name: 'api.admin.known-ips', methods: ['GET'])]
118
    public function knownIps(Request $request): Response
119
    {
120
        $ips = [];
121
122
        foreach ($this->knownIpsCollector->collectIps($request) as $ip => $name) {
123
            $ips[] = [
124
                'name' => $name,
125
                'value' => $ip,
126
            ];
127
        }
128
129
        return new JsonResponse(['ips' => $ips]);
130
    }
131
132
    #[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'])]
133
    public function resetExcludedSearchTerm(Context $context): JsonResponse
134
    {
135
        $searchConfigId = $this->connection->fetchOne('SELECT id FROM product_search_config WHERE language_id = :language_id', ['language_id' => Uuid::fromHexToBytes($context->getLanguageId())]);
136
137
        if ($searchConfigId === false) {
138
            throw RoutingException::languageNotFound($context->getLanguageId());
139
        }
140
141
        $deLanguageId = $this->fetchLanguageIdByName('de-DE', $this->connection);
142
        $enLanguageId = $this->fetchLanguageIdByName('en-GB', $this->connection);
143
144
        switch ($context->getLanguageId()) {
145
            case $deLanguageId:
146
                $defaultExcludedTerm = require $this->shopwareCoreDir . '/Migration/Fixtures/stopwords/de.php';
147
148
                break;
149
            case $enLanguageId:
150
                $defaultExcludedTerm = require $this->shopwareCoreDir . '/Migration/Fixtures/stopwords/en.php';
151
152
                break;
153
            default:
154
                /** @var PreResetExcludedSearchTermEvent $preResetExcludedSearchTermEvent */
155
                $preResetExcludedSearchTermEvent = $this->eventDispatcher->dispatch(new PreResetExcludedSearchTermEvent($searchConfigId, [], $context));
156
                $defaultExcludedTerm = $preResetExcludedSearchTermEvent->getExcludedTerms();
157
        }
158
159
        $this->connection->executeStatement(
160
            'UPDATE `product_search_config` SET `excluded_terms` = :excludedTerms WHERE `id` = :id',
161
            [
162
                'excludedTerms' => json_encode($defaultExcludedTerm, \JSON_THROW_ON_ERROR),
163
                'id' => $searchConfigId,
164
            ]
165
        );
166
167
        return new JsonResponse([
168
            'success' => true,
169
        ]);
170
    }
171
172
    #[Route(path: '/api/_admin/check-customer-email-valid', name: 'api.admin.check-customer-email-valid', methods: ['POST'])]
173
    public function checkCustomerEmailValid(Request $request, Context $context): JsonResponse
174
    {
175
        $params = [];
176
        if (!$request->request->has('email')) {
177
            throw RoutingException::missingRequestParameter('email');
178
        }
179
180
        $email = (string) $request->request->get('email');
181
        $isCustomerBoundSalesChannel = $this->systemConfigService->get('core.systemWideLoginRegistration.isCustomerBoundToSalesChannel');
182
        $boundSalesChannelId = null;
183
        if ($isCustomerBoundSalesChannel) {
184
            $boundSalesChannelId = $request->request->get('boundSalesChannelId');
185
            if ($boundSalesChannelId !== null && !\is_string($boundSalesChannelId)) {
186
                throw RoutingException::invalidRequestParameter('boundSalesChannelId');
187
            }
188
        }
189
190
        $customer = $this->getCustomerByEmail((string) $request->request->get('id'), $email, $context, $boundSalesChannelId);
191
        if (!$customer) {
192
            return new JsonResponse(
193
                ['isValid' => true]
194
            );
195
        }
196
197
        $message = 'The email address {{ email }} is already in use';
198
        $params['{{ email }}'] = $email;
199
200
        if ($customer->getBoundSalesChannel()) {
201
            $message .= ' in the Sales Channel {{ salesChannel }}';
202
            $params['{{ salesChannel }}'] = $customer->getBoundSalesChannel()->getName();
203
        }
204
205
        $violations = new ConstraintViolationList();
206
        $violations->add(new ConstraintViolation(
207
            str_replace(array_keys($params), array_values($params), $message),
208
            $message,
209
            $params,
210
            null,
211
            null,
212
            $email,
213
            null,
214
            '79d30fe0-febf-421e-ac9b-1bfd5c9007f7'
215
        ));
216
217
        throw new ConstraintViolationException($violations, $request->request->all());
218
    }
219
220
    #[Route(path: '/api/_admin/sanitize-html', name: 'api.admin.sanitize-html', methods: ['POST'])]
221
    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

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