Completed
Pull Request — 3.x (#6200)
by
unknown
03:28
created

SonataAdminExtension::setXEditableTypeMapping()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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
        if (\is_object($listElement)) {
207
            $object = $listElement;
208
        } elseif (\is_array($listElement)) {
209
            if (!isset($listElement[0]) || !\is_object($listElement[0])) {
210
                throw new \InvalidArgumentException('If listElement is an array, it must contain an object on key 0.');
211
            }
212
213
            $object = $listElement[0];
214
        } else {
215
            throw new \InvalidArgumentException('listElement must be an object or an array.');
216
        }
217
218
        $value = null;
219
220
        if (\is_array($listElement) && isset($listElement[$fieldDescription->getName()])) {
221
            $value = $listElement[$fieldDescription->getName()];
222
        } else {
223
            try {
224
                $value = $fieldDescription->getValue($object);
225
            } catch (NoValueException $e) {
226
                // NEXT_MAJOR: throw the NoValueException.
227
                @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...
228
                    'Accessing a non existing value is deprecated'
229
                    .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
230
                    E_USER_DEPRECATED
231
                );
232
            }
233
        }
234
235
        return $this->render($fieldDescription, $template, array_merge($params, [
236
            'admin' => $fieldDescription->getAdmin(),
237
            'object' => $object,
238
            'value' => $value,
239
            'field_description' => $fieldDescription,
240
        ]), $environment);
241
    }
242
243
    /**
244
     * @deprecated since sonata-project/admin-bundle 3.33, to be removed in 4.0. Use render instead
245
     *
246
     * @return string
247
     */
248
    public function output(
249
        FieldDescriptionInterface $fieldDescription,
250
        Template $template,
251
        array $parameters,
252
        Environment $environment
253
    ) {
254
        return $this->render(
255
            $fieldDescription,
256
            new TemplateWrapper($environment, $template),
257
            $parameters,
258
            $environment
259
        );
260
    }
261
262
    /**
263
     * return the value related to FieldDescription, if the associated object does no
264
     * exists => a temporary one is created.
265
     *
266
     * @param object $object
267
     *
268
     * @throws \RuntimeException
269
     *
270
     * @deprecated This method is deprecated with no replacement since sonata-project/admin-bundle 3.x and will be removed in 4.0.
271
     *
272
     * @return mixed
273
     */
274
    public function getValueFromFieldDescription(
275
        $object,
276
        FieldDescriptionInterface $fieldDescription,
277
        array $params = []
278
    ) {
279
        if (isset($params['loop']) && $object instanceof \ArrayAccess) {
280
            throw new \RuntimeException('remove the loop requirement');
281
        }
282
283
        $value = null;
284
285
        try {
286
            $value = $fieldDescription->getValue($object);
287
        } catch (NoValueException $e) {
288
            if ($fieldDescription->getAssociationAdmin()) {
289
                $value = $fieldDescription->getAssociationAdmin()->getNewInstance();
290
            } else {
291
                // NEXT_MAJOR: throw the NoValueException.
292
                @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...
293
                    'Accessing a non existing value is deprecated'
294
                    .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
295
                    E_USER_DEPRECATED
296
                );
297
            }
298
        }
299
300
        return $value;
301
    }
302
303
    /**
304
     * render a view element.
305
     *
306
     * @param object $object
307
     *
308
     * @return string
309
     */
310
    public function renderViewElement(
311
        Environment $environment,
312
        FieldDescriptionInterface $fieldDescription,
313
        $object
314
    ) {
315
        $template = $this->getTemplate(
316
            $fieldDescription,
317
            '@SonataAdmin/CRUD/base_show_field.html.twig',
318
            $environment
319
        );
320
321
        try {
322
            $value = $fieldDescription->getValue($object);
323
        } catch (NoValueException $e) {
324
            // NEXT_MAJOR: Remove the try catch in order to throw the NoValueException.
325
            @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...
326
                'Accessing a non existing value is deprecated'
327
                .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
328
                E_USER_DEPRECATED
329
            );
330
331
            $value = null;
332
        }
333
334
        return $this->render($fieldDescription, $template, [
335
            'field_description' => $fieldDescription,
336
            'object' => $object,
337
            'value' => $value,
338
            'admin' => $fieldDescription->getAdmin(),
339
        ], $environment);
