Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Youtube
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
5 / 5
11
100.00% covered (success)
100.00%
1 / 1
 control
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 metaControl
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 value
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 structure
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 shape
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Field;
6
7use Celema\Sire\Shape;
8use Cosray\Validation\Shapes;
9use Cosray\Value\Youtube as YoutubeValue;
10
11class Youtube extends Field implements Capability\Translatable, Capability\Limitable, Capability\Placeholdable
12{
13    use Capability\IsTranslatable;
14    use Capability\IsLimitable;
15    use Capability\IsPlaceholdable;
16
17    /** A video id, not a URL: the value is spliced into the embed `src`. */
18    private const string ID_RULE = 'regex:/^[A-Za-z0-9_-]{1,64}$/';
19
20    public function control(): Control
21    {
22        return Control::youtube();
23    }
24
25    public function metaControl(): ?Control
26    {
27        return Control::group([
28            [
29                'key' => 'aspectRatioX',
30                'label' => __('youtube:aspect-ratio-x'),
31                'control' => Control::number(step: 1, min: 1),
32                'width' => 50,
33            ],
34            [
35                'key' => 'aspectRatioY',
36                'label' => __('youtube:aspect-ratio-y'),
37                'control' => Control::number(step: 1, min: 1),
38                'width' => 50,
39            ],
40        ]);
41    }
42
43    public function value(): YoutubeValue
44    {
45        return new YoutubeValue($this->owner, $this, $this->valueContext);
46    }
47
48    public function structure(mixed $value = null): array
49    {
50        $result = $this->getTranslatableStructure('youtube', $value);
51        $result['meta']['aspectRatioX'] = [self::NEUTRAL_LOCALE => 16];
52        $result['meta']['aspectRatioY'] = [self::NEUTRAL_LOCALE => 9];
53
54        return $result;
55    }
56
57    public function shape(): Shape
58    {
59        $shape = Shapes::create();
60        $this->addType($shape);
61
62        if ($this->isTranslatable()) {
63            $locales = $this->owner->locales();
64            $defaultLocale = $locales->getDefault()->id;
65            $i18nShape = Shapes::create();
66
67            foreach ($locales as $locale) {
68                $localeValidators = [];
69
70                if ($this->isRequired() && $locale->id === $defaultLocale) {
71                    $localeValidators[] = 'required';
72                }
73
74                $localeField = $i18nShape
75                    ->add($locale->id, 'string')
76                    ->label($this->valueLabel($locale))
77                    ->rules(self::ID_RULE, ...$localeValidators);
78
79                if (!in_array('required', $localeValidators, true)) {
80                    $localeField->optional()->nullable();
81                }
82            }
83
84            $value = $shape->add('value', $i18nShape)->rules(...$this->validators);
85        } else {
86            $value = $shape->add('value', $this->zxxShape('string', [self::ID_RULE, ...$this->validators]));
87        }
88
89        if (!$this->isRequired()) {
90            $value->optional()->nullable();
91        }
92
93        $this->addMeta($shape);
94
95        return $shape;
96    }
97}