Passed
Push — master ( 08b709...8a3439 )
by Kane
05:11
created

HtmlBuilder::applyOptions()   C

Complexity

Conditions 14
Paths 9

Size

Total Lines 38
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 24
c 1
b 0
f 0
dl 0
loc 38
rs 6.2666
cc 14
nc 9
nop 3

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Cohensive\OEmbed;
3
4
class HtmlBuilder
5
{
6
    const TYPE_RAW = 'raw';
7
    const TYPE_IFRAME = 'iframe';
8
    const TYPE_VIDEO = 'video';
9
10
    public function __construct(
11
        protected string $type,
12
        protected string|array $html,
13
        protected ?string $script = null
14
    ) {
15
    }
16
17
    /**
18
     * Returns current type.
19
     */
20
    public function type(): string
21
    {
22
        return $this->type;
23
    }
24
25
    /**
26
     * Returns HTML code for media provider.
27
     */
28
    public function html(array $options = [], array $globalOptions = [], bool $amp = false): string
29
    {
30
        if (is_array($this->html)) {
31
            $attrs = $this->applyOptions($this->html, $options, $globalOptions);
32
33
            if ($this->type === self::TYPE_IFRAME) {
34
                return $this->iframe($attrs, $amp);
35
            }
36
37
            if ($this->type === self::TYPE_VIDEO) {
38
                return $this->video($attrs, $amp);
39
            }
40
41
            return '';
42
        } else {
43
            return $this->html;
44
        }
45
    }
46
47
    /**
48
     * Return AMP-friendly HTML for media provider.
49
     */
50
    public function ampHtml(array $options = [], array $globalOptions = []): string
51
    {
52
        return $this->html($options, $globalOptions, true);
53
    }
54
55
    /**
56
     * Returns URL for a given media provider embed. Returned url type depends on embed type:
57
     * iframe - string
58
     * video - string[]
59
     * raw - null
60
     */
61
    public function src(array $options = [], array $globalOptions = []): mixed
62
    {
63
        if (is_array($this->html)) {
64
            $attrs = $this->applyOptions($this->html, $options, $globalOptions);
65
66
            if ($this->type === self::TYPE_IFRAME) {
67
                return $attrs['src'] ?? null;
68
            }
69
70
            if ($this->type === self::TYPE_VIDEO) {
71
                return array_map(function ($source) {
72
                    return $source['src'];
73
                }, $attrs['source']);
74
            }
75
        }
76
77
        return null;
78
    }
79
80
    /**
81
     * Constructs <iframe> HTML-element based on array of provider attributes.
82
     */
83
    protected function iframe(array $attrs, bool $amp = false): string
84
    {
85
        $tag = $amp ? 'amp-iframe' : 'iframe';
86
87
        $html = "<$tag";
88
        foreach ($attrs as $attr => $val) {
89
            $html .= sprintf(' %s="%s"', $attr, $val);
90
        }
91
        $html .= "></$tag>";
92
93
        return $html;
94
    }
95
96
    /**
97
     * Constructs <video> HTML-element based on an array of provider attributes.
98
     */
99
    protected function video(array $attrs, bool $amp = false): string
100
    {
101
        $tag = $amp ? 'amp-video' : 'video';
102
103
        $inner = '';
104
105
        $html = "<$tag";
106
        foreach ($attrs as $attr => $val) {
107
            if (is_array($val)) {
108
                foreach ($val as $child) {
109
                    $inner .= "<$attr";
110
                    foreach ($child as $iattr => $ival) {
111
                        $inner .= sprintf(' %s="%s"', $iattr, $ival);
112
                    }
113
                    $inner .= ">";
114
                }
115
            } else {
116
                $html .= sprintf(' %s="%s"', $attr, $val);
117
            }
118
        }
119
        $html .= ">";
120
121
        $html .= $inner;
122
123
        $html .= "</$tag>";
124
125
        return $html;
126
    }
127
128
    /**
129
     * Returns script source if available.
130
     */
131
    public function script(): ?string
132
    {
133
        return $this->script;
134
    }
135
136
    /**
137
     * Converts class to an array.
138
     */
139
    public function toArray(): array
140
    {
141
        return [
142
            'type' => $this->type,
143
            'html' => $this->html,
144
        ];
145
    }
146
147
    /**
148
     * Extracts and returns an array of options for a current HTML element type.
149
     */
150
    protected function getTypeOptions(array $options): array
151
    {
152
        if (isset($options['html'])) {
153
            return $options['html'][$this->type] ?? [];
154
        }
155
156
        return [];
157
    }
158
159
    /**
160
     * Merge and apply local and global options to the provider attributes.
161
     */
162
    protected function applyOptions(array $attrs, array $options, array $globalOptions): array
163
    {
164
        $options = array_merge($globalOptions['attributes'] ?? [], $options);
165
        $width = $options['width'] ?? null;
166
        $height = $options['height'] ?? null;
167
168
        // If embed output dimensions are set and numeric use them to calculate output with correct aspect ratio.
169
        // If dimensions are not numeric attempt to set them based on manual input.
170
        if (isset($attrs['width'])
171
            && isset($attrs['height'])
172
            && is_numeric($attrs['width'])
173
            && is_numeric($attrs['height'])
174
        ) {
175
            $ratio = $attrs['width'] / $attrs['height'];
176
            $attrs['width'] = $width ?: round(($height ?: $attrs['height']) * $ratio);
177
            $attrs['height'] = $height ?: round($attrs['width'] / $ratio);
178
        } elseif ($width || $height) {
179
            $attrs['width'] = $width ?: $attrs['width'];
180
            $attrs['height'] = $height ?: $attrs['height'];
181
        }
182
183
        $typeOptions = $this->getTypeOptions($globalOptions);
184
185
        if ($options['autoplay'] ?? false) {
186
            $attrs['autoplay'] = $options['autoplay'];
187
188
            // We can remove autoplay option if type is "iframe" after we change "src" attribute.
189
            if ($this->type === self::TYPE_IFRAME) {
190
                $attrs['src'] = $this->addUrlParam($attrs['src'], sprintf('%s=%s', 'autoplay', $attrs['autoplay']));
191
                unset($options['autoplay']);
192
                unset($attrs['autoplay']);
193
            }
194
        }
195
196
        return array_filter(
197
            array_merge($typeOptions, $options, $attrs),
198
            function ($v) {
199
                return $v !== null;
200
            }
201
        );
202
    }
203
204
    /**
205
     * Append custom parameter to the end of the url.
206
     */
207
    protected function addUrlParam(string $url, string $param): string
208
    {
209
        $operator = strpos($url, '?') >= 0 ? '&' : '?';
210
        return $url . $operator . $param;
211
    }
212
}
213