Completed
Push — 3.x ( dca2a5...0d5aea )
by Grégoire
08:20
created

SonataAdminExtension   F

Complexity

Total Complexity 78

Size/Duplication

Total Lines 712
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 17

Importance

Changes 0
Metric Value
wmc 78
lcom 5
cbo 17
dl 0
loc 712
rs 2.048
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 39 5
A getFilters() 0 45 1
A getFunctions() 0 8 1
A getName() 0 4 1
A renderListElement() 0 23 1
A output() 0 13 1
A getValueFromFieldDescription() 0 34 5
A renderViewElement() 0 31 2
B renderViewElementCompare() 0 64 3
B renderRelationElement() 0 40 6
A getUrlSafeIdentifier() 0 13 3
A setXEditableTypeMapping() 0 4 1
A getXEditableType() 0 4 2
B getXEditableChoices() 0 41 11
A getCanonicalizedLocaleForMoment() 0 17 6
B getCanonicalizedLocaleForSelect2() 0 27 6
B isGrantedAffirmative() 0 26 7
A getTemplate() 0 30 4
A render() 0 32 2
A getTemplateRegistry() 0 11 2
B getObjectAndValueFromListElement() 0 35 8

How to fix   Complexity   

Complex Class

Complex classes like SonataAdminExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SonataAdminExtension, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\AdminBundle\Twig\Extension;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Psr\Log\LoggerInterface;
18
use Sonata\AdminBundle\Admin\AdminInterface;
19
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
20
use Sonata\AdminBundle\Admin\Pool;
21
use Sonata\AdminBundle\Exception\NoValueException;
22
use Sonata\AdminBundle\Templating\TemplateRegistryInterface;
23
use Symfony\Component\DependencyInjection\ContainerInterface;
24
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
25
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
26
use Symfony\Component\Security\Acl\Voter\FieldVote;
27
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
28
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
29
use Symfony\Component\Translation\TranslatorInterface as LegacyTranslationInterface;
30
use Symfony\Contracts\Translation\TranslatorInterface;
31
use Twig\Environment;
32
use Twig\Error\LoaderError;
33
use Twig\Extension\AbstractExtension;
34
use Twig\Template;
35
use Twig\TemplateWrapper;
36
use Twig\TwigFilter;
37
use Twig\TwigFunction;
38
39
/**
40
 * @final since sonata-project/admin-bundle 3.52
41
 *
42
 * @author Thomas Rabaix <[email protected]>
43
 */
44
class SonataAdminExtension extends AbstractExtension
45
{
46
    // @todo: there are more locales which are not supported by moment and they need to be translated/normalized/canonicalized here
47
    public const MOMENT_UNSUPPORTED_LOCALES = [
48
        'de' => ['de', 'de-at'],
49
        'es' => ['es', 'es-do'],
50
        'nl' => ['nl', 'nl-be'],
51
        'fr' => ['fr', 'fr-ca', 'fr-ch'],
52
    ];
53
54
    /**
55
     * @var Pool
56
     */
57
    protected $pool;
58
59
    /**
60
     * @var LoggerInterface
61
     */
62
    protected $logger;
63
64
    /**
65
     * @var TranslatorInterface|null
66
     */
67
    protected $translator;
68
69
    /**
70
     * @var string[]
71
     */
72
    private $xEditableTypeMapping = [];
73
74
    /**
75
     * @var ContainerInterface
76
     */
77
    private $templateRegistries;
78
79
    /**
80
     * @var AuthorizationCheckerInterface
81
     */
82
    private $securityChecker;
83
84
    public function __construct(
85
        Pool $pool,
86
        ?LoggerInterface $logger = null,
87
        $translator = null,
88
        ?ContainerInterface $templateRegistries = null,
89
        ?AuthorizationCheckerInterface $securityChecker = null
90
    ) {
91
        // NEXT_MAJOR: make the translator parameter required, move TranslatorInterface check to method signature
92
        // and remove this block
93
94
        if (null === $translator) {
95
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
96
                'The $translator parameter will be required fields with the 4.0 release.',
97
                E_USER_DEPRECATED
98
            );
99
        } else {
100
            if (!$translator instanceof TranslatorInterface) {
101
                @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
102
                    'The $translator parameter should be an instance of "%s" and will be mandatory in 4.0.',
103
                    TranslatorInterface::class
104
                ), E_USER_DEPRECATED);
105
            }