340
    }
341
342
    /**
343
     * render a compared view element.
344
     *
345
     * @param mixed $baseObject
346
     * @param mixed $compareObject
347
     *
348
     * @return string
349
     */
350
    public function renderViewElementCompare(
351
        Environment $environment,
352
        FieldDescriptionInterface $fieldDescription,
353
        $baseObject,
354
        $compareObject
355
    ) {
356
        $template = $this->getTemplate(
357
            $fieldDescription,
358
            '@SonataAdmin/CRUD/base_show_field.html.twig',
359
            $environment
360
        );
361
362
        try {
363
            $baseValue = $fieldDescription->getValue($baseObject);
364
        } catch (NoValueException $e) {
365
            // NEXT_MAJOR: Remove the try catch in order to throw the NoValueException.
366
            @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...
367
                'Accessing a non existing value is deprecated'
368
                .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
369
                E_USER_DEPRECATED
370
            );
371
372
            $baseValue = null;
373
        }
374
375
        try {
376
            $compareValue = $fieldDescription->getValue($compareObject);
377
        } catch (NoValueException $e) {
378
            // NEXT_MAJOR: Remove the try catch in order to throw the NoValueException.
379
            @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...
380
                'Accessing a non existing value is deprecated'
381
                .' since sonata-project/admin-bundle 3.67 and will throw an exception in 4.0.',
382
                E_USER_DEPRECATED
383
            );
384
385
            $compareValue = null;
386
        }
387
388
        $baseValueOutput = $template->render([
389
            'admin' => $fieldDescription->getAdmin(),
390
            'field_description' => $fieldDescription,
391
            'value' => $baseValue,
392
            'object' => $baseObject,
393
        ]);
394
395
        $compareValueOutput = $template->render([
396
            'field_description' => $fieldDescription,
397
            'admin' => $fieldDescription->getAdmin(),
398
            'value' => $compareValue,
399
            'object' => $compareObject,
400
        ]);
401
402
        // Compare the rendered output of both objects by using the (possibly) overridden field block
403
        $isDiff = $baseValueOutput !== $compareValueOutput;
404
405
        return $this->render($fieldDescription, $template, [
406
            'field_description' => $fieldDescription,
407
            'value' => $baseValue,
408
            'value_compare' => $compareValue,
409
            'is_diff' => $isDiff,
410
            'admin' => $fieldDescription->getAdmin(),
411
            'object' => $baseObject,
412
        ], $environment);
413
    }
414
415
    /**
416
     * @param mixed $element
417
     *
418
     * @throws \RuntimeException
419
     *
420
     * @return mixed
421
     */
422
    public function renderRelationElement($element, FieldDescriptionInterface $fieldDescription)
423
    {
424
        if (!\is_object($element)) {
425
            return $element;
426
        }
427
428
        $propertyPath = $fieldDescription->getOption('associated_property');
429
430
        if (null === $propertyPath) {
431
            // For BC kept associated_tostring option behavior
432
            $method = $fieldDescription->getOption('associated_tostring');
433
434
            if ($method) {
435
                @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...
436
                    'Option "associated_tostring" is deprecated since version 2.3 and will be removed in 4.0. Use "associated_property" instead.',
437
                    E_USER_DEPRECATED
438
                );
439
            } else {
440
                $method = '__toString';
441
            }
442
443
            if (!method_exists($element, $method)) {
444
                throw new \RuntimeException(sprintf(
445
                    'You must define an `associated_property` option or create a `%s::__toString` method'
446
                    .' to the field option %s from service %s is ',
447
                    \get_class($element),
448
                    $fieldDescription->getName(),
449
                    $fieldDescription->getAdmin()->getCode()
450
                ));
451
            }
452
453
            return $element->{$method}();
454
        }
455
456
        if (\is_callable($propertyPath)) {
457
            return $propertyPath($element);
458
        }
459
460
        return $this->pool->getPropertyAccessor()->getValue($element, $propertyPath);
461
    }
462
463
    /**
464
     * Get the identifiers as a string that is safe to use in a url.
465
     *
466
     * @param object $model
467
     *
468
     * @return string string representation of the id that is safe to use in a url
469
     */
