Passed
Push — master ( e98579...d111dc )
by Kane
03:01
created

Embed::width()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 1
c 1
b 0
f 1
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
namespace Cohensive\OEmbed;
3
4
use Cohensive\OEmbed\Exceptions\HtmlParsingException;
5
use DOMDocument;
6
7
class Embed
8
{
9
    const TYPE_OEMBED = 0;
10
11
    const TYPE_REGEX = 1;
12
13
    /**
14
     * Type of an Embed object source data - OEmbed or Regex-based.
15
     */
16
    protected int $type;
17
18
    /**
19
     * Array of global options applied to embed objects.
20
     */
21
    protected array $options;
22
23
    /**
24
     * AMP flag.
25
     */
26
    protected bool $amp;
27
28
    /**
29
     * Original media URL.
30
     */
31
    protected string $url;
32
33
    /**
34
     * Embed data extracted via OEmbed or Regex extractors.
35
     */
36
    protected array $data;
37
38
    /**
39
     * Class containing Embed HTML code.
40
     */
41
    protected HtmlBuilder $html;
42
43
    /**
44
     * Thumbnail data if available.
45
     */
46
    protected ?array $thumbnail;
47
48
    /**
49
     * Creates Embed instance.
50
     */
51
    public function __construct(
52
        string $type,
53
        string $url,
54
        array $data,
55
        array $options = [],
56
        bool $amp = false
57
    ) {
58
        $this->type = $type;
59
        $this->url = $url;
60
        $this->data = $data;
61
        $this->options = $options;
62
        $this->amp = $amp;
63
64
        $this->initData();
65
    }
66
67
    /**
68
     * Initializer for the Embed object filling thumbnail, html and type based
69
     * on a given media provider data.
70
     */
71
    public function initData(): self
72
    {
73
        $data = $this->data();
74
75
        if (isset($data['thumbnail_url'])) {
76
            $this->thumbnail = [
77
                'url' => $data['thumbnail_url'],
78
                'width' => $data['thumbnail_width'] ?? null,
79
                'height' => $data['thumbnail_height'] ?? null,
80
            ];
81
        }
82
83
        if ($this->type == self::TYPE_OEMBED) {
84
            $this->html = $this->extractOEmbedHtml($data['html']);
85
        } else {
86
            $this->html = $this->extractRegexHtml($data['html']);
87
        }
88
89
        return $this;
90
    }
91
92
    /**
93
     * Returns mbed options.
94
     */
95
    public function getOptions(): array
96
    {
97
        return $this->options;
98
    }
99
100
    /**
101
     * Sets new embed options. See config file 'options' key.
102
     */
103
    public function setOptions(array $options): self
104
    {
105
        $this->options = $options;
106
        return $this;
107
    }
108
109
    /**
110
     * Returns provider-specific data options.
111
    */
112
    public function getProviderOptions(): array
113
    {
114
        if (isset($this->options['providers'])) {
115
            return $this->options['providers'][$this->data['provider_name']]['data'] ?? [];
116
        }
117
118
        return [];
119
    }
120
121
    /**
122
     * Returns provider-specific HTML options.
123
    */
124
    public function getProviderHtmlOptions(): array
125
    {
126
        if (isset($this->options['providers'])) {
127
            return $this->options['providers'][$this->data['provider_name']]['html'] ?? [];
128
        }
129
130
        return [];
131
    }
132
133
    /**
134
     * Sets AMP mode.
135
     */
136
    public function setAmp(bool $amp): self
137
    {
138
        $this->amp = $amp;
139
        return $this;
140
    }
141
142
    /**
143
     * Returns current HtmlBuilder instance.
144
     */
145
    public function htmlBuilder(): HtmlBuilder
146
    {
147
        return $this->html;
148
    }
149
150
    /**
151
     * Returns string with HTML to embed in application. Will return AMP-friendly
152
     * HTML if global amp mode is enabled and not overwitten.
153
     */
154
    public function html(array $options = null, ?bool $amp = null): string
155
    {
156
        if (is_null($options)) {
157
            $options = $this->options;
158
        } else {
159
            $options = array_merge($this->options, $options);
160
        }
161
162
        if (is_null($amp)) {
163
            $amp = $this->amp;
164
        }
165
166
        return $this->html->html($options, $amp);
167
    }
168
169
    /**
170
     * Returns string with AMP-friendly HTML to embed in an application.
171
     */
172
    public function ampHtml(array $options = null): string
173
    {
174
        return $this->html($options, true);
175
    }
176
177
    /**
178
     * Return script source if available in embed html.
179
     */
180
    public function script(): ?string
181
    {
182
        return $this->html->script();
183
    }
184
185
    /**
186
     * Returns embed provider type.
187
     */
188
    public function type(): int
189
    {
190
        return $this->type;
191
    }
192
193
    /**
194
     * Returns embed html type.
195
     */
196
    public function htmlType(): string
197
    {
198
        return $this->html->type();
199
    }
200
201
    /**
202
     * Returns media provider data.
203
     */
204
    public function url(): string
205
    {
206
        return $this->url;
207
    }
208
209
    /**
210
     * Returns media provider data with applied options if set.
211
     */
212
    public function data(): array
213
    {
214
        $data = array_merge($this->data, $this->getProviderOptions());
215
216
        return $data;
217
    }
218
219
    /**
220
     * Returns width of the embed. 0 if not set.
221
     */
222
    public function width(): int
223
    {
224
        return $this->data()['width'] ?? 0;
225
    }
226
227
    /**
228
     * Returns height of the embed. 0 if not set.
229
     */
230
    public function height(): int
231
    {
232
        return $this->data()['height'] ?? 0;
233
    }
234
235
    /**
236
     * Returns width/height ratio of the embed. 0 if dimensions are not set.
237
     */
238
    public function ratio(): float
239
    {
240
        if (isset($this->data()['width']) && isset($this->data()['height'])) {
241
            return $this->data()['width'] / $this->data()['height'];
242
        }
243
244
        return 0;
245
    }
246
247
    /**
248
     * Returns string describing media type. According to OEmbed spec it could be:
249
     * one of these: photo, video, link, rich
250
     */
251
    public function mediaType(): string
252
    {
253
        return $this->data['type'];
254
    }
255
256
    /**
257
     * Returns boolean flag telling if given embed data has a thumbnail.
258
     */
259
    public function hasThumbnail(): bool
260
    {
261
        return is_array($this->thumbnail);
262
    }
263
264
    /**
265
     * Returns thumbnail data in an array form containing url and its dimensions.
266
     *
267
     */
268
    public function thumbnail(): ?array
269
    {
270
        return $this->thumbnail;
271
    }
272
273
    /**
274
     * Reutrns string for thumbnail or null if it's not set.
275
     */
276
    public function thumbnailUrl(): ?string
277
    {
278
        return $this->hasThumbnail() ? $this->thumbnail['url'] : null;
279
    }
280
281
    /**
282
     * Return thumbnail width or 0 if not set.
283
     */
284
    public function thumbnailWidth(): int
285
    {
286
        return $this->hasThumbnail() ? $this->thumbnail['width'] : 0;
287
    }
288
289
    /**
290
     * Return thumbnail height or 0 if not set.
291
     */
292
    public function thumbnailHeight(): int
293
    {
294
        return $this->hasThumbnail() ? $this->thumbnail['height'] : 0;
295
    }
296
297
    /**
298
     * Converts Embed instance into an array for caching.
299
     */
300
    public function toArray(): array
301
    {
302
        return [
303
            'type' => $this->type,
304
            'url' => $this->url,
305
            'data' => $this->data,
306
        ];
307
    }
308
309
    /**
310
     * Converts Embed instance into json string.
311
     */
312
    public function toJson(): string
313
    {
314
        return json_encode($this->toArray());
315
    }
316
317
    /**
318
     * Returns HtmlBuilder instance of OEmbed media provider HTML string.
319
     */
320
    protected function extractOEmbedHtml(string $html): HtmlBuilder
321
    {
322
        $script = null;
323
        $doc = new DOMDocument();
324
        $doc->loadHTML("<html><body>$html</body></html>", LIBXML_NOERROR);
325
        $body = $doc->documentElement->lastChild;
326
327
        if (!$body) {
328
            throw new HtmlParsingException();
329
        }
330
331
        $scripts = $body->getElementsByTagName('script');
332
        foreach ($scripts as $node) {
333
            $script = $node->getAttribute('src');
334
            break;
335
        }
336
337
        if (!$body->firstChild) {
338
            throw new HtmlParsingException();
339
        }
340
341
        if ($body->firstChild && $body->firstChild->nodeName === 'iframe') {
342
            $attrs = [];
343
344
            foreach ($body->firstChild->attributes as $attribute) {
345
                $attrs[$attribute->name] = $attribute->value;
346
            }
347
348
            $attrs = array_merge($attrs, $this->getProviderHtmlOptions());
349
350
            return new HtmlBuilder(HtmlBuilder::TYPE_IFRAME, $attrs, $script);
351
        }
352
353
        return new HtmlBuilder(HtmlBuilder::TYPE_RAW, $html, $script);
354
    }
355
356
    /**
357
     * Returns HtmlBuilder instance of Regex media provider HTML string.
358
     */
359
    protected function extractRegexHtml(array $html): HtmlBuilder
360
    {
361
        $type = array_key_first($html);
362
        return new HtmlBuilder($type, $html[$type]);
363
    }
364
}
365