106
107
            if (!$translator instanceof TranslatorInterface && !$translator instanceof LegacyTranslationInterface) {
108
                throw new \TypeError(sprintf(
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('Argument 2 must...get_class($translator)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
109
                    'Argument 2 must be an instance of "%s" or preferably "%s", "%s given"',
110
                    TranslatorInterface::class,
111
                    LegacyTranslationInterface::class,
112
                    \get_class($translator)
113
                ));
114
            }
115
        }
116
117
        $this->pool = $pool;
118
        $this->logger = $logger;
119
        $this->translator = $translator;
120
        $this->templateRegistries = $templateRegistries;
121
        $this->securityChecker = $securityChecker;
122
    }
123
124
    public function getFilters()
125
    {
126
        return [
127
            new TwigFilter(
128
                'render_list_element',
129
                [$this, 'renderListElement'],
130
                [
131
                    'is_safe' => ['html'],
132
                    'needs_environment' => true,
133
                ]
134
            ),
135
            new TwigFilter(
136
                'render_view_element',
137
                [$this, 'renderViewElement'],
138
                [
139
                    'is_safe' => ['html'],
140
                    'needs_environment' => true,
141
                ]
142
            ),
143
            new TwigFilter(
144
                'render_view_element_compare',
145
                [$this, 'renderViewElementCompare'],
146
                [
147
                    'is_safe' => ['html'],
148
                    'needs_environment' => true,
149
                ]
150
            ),
151
            new TwigFilter(
152
                'render_relation_element',
153
                [$this, 'renderRelationElement']
154
            ),
155
            new TwigFilter(
156
                'sonata_urlsafeid',
157
                [$this, 'getUrlSafeIdentifier']
158
            ),
159
            new TwigFilter(
160
                'sonata_xeditable_type',
161
                [$this, 'getXEditableType']
162
            ),
163
            new TwigFilter(
164
                'sonata_xeditable_choices',
165
                [$this, 'getXEditableChoices']
166
            ),
167
        ];
168
    }
169
170
    public function getFunctions()
171
    {
172
        return [
173
            new TwigFunction('canonicalize_locale_for_moment', [$this, 'getCanonicalizedLocaleForMoment'], ['needs_context' => true]),
174
            new TwigFunction('canonicalize_locale_for_select2', [$this, 'getCanonicalizedLocaleForSelect2'], ['needs_context' => true]),
175
            new TwigFunction('is_granted_affirmative', [$this, 'isGrantedAffirmative']),
176
        ];
177
    }
178
179
    public function getName()
180
    {
181
        return 'sonata_admin';
182
    }
183
184
    /**
185
     * render a list element from the FieldDescription.
186
     *
187
     * @param object|array $listElement
188
     * @param array        $params
189
     *
190
     * @return string
191
     */
192
    public function renderListElement(
193
        Environment $environment,
194
        $listElement,
195
        FieldDescriptionInterface $fieldDescription,
196
        $params = []
197
    ) {
198
        $template = $this->getTemplate(
199
            $fieldDescription,
200
            // NEXT_MAJOR: Remove this line and use commented line below instead
201
            $fieldDescription->getAdmin()->getTemplate('base_list_field'),
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...nterface::getTemplate() has been deprecated with message: since sonata-project/admin-bundle 3.35. To be removed in 4.0. Use TemplateRegistry services instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
202
            //$this->getTemplateRegistry($fieldDescription->getAdmin()->getCode())->getTemplate('base_list_field'),
203
            $environment
204
        );
205
206
        [$object, $value] = $this->getObjectAndValueFromListElement($listElement, $fieldDescription);
0 ignored issues
show
Bug introduced by
The variable $object does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
207
208
        return $this->render($fieldDescription, $template, array_merge($params, [
209
            'admin' => $fieldDescription->getAdmin(),
210
            'object' => $object,
211
            'value' => $value,
212
            'field_description' => $fieldDescription,
213
        ]), $environment);
214
    }
215
216
    /**
217
     * @deprecated since sonata-project/admin-bundle 3.33, to be removed in 4.0. Use render instead
218
     *
219
     * @return string
220
     */
221
    public function output(
222
        FieldDescriptionInterface $fieldDescription,
223
        Template $template,
224
        array $parameters,
225
        Environment $environment
226
    ) {
227
        return $this->render(
228
            $fieldDescription,
229
            new TemplateWrapper($environment, $template),
230
            $parameters,
231
            $environment
232
        );
233
    }