470
    public function getUrlSafeIdentifier($model, ?AdminInterface $admin = null)
471
    {
472
        if (null === $admin) {
473
            $class = ClassUtils::getClass($model);
474
            if (!$this->pool->hasAdminByClass($class)) {
475
                throw new \InvalidArgumentException('You must pass an admin.');
476
            }
477
478
            $admin = $this->pool->getAdminByClass($class);
479
        }
480
481
        return $admin->getUrlSafeIdentifier($model);
482
    }
483
484
    /**
485
     * @param string[] $xEditableTypeMapping
486
     */
487
    public function setXEditableTypeMapping($xEditableTypeMapping)
488
    {
489
        $this->xEditableTypeMapping = $xEditableTypeMapping;
490
    }
491
492
    /**
493
     * @return string|bool
494
     */
495
    public function getXEditableType($type)
496
    {
497
        return isset($this->xEditableTypeMapping[$type]) ? $this->xEditableTypeMapping[$type] : false;
498
    }
499
500
    /**
501
     * Return xEditable choices based on the field description choices options & catalogue options.
502
     * With the following choice options:
503
     *     ['Status1' => 'Alias1', 'Status2' => 'Alias2']
504
     * The method will return:
505
     *     [['value' => 'Status1', 'text' => 'Alias1'], ['value' => 'Status2', 'text' => 'Alias2']].
506
     *
507
     * @return array
508
     */
509
    public function getXEditableChoices(FieldDescriptionInterface $fieldDescription)
510
    {
511
        $choices = $fieldDescription->getOption('choices', []);
512
        $catalogue = $fieldDescription->getOption('catalogue');
513
        $xEditableChoices = [];
514
        if (!empty($choices)) {
515
            reset($choices);
516
            $first = current($choices);
517
            // the choices are already in the right format
518
            if (\is_array($first) && \array_key_exists('value', $first) && \array_key_exists('text', $first)) {
519
                $xEditableChoices = $choices;
520
            } else {
521
                foreach ($choices as $value => $text) {
522
                    if ($catalogue) {
523
                        if (null !== $this->translator) {
524
                            $text = $this->translator->trans($text, [], $catalogue);
525
                        // NEXT_MAJOR: Remove this check
526
                        } elseif (method_exists($fieldDescription->getAdmin(), 'trans')) {
527
                            $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...
528
                        }
529
                    }
530
531
                    $xEditableChoices[] = [
532
                        'value' => $value,
533
                        'text' => $text,
534
                    ];
535
                }
536
            }
537
        }
538
539
        if (false === $fieldDescription->getOption('required', true)
540
            && false === $fieldDescription->getOption('multiple', false)
541
        ) {
542
            $xEditableChoices = array_merge([[
543
                'value' => '',
544
                'text' => '',
545
            ]], $xEditableChoices);
546
        }
547
548
        return $xEditableChoices;
549
    }
550
551
    /**
552
     * Returns a canonicalized locale for "moment" NPM library,
553
     * or `null` if the locale's language is "en", which doesn't require localization.
554
     *
555
     * @return string|null
556
     */
557
    final public function getCanonicalizedLocaleForMoment(array $context)
558
    {
559
        $locale = strtolower(str_replace('_', '-', $context['app']->getRequest()->getLocale()));
560
561
        // "en" language doesn't require localization.
562
        if (('en' === $lang = substr($locale, 0, 2)) && !\in_array($locale, ['en-au', 'en-ca', 'en-gb', 'en-ie', 'en-nz'], true)) {
563
            return null;
564
        }
565
566
        foreach (self::MOMENT_UNSUPPORTED_LOCALES as $language => $locales) {
567
            if ($language === $lang && !\in_array($locale, $locales, true)) {
568
                $locale = $language;
569
            }
570
        }
571
572
        return $locale;
573
    }
574
575
    /**
576
     * Returns a canonicalized locale for "select2" NPM library,
577
     * or `null` if the locale's language is "en", which doesn't require localization.
578
     *
579
     * @return string|null
580
     */
581
    final public function getCanonicalizedLocaleForSelect2(array $context)
