Embed::extractRegexHtml()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
nc 1
nop 1
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']) && isset($this->data['provider_name'])) {
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']) && isset($this->data['provider_name'])) {
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 = [], ?bool $amp = null): string
155
    {
156
        if (is_null($amp)) {
157
            $amp = $this->amp;
158
        }
159
160
        return $this->html->html($options, $this->options, $amp);
161
    }
162
163
    /**
164
     * Returns string with AMP-friendly HTML to embed in an application.
165
     */
166
    public function ampHtml(array $options = []): string
167
    {
168
        return $this->html($options, true);
169
    }
170
171
    /**
172
     * Returns URL of a resulting embed object.
173
     */
174
    public function src(array $options = []): mixed
175
    {
176
        $options = array_merge($this->options, $options ?? []);
177
178
        return $this->html->src($options, $this->options);
179
    }
180
181
    /**
182
     * Return script source if available in embed html.
183
     */
184
    public function script(): ?string
185
    {
186
        return $this->html->script();
187
    }
188
189
    /**
190
     * Returns embed provider type.
191
     */
192
    public function type(): int
193
    {
194
        return $this->type;
195
    }
196
197
    /**
198
     * Returns embed html type.
199
     */
200
    public function htmlType(): string
201
    {
202
        return $this->html->type();
203
    }
204
205
    /**
206
     * Returns media provider data.
207
     */
208
    public function url(): string
209
    {
210
        return $this->url;
211
    }
212
213
    /**
214
     * Returns media provider data with applied options if set.
215
     */
216
    public function data(): array
217
    {
218
        $data = array_merge($this->data, $this->getProviderOptions());
219
220
        return $data;
221
    }
222
223
    /**
224
     * Returns width of the embed. 0 if not set.
225
     */
226
    public function width(): int|string
227
    {
228
        return $this->data()['width'] ?? 0;
229
    }
230
231
    /**
232
     * Returns height of the embed. 0 if not set.
233
     */
234
    public function height(): int|string
235
    {
236
        return $this->data()['height'] ?? 0;
237
    }
238
239
    /**
240
     * Returns width/height ratio of the embed. 0 if dimensions are not set.
241
     */
242
    public function ratio(): float
243
    {
244
        if (isset($this->data()['width']) && isset($this->data()['height'])) {
245
            return (int) $this->data()['width'] / (int) $this->data()['height'];
246
        }
247
248
        return 0;
249
    }
250
251
    /**
252
     * Returns string describing media type. According to OEmbed spec it could be:
253
     * one of these: photo, video, link, rich
254
     */
255
    public function mediaType(): string
256
    {
257
        return $this->data['type'];
258
    }
259
260
    /**
261
     * Returns boolean flag telling if given embed data has a thumbnail.
262
     */
263
    public function hasThumbnail(): bool
264
    {
265
        return is_array($this->thumbnail);
266
    }
267
268
    /**
269
     * Returns thumbnail data in an array form containing url and its dimensions.
270
     *
271
     */
272
    public function thumbnail(): ?array
273
    {
274
        return $this->thumbnail;
275
    }
276
277
    /**
278
     * Reutrns string for thumbnail or null if it's not set.
279
     */
280
    public function thumbnailUrl(): ?string
281
    {
282
        return $this->hasThumbnail() ? $this->thumbnail['url'] : null;
283
    }
284
285
    /**
286
     * Return thumbnail width or 0 if not set.
287
     */
288
    public function thumbnailWidth(): int|string
289
    {
290
        return $this->hasThumbnail() ? $this->thumbnail['width'] : 0;
291
    }
292
293
    /**
294
     * Return thumbnail height or 0 if not set.
295
     */
296
    public function thumbnailHeight(): int|string
297
    {
298
        return $this->hasThumbnail() ? $this->thumbnail['height'] : 0;
299
    }
300
301
    /**
302
     * Converts Embed instance into an array for caching.
303
     */
304
    public function toArray(): array
305
    {
306
        return [
307
            'type' => $this->type,
308
            'url' => $this->url,
309
            'data' => $this->data,
310
        ];
311
    }
312
313
    /**
314
     * Converts Embed instance into json string.
315
     */
316
    public function toJson(): string
317
    {
318
        return json_encode($this->toArray());
319
    }
320
321
    /**
322
     * Returns HtmlBuilder instance of OEmbed media provider HTML string.
323
     */
324
    protected function extractOEmbedHtml(string $html): HtmlBuilder
325
    {
326
        $script = null;
327
        $doc = new DOMDocument();
328
        $doc->loadHTML("<?xml encoding='utf-8' ?><html><body>$html</body></html>", LIBXML_NOERROR);
329
        $body = $doc->documentElement->lastChild;
330
331
        if (!$body || ($body && !$body->firstChild) || !($body instanceof \DOMElement)) {
332
            throw new HtmlParsingException();
333
        }
334
335
        foreach ($body->getElementsByTagName('script') as $node) {
336
            $script = $node->getAttribute('src');
337
            break;
338
        }
339
340
        if ($body->firstChild->nodeName === 'iframe') {
341
            $attrs = [];
342
343
            foreach ($body->firstChild->attributes as $attribute) {
344
                $attrs[$attribute->name] = $attribute->value;
345
            }
346
347
            $attrs = array_merge($attrs, $this->getProviderHtmlOptions());
348
349
            return new HtmlBuilder(HtmlBuilder::TYPE_IFRAME, $attrs, $script);
350
        }
351
352
        return new HtmlBuilder(HtmlBuilder::TYPE_RAW, $html, $script);
353
    }
354
355
    /**
356
     * Returns HtmlBuilder instance of Regex media provider HTML string.
357
     */
358
    protected function extractRegexHtml(array $html): HtmlBuilder
359
    {
360
        $type = array_key_first($html);
361
        return new HtmlBuilder($type, $html[$type]);
362
    }
363
}
364