234
235
    /**
236
     * return the value related to FieldDescription, if the associated object does no
237
     * exists => a temporary one is created.
238
     *
239
     * @param object $object
240
     *
241
     * @throws \RuntimeException
242
     *
243
     * NEXT_MAJOR: Remove this method
244
     *
245
     * @deprecated This method is deprecated with no replacement since sonata-project/admin-bundle 3.x and will be removed in 4.0.
246
     *
247
     * @return mixed
248
     */
249
    public function getValueFromFieldDescription(
250
        $object,
251
        FieldDescriptionInterface $fieldDescription,
252
        array $params = []
253
    ) {
254
        @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
255
            'The %s() method is deprecated since sonata-project/admin-bundle 3.x and will be removed in version 4.0.'
256
            .' There is no replacement.',
257
            __METHOD__
258
        ), E_USER_DEPRECATED);
259
260
        if (isset($params['loop']) && $object instanceof \ArrayAccess) {
261
            throw new \RuntimeException('remove the loop requirement');
262
        }
263
264
        $value = null;
265
266
        try {
267
            $value = $fieldDescription->getValue($object);
268
        } catch (NoValueException $e) {
269
            if ($fieldDescription->getAssociationAdmin()) {
270
                $value = $fieldDescription->getAssociationAdmin()->getNewInstance();
271
            } else {
272
                // NEXT_MAJOR: throw the NoValueException.
273
                @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
274
                    'Accessing a non existing value is deprecated'
275
                    .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
276
                    E_USER_DEPRECATED
277
                );
278
            }
279
        }
280
281
        return $value;
282
    }
283
284
    /**
285
     * render a view element.
286
     *
287
     * @param object $object
288
     *
289
     * @return string
290
     */
291
    public function renderViewElement(
292
        Environment $environment,
293
        FieldDescriptionInterface $fieldDescription,
294
        $object
295
    ) {
296
        $template = $this->getTemplate(
297
            $fieldDescription,
298
            '@SonataAdmin/CRUD/base_show_field.html.twig',
299
            $environment
300
        );
301
302
        try {
303
            $value = $fieldDescription->getValue($object);
304
        } catch (NoValueException $e) {
305
            // NEXT_MAJOR: Remove the try catch in order to throw the NoValueException.
306
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
307
                'Accessing a non existing value is deprecated'
308
                .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
309
                E_USER_DEPRECATED
310
            );
311
312
            $value = null;
313
        }
314
315
        return $this->render($fieldDescription, $template, [
316
            'field_description' => $fieldDescription,
317
            'object' => $object,
318
            'value' => $value,
319
            'admin' => $fieldDescription->getAdmin(),
320
        ], $environment);
321
    }
322
323
    /**
324
     * render a compared view element.
325
     *
326
     * @param mixed $baseObject
327
     * @param mixed $compareObject
328
     *
329
     * @return string
330
     */
331
    public function renderViewElementCompare(
332
        Environment $environment,
333
        FieldDescriptionInterface $fieldDescription,
334
        $baseObject,
335
        $compareObject
336
    ) {
337
        $template = $this->getTemplate(
338
            $fieldDescription,
339
            '@SonataAdmin/CRUD/base_show_field.html.twig',
340
            $environment
341
        );
342
343
        try {
344
            $baseValue = $fieldDescription->getValue($baseObject);
345
        } catch (NoValueException $e) {
346
            // NEXT_MAJOR: Remove the try catch in order to throw the NoValueException.
347
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
348
                'Accessing a non existing value is deprecated'
349
                .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
350
                E_USER_DEPRECATED
351
            );
352
353
            $baseValue = null;
354
        }
355
356
        try {
357
            $compareValue = $fieldDescription->getValue($compareObject);
358
        } catch (NoValueException $e) {
359
            // NEXT_MAJOR: Remove the try catch in order to throw the NoValueException.
360
            @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
361
                'Accessing a non existing value is deprecated'
362
                .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
363
                E_USER_DEPRECATED
364
            );
365
366
            $compareValue = null;
367
        }
368
369
        $baseValueOutput = $template->render([
370
            'admin' => $fieldDescription->getAdmin(),
371
            'field_description' => $fieldDescription,
372
            'value' => $baseValue,
373
            'object' => $baseObject,
374
        ]);