582
    {
583
        $locale = str_replace('_', '-', $context['app']->getRequest()->getLocale());
584
585
        // "en" language doesn't require localization.
586
        if ('en' === $lang = substr($locale, 0, 2)) {
587
            return null;
588
        }
589
590
        switch ($locale) {
591
            case 'pt':
592
                $locale = 'pt-PT';
593
                break;
594
            case 'ug':
595
                $locale = 'ug-CN';
596
                break;
597
            case 'zh':
598
                $locale = 'zh-CN';
599
                break;
600
            default:
601
                if (!\in_array($locale, ['pt-BR', 'pt-PT', 'ug-CN', 'zh-CN', 'zh-TW'], true)) {
602
                    $locale = $lang;
603
                }
604
        }
605
606
        return $locale;
607
    }
608
609
    /**
610
     * @param string|array $role
611
     * @param object|null  $object
612
     * @param string|null  $field
613
     *
614
     * @return bool
615
     */
616
    public function isGrantedAffirmative($role, $object = null, $field = null)
617
    {
618
        if (null === $this->securityChecker) {
619
            return false;
620
        }
621
622
        if (null !== $field) {
623
            $object = new FieldVote($object, $field);
624
        }
625
626
        if (!\is_array($role)) {
627
            $role = [$role];
628
        }
629
630
        foreach ($role as $oneRole) {
631
            try {
632
                if ($this->securityChecker->isGranted($oneRole, $object)) {
633
                    return true;
634
                }
635
            } catch (AuthenticationCredentialsNotFoundException $e) {
636
                // empty on purpose
637
            }
638
        }
639
640
        return false;
641
    }
642
643
    /**
644
     * Get template.
645
     *
646
     * @param string $defaultTemplate
647
     *
648
     * @return TemplateWrapper
649
     */
650
    protected function getTemplate(
651
        FieldDescriptionInterface $fieldDescription,
652
        $defaultTemplate,
653
        Environment $environment
654
    ) {
655
        $templateName = $fieldDescription->getTemplate() ?: $defaultTemplate;
656
657
        try {
658
            $template = $environment->load($templateName);
659
        } catch (LoaderError $e) {
660
            @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...
661
                'Relying on default template loading on field template loading exception is deprecated since 3.1'
662
                .' and will be removed in 4.0. A %s exception will be thrown instead',
663
                LoaderError::class
664
            ), E_USER_DEPRECATED);
665
            $template = $environment->load($defaultTemplate);
666
667
            if (null !== $this->logger) {
668
                $this->logger->warning(sprintf(
669
                    'An error occured trying to load the template "%s" for the field "%s",'
670
                    .' the default template "%s" was used instead.',
671
                    $templateName,
672
                    $fieldDescription->getFieldName(),
673
                    $defaultTemplate
674
                ), ['exception' => $e]);
675
            }
676
        }
677
678
        return $template;
679
    }
680
681
    private function render(
682
        FieldDescriptionInterface $fieldDescription,
683
        TemplateWrapper $template,
684
        array $parameters,
685
        Environment $environment
686
    ): ?string {
687
        $content = $template->render($parameters);
688
689
        if ($environment->isDebug()) {
690
            $commentTemplate = <<<'EOT'
691
692
<!-- START
693
    fieldName: %s
694
    template: %s
695
    compiled template: %s
696
    -->
697
    %s
698
<!-- END - fieldName: %s -->
699
EOT;
700
701
            return sprintf(
702
                $commentTemplate,
703
                $fieldDescription->getFieldName(),
704
                $fieldDescription->getTemplate(),
705
                $template->getSourceContext()->getName(),
706
                $content,
707
                $fieldDescription->getFieldName()
708
            );
709
        }
710
711
        return $content;
712
    }
713
714
    /**
715
     * @throws ServiceCircularReferenceException
716
     * @throws ServiceNotFoundException
717
     */
718
    private function getTemplateRegistry(string $adminCode): TemplateRegistryInterface
719
    {
720
        $serviceId = $adminCode.'.template_registry';
721
        $templateRegistry = $this->templateRegistries->get($serviceId);
722
723
        if ($templateRegistry instanceof TemplateRegistryInterface) {
724
            return $templateRegistry;
725
        }
726
727
        throw new ServiceNotFoundException($serviceId);
728
    }
729
}
730