aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/htmlpurifier/HTMLPurifier_Injector_OembedToIframe.php
blob: 501fbadca428a81b925d09449a9fb2fb17a31ca2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<?php

/**
 * Converts oembed tags to embedded content like the ckeditor does in its `previewInData` feature.
 * But we only store the oembed into the database and perform the transformation here (with purify)
 *
 * Basically replaced <oembed url="..."></oembed> with a styled iframe.
 * Currently only Youtube embed are supported
 * TODO CKEditor also supports several other embed, which could also be implemented
 */
class HTMLPurifier_Injector_OembedToIframe extends HTMLPurifier_Injector
{
    public $name = 'OEmbed';
    public $needed = ['div', 'iframe'];

    /** @var array[] mapping the corresponding embed url to all their valid urls */
    const VALID_URLS = [
        'https://www.youtube.com/embed/' => [
            // See https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-media-embed/src/mediaembedediting.ts#L95
            '/^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)(?:&t=(\d+))?/',
            '/^(?:m\.)?youtube\.com\/shorts\/([\w-]+)(?:\?t=(\d+))?/',
            '/^(?:m\.)?youtube\.com\/v\/([\w-]+)(?:\?t=(\d+))?/',
            '/^youtube\.com\/embed\/([\w-]+)(?:\?start=(\d+))?/',
            '/^youtu\.be\/([\w-]+)(?:\?t=(\d+))?/'
        ]
    ];

    public function handleElement(&$token)
    {
        if ($token->name !== 'oembed') {
            return;
        }

        $url = $token->attr['url'] ?? '';
        $embedUrl = self::toEmbedUrl($url);
        if (!$embedUrl) {
            $token = false;
            return;
        }

        // See https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-media-embed/src/mediaembedediting.ts#L108
        // The css is in content.scss and matches the css of ckeditor5
        $token = [
            new HTMLPurifier_Token_Start('div', [
                'data-oembed-url' => $embedUrl
            ]),
            new HTMLPurifier_Token_Start('div', [
                'class' => 'ckeditor-embed-container'
            ]),
            new HTMLPurifier_Token_Empty('iframe', [
                'src' => $embedUrl,
                'class' => 'ckeditor-embed',
            ]),
            new HTMLPurifier_Token_End('div'),
            new HTMLPurifier_Token_End('div'),
        ];
    }

    /**
     * Transforms a valid url into a embed url
     * @param string $url
     * @return string|null
     */
    private static function toEmbedUrl(string $url): ?string
    {
        if (empty($url)) {
            return null;
        }
        $cleanUrl = preg_replace('/^https?:\/\/(?:www\.)?/', '', $url);
        foreach (self::VALID_URLS as $embedUrl => $patterns) {
            foreach ($patterns as $pattern) {
                if (preg_match($pattern, $cleanUrl, $matches)) {
                    $videoId = $matches[1];
                    $timestamp = !empty($matches[2]) ? $matches[2] : null;

                    $full_url = $embedUrl . $videoId;
                    if ($timestamp) {
                        $full_url .= '?start=' . $timestamp;
                    }

                    return $full_url;
                }
            }
        }

        return null;
    }
}