375
376
        $compareValueOutput = $template->render([
377
            'field_description' => $fieldDescription,
378
            'admin' => $fieldDescription->getAdmin(),
379
            'value' => $compareValue,
380
            'object' => $compareObject,
381
        ]);
382
383
        // Compare the rendered output of both objects by using the (possibly) overridden field block
384
        $isDiff = $baseValueOutput !== $compareValueOutput;
385
386
        return $this->render($fieldDescription, $template, [
387
            'field_description' => $fieldDescription,
388
            'value' => $baseValue,
389
            'value_compare' => $compareValue,
390
            'is_diff' => $isDiff,
391
            'admin' => $fieldDescription->getAdmin(),
392
            'object' => $baseObject,
393
        ], $environment);
394
    }
395
396
    /**
397
     * @param mixed $element
398
     *
399
     * @throws \RuntimeException
400
     *
401
     * @return mixed
402
     */
403
    public function renderRelationElement($element, FieldDescriptionInterface $fieldDescription)
404
    {
405
        if (!\is_object($element)) {
406
            return $element;
407
        }
408
409
        $propertyPath = $fieldDescription->getOption('associated_property');
410
411
        if (null === $propertyPath) {
412
            // For BC kept associated_tostring option behavior
413
            $method = $fieldDescription->getOption('associated_tostring');
414
415
            if ($method) {
416
                @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
417
                    'Option "associated_tostring" is deprecated since version 2.3 and will be removed in 4.0. Use "associated_property" instead.',
418
                    E_USER_DEPRECATED
419
                );
420
            } else {
421
                $method = '__toString';
422
            }
423
424
            if (!method_exists($element, $method)) {
425
                throw new \RuntimeException(sprintf(
426
                    'You must define an `associated_property` option or create a `%s::__toString` method'
427
                    .' to the field option %s from service %s is ',
428
                    \get_class($element),
429
                    $fieldDescription->getName(),
430
                    $fieldDescription->getAdmin()->getCode()
431
                ));
432
            }
433
434
            return $element->{$method}();
435
        }
436
437
        if (\is_callable($propertyPath)) {
438
            return $propertyPath($element);
439
        }
440
441
        return $this->pool->getPropertyAccessor()->getValue($element, $propertyPath);
442
    }
443
444
    /**
445
     * Get the identifiers as a string that is safe to use in a url.
446
     *
447
     * @param object $model
448
     *
449
     * @return string string representation of the id that is safe to use in a url
450
     */
451
    public function getUrlSafeIdentifier($model, ?AdminInterface $admin = null)
452
    {
453
        if (null === $admin) {
454
            $class = ClassUtils::getClass($model);
455
            if (!$this->pool->hasAdminByClass($class)) {
456
                throw new \InvalidArgumentException('You must pass an admin.');
457
            }
458
459
            $admin = $this->pool->getAdminByClass($class);
460
        }
461
462
        return $admin->getUrlSafeIdentifier($model);
463
    }
464
465
    /**
466
     * @param string[] $xEditableTypeMapping
467
     */
468
    public function setXEditableTypeMapping($xEditableTypeMapping)
469
    {
470
        $this->xEditableTypeMapping = $xEditableTypeMapping;
471
    }
472
473
    /**
474
     * @return string|bool
475
     */
476
    public function getXEditableType($type)
477
    {
478
        return isset($this->xEditableTypeMapping[$type]) ? $this->xEditableTypeMapping[$type] : false;
479
    }
480
481
    /**
482
     * Return xEditable choices based on the field description choices options & catalogue options.
483
     * With the following choice options:
484
     *     ['Status1' => 'Alias1', 'Status2' => 'Alias2']
485
     * The method will return:
486
     *     [['value' => 'Status1', 'text' => 'Alias1'], ['value' => 'Status2', 'text' => 'Alias2']].
487
     *
488
     * @return array
489
     */
490
    public function getXEditableChoices(FieldDescriptionInterface $fieldDescription)
491
    {
492
        $choices = $fieldDescription->getOption('choices', []);
493
        $catalogue = $fieldDescription->getOption('catalogue');
494
        $xEditableChoices = [];
495
        if (!empty($choices)) {
496
            reset($choices);
497
            $first = current($choices);
498
            // the choices are already in the right format
499
            if (\is_array($first) && \array_key_exists('value', $first) && \array_key_exists('text', $first)) {
500
                $xEditableChoices = $choices;
501
            } else {
502
                foreach ($choices as $value => $text) {
503
                    if ($catalogue) {
504
                        if (null !== $this->translator) {
505
                            $text = $this->translator->trans($text, [], $catalogue);
506
                        // NEXT_MAJOR: Remove this check
507
                        } elseif (method_exists($fieldDescription->getAdmin(), 'trans')) {
508
                            $text = $fieldDescription->getAdmin()->trans($text, [], $catalogue);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin\AdminInterface::trans() has been deprecated with message: since sonata-project/admin-bundle 3.9, to be removed in 4.0

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
509
                        }
510
                    }
511
512
                    $xEditableChoices[] = [
513
                        'value' => $value,
514
                        'text' => $text,
515
                    ];
516
                }
517
            }
518
        }
519
520
        if (false === $fieldDescription->getOption('required', true)
521
            && false === $fieldDescription->getOption('multiple', false)
522
        ) {
523
            $xEditableChoices = array_merge([[
524
                'value' => '',
525
                'text' => '',
526
            ]], $xEditableChoices);
527
        }
528
529
        return $xEditableChoices;
530
    }
531
532
    /**
533
     * Returns a canonicalized locale for "moment" NPM library,
534
     * or `null` if the locale's language is "en", which doesn't require localization.
535
     *
536
     * @return string|null
537
     */
538
    final public function getCanonicalizedLocaleForMoment(array $context)
539
    {
540
        $locale = strtolower(str_replace('_', '-', $context['app']->getRequest()->getLocale()));
541
542
        // "en" language doesn't require localization.
543
        if (('en' === $lang = substr($locale, 0, 2)) && !\in_array($locale, ['en-au', 'en-ca', 'en-gb', 'en-ie', 'en-nz'], true)) {
544
            return null;
545
        }
546
547
        foreach (self::MOMENT_UNSUPPORTED_LOCALES as $language => $locales) {
548
            if ($language === $lang && !\in_array($locale, $locales, true)) {
549
                $locale = $language;
550
            }
551
        }
552
553
        return $locale;
554
    }
555
556
    /**
557
     * Returns a canonicalized locale for "select2" NPM library,
558
     * or `null` if the locale's language is "en", which doesn't require localization.
559
     *
560
     * @return string|null
561
     */
562
    final public function getCanonicalizedLocaleForSelect2(array $context)
563
    {
564
        $locale = str_replace('_', '-', $context['app']->getRequest()->getLocale());
565
566
        // "en" language doesn't require localization.
567
        if ('en' === $lang = substr($locale, 0, 2)) {
568
            return null;
569
        }
570
571
        switch ($locale) {
572
            case 'pt':
573
                $locale = 'pt-PT';
574
                break;
575
            case 'ug':
576
                $locale = 'ug-CN';
577
                break;
578
            case 'zh':
579
                $locale = 'zh-CN';
580
                break;
581
            default:
582
                if (!\in_array($locale, ['pt-BR', 'pt-PT', 'ug-CN', 'zh-CN', 'zh-TW'], true)) {
583
                    $locale = $lang;
584
                }
585
        }
586
587
        return $locale;
588
    }
589
590
    /**
591
     * @param string|array $role
592
     * @param object|null  $object
593
     * @param string|null  $field
594
     *
595
     * @return bool
596
     */
597
    public function isGrantedAffirmative($role, $object = null, $field = null)
598
    {
599
        if (null === $this->securityChecker) {
600
            return false;
601
        }
602
603
        if (null !== $field) {
604
            $object = new FieldVote($object, $field);
605
        }
606
607
        if (!\is_array($role)) {
608
            $role = [$role];
609
        }
610
611
        foreach ($role as $oneRole) {
612
            try {
613
                if ($this->securityChecker->isGranted($oneRole, $object)) {
614
                    return true;
615
                }
616
            } catch (AuthenticationCredentialsNotFoundException $e) {
617
                // empty on purpose
618
            }
619
        }
620
621
        return false;
622
    }
623
624
    /**
625
     * Get template.
626
     *
627
     * @param string $defaultTemplate
628
     *
629
     * @return TemplateWrapper
630
     */
631
    protected function getTemplate(
632
        FieldDescriptionInterface $fieldDescription,
633
        $defaultTemplate,
634
        Environment $environment
635
    ) {
636
        $templateName = $fieldDescription->getTemplate() ?: $defaultTemplate;
637
638
        try {
639
            $template = $environment->load($templateName);
640
        } catch (LoaderError $e) {
641
            @trigger_error(sprintf(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
642
                'Relying on default template loading on field template loading exception is deprecated since 3.1'
643
                .' and will be removed in 4.0. A %s exception will be thrown instead',
644
                LoaderError::class
645
            ), E_USER_DEPRECATED);
646
            $template = $environment->load($defaultTemplate);
647
648
            if (null !== $this->logger) {
649
                $this->logger->warning(sprintf(
650
                    'An error occured trying to load the template "%s" for the field "%s",'
651
                    .' the default template "%s" was used instead.',
652
                    $templateName,
653
                    $fieldDescription->getFieldName(),
654
                    $defaultTemplate
655
                ), ['exception' => $e]);
656
            }
657
        }
658
659
        return $template;
660
    }
661
662
    private function render(
663
        FieldDescriptionInterface $fieldDescription,
664
        TemplateWrapper $template,
665
        array $parameters,
666
        Environment $environment
667
    ): ?string {
668
        $content = $template->render($parameters);
669
670
        if ($environment->isDebug()) {
671
            $commentTemplate = <<<'EOT'
672
673
<!-- START
674
    fieldName: %s
675
    template: %s
676
    compiled template: %s
677
    -->
678
    %s
679
<!-- END - fieldName: %s -->
680
EOT;
681
682
            return sprintf(
683
                $commentTemplate,
684
                $fieldDescription->getFieldName(),
685
                $fieldDescription->getTemplate(),
686
                $template->getSourceContext()->getName(),
687
                $content,
688
                $fieldDescription->getFieldName()
689
            );
690
        }
691
692
        return $content;
693
    }
694
695
    /**
696
     * @throws ServiceCircularReferenceException
697
     * @throws ServiceNotFoundException
698
     */
699
    private function getTemplateRegistry(string $adminCode): TemplateRegistryInterface
700
    {
701
        $serviceId = $adminCode.'.template_registry';
702
        $templateRegistry = $this->templateRegistries->get($serviceId);
703
704
        if ($templateRegistry instanceof TemplateRegistryInterface) {
705
            return $templateRegistry;
706
        }
707
708
        throw new ServiceNotFoundException($serviceId);
709
    }
710
711
    /**
712
     * Extracts the object and requested value from the $listElement.
713
     *
714
     * @param object|array $listElement
715
     *
716
     * @throws \TypeError when $listElement is not an object or an array with an object on offset 0
717
     *
718
     * @return array An array containing object and value
719
     */
720
    private function getObjectAndValueFromListElement(
721
        $listElement,
722
        FieldDescriptionInterface $fieldDescription
723
    ): array {
724
        if (\is_object($listElement)) {
725
            $object = $listElement;
726
        } elseif (\is_array($listElement)) {
727
            if (!isset($listElement[0]) || !\is_object($listElement[0])) {
728
                throw new \TypeError(sprintf('If argument 1 passed to %s() is an array it must contain an object at offset 0.', __METHOD__));
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('If argument 1 p...offset 0.', __METHOD__).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
729
            }
730
731
            $object = $listElement[0];
732
        } else {
733
            throw new \TypeError(sprintf('Argument 1 passed to %s() must be an object or an array, %s given.', __METHOD__, \gettype($listElement)));
0 ignored issues
show
Unused Code introduced by
The call to TypeError::__construct() has too many arguments starting with sprintf('Argument 1 pass...\gettype($listElement)).

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
734
        }
735
736
        if (\is_array($listElement) && \array_key_exists($fieldDescription->getName(), $listElement)) {
737
            $value = $listElement[$fieldDescription->getName()];
738
        } else {
739
            try {
740
                $value = $fieldDescription->getValue($object);
741
            } catch (NoValueException $e) {
742
                // NEXT_MAJOR: throw the NoValueException.
743
                @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
744
                    'Accessing a non existing value is deprecated'
745
                    .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
746
                    E_USER_DEPRECATED
747
                );
748
749
                $value = null;
750
            }
751
        }
752
753
        return [$object, $value];
754
    }
755
}
756