xref: /plugin/vshare/syntax/video.php (revision 08b6034edc581b6537a231f469288a06792e9d7d)
1b82f24afSAndreas Gohr<?php
2b82f24afSAndreas Gohr
35895dcbaSAndreas Gohruse dokuwiki\Extension\SyntaxPlugin;
45895dcbaSAndreas Gohr
5b82f24afSAndreas Gohr/**
6b82f24afSAndreas Gohr * Easily embed videos from various Video Sharing sites
7b82f24afSAndreas Gohr *
8b82f24afSAndreas Gohr * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
9b82f24afSAndreas Gohr * @author     Andreas Gohr <andi@splitbrain.org>
10b82f24afSAndreas Gohr */
115895dcbaSAndreas Gohrclass syntax_plugin_vshare_video extends SyntaxPlugin
12b82f24afSAndreas Gohr{
13b82f24afSAndreas Gohr    protected $sites;
14b82f24afSAndreas Gohr
15b82f24afSAndreas Gohr    protected $sizes = [
16b82f24afSAndreas Gohr        'small' => [255, 143],
17b82f24afSAndreas Gohr        'medium' => [425, 239],
18b82f24afSAndreas Gohr        'large' => [520, 293],
19b82f24afSAndreas Gohr        'full' => ['100%', ''],
20b82f24afSAndreas Gohr        'half' => ['50%', ''],
21b82f24afSAndreas Gohr    ];
22b82f24afSAndreas Gohr
23b82f24afSAndreas Gohr    protected $alignments = [
24b82f24afSAndreas Gohr        0 => 'none',
25b82f24afSAndreas Gohr        1 => 'right',
26b82f24afSAndreas Gohr        2 => 'left',
27b82f24afSAndreas Gohr        3 => 'center',
28b82f24afSAndreas Gohr    ];
29b82f24afSAndreas Gohr
30b82f24afSAndreas Gohr    /**
31b82f24afSAndreas Gohr     * Constructor.
32b82f24afSAndreas Gohr     * Intitalizes the supported video sites
33b82f24afSAndreas Gohr     */
34b82f24afSAndreas Gohr    public function __construct()
35b82f24afSAndreas Gohr    {
36b82f24afSAndreas Gohr        $this->sites = helper_plugin_vshare::loadSites();
37b82f24afSAndreas Gohr    }
38b82f24afSAndreas Gohr
39b82f24afSAndreas Gohr    /** @inheritdoc */
40b82f24afSAndreas Gohr    public function getType()
41b82f24afSAndreas Gohr    {
42b82f24afSAndreas Gohr        return 'substition';
43b82f24afSAndreas Gohr    }
44b82f24afSAndreas Gohr
45b82f24afSAndreas Gohr    /** @inheritdoc */
46b82f24afSAndreas Gohr    public function getPType()
47b82f24afSAndreas Gohr    {
48b82f24afSAndreas Gohr        return 'block';
49b82f24afSAndreas Gohr    }
50b82f24afSAndreas Gohr
51b82f24afSAndreas Gohr    /** @inheritdoc */
52b82f24afSAndreas Gohr    public function getSort()
53b82f24afSAndreas Gohr    {
54b82f24afSAndreas Gohr        return 159;
55b82f24afSAndreas Gohr    }
56b82f24afSAndreas Gohr
57b82f24afSAndreas Gohr    /** @inheritdoc */
58b82f24afSAndreas Gohr    public function connectTo($mode)
59b82f24afSAndreas Gohr    {
605895dcbaSAndreas Gohr        $pattern = implode('|', array_keys($this->sites));
61b82f24afSAndreas Gohr        $this->Lexer->addSpecialPattern('\{\{\s?(?:' . $pattern . ')>[^}]*\}\}', $mode, 'plugin_vshare_video');
62b82f24afSAndreas Gohr    }
63b82f24afSAndreas Gohr
64b82f24afSAndreas Gohr    /** @inheritdoc */
65b82f24afSAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
66b82f24afSAndreas Gohr    {
67b82f24afSAndreas Gohr        $command = substr($match, 2, -2);
68b82f24afSAndreas Gohr
69b82f24afSAndreas Gohr        // title
705895dcbaSAndreas Gohr        [$command, $title] = sexplode('|', $command, 2, '');
71b82f24afSAndreas Gohr        $title = trim($title);
72b82f24afSAndreas Gohr
73b82f24afSAndreas Gohr        // alignment
74b82f24afSAndreas Gohr        $align = 0;
755895dcbaSAndreas Gohr        if (substr($command, 0, 1) == ' ') ++$align;
76b82f24afSAndreas Gohr        if (substr($command, -1) == ' ') $align += 2;
77b82f24afSAndreas Gohr        $command = trim($command);
78b82f24afSAndreas Gohr
79b82f24afSAndreas Gohr        // get site and video
805895dcbaSAndreas Gohr        [$site, $vid] = explode('>', $command);
81b82f24afSAndreas Gohr        if (!$this->sites[$site]) return null; // unknown site
82b82f24afSAndreas Gohr        if (!$vid) return null; // no video!?
83b82f24afSAndreas Gohr
84b82f24afSAndreas Gohr        // what size?
855895dcbaSAndreas Gohr        [$vid, $pstr] = sexplode('?', $vid, 2, '');
86b82f24afSAndreas Gohr        parse_str($pstr, $userparams);
875895dcbaSAndreas Gohr        [$width, $height] = $this->parseSize($userparams);
88b82f24afSAndreas Gohr
89b82f24afSAndreas Gohr        // get URL
90b82f24afSAndreas Gohr        $url = $this->insertPlaceholders($this->sites[$site]['url'], $vid, $width, $height);
915895dcbaSAndreas Gohr        [$url, $urlpstr] = sexplode('?', $url, 2, '');
92b82f24afSAndreas Gohr        parse_str($urlpstr, $urlparams);
93b82f24afSAndreas Gohr
94b82f24afSAndreas Gohr        // merge parameters
95b82f24afSAndreas Gohr        $params = array_merge($urlparams, $userparams);
96b82f24afSAndreas Gohr        $url = $url . '?' . buildURLparams($params, '&');
97b82f24afSAndreas Gohr
985895dcbaSAndreas Gohr        return [
99b82f24afSAndreas Gohr            'site' => $site,
100b82f24afSAndreas Gohr            'domain' => parse_url($url, PHP_URL_HOST),
101b82f24afSAndreas Gohr            'video' => $vid,
102b82f24afSAndreas Gohr            'url' => $url,
103b82f24afSAndreas Gohr            'align' => $this->alignments[$align],
104b82f24afSAndreas Gohr            'width' => $width,
105b82f24afSAndreas Gohr            'height' => $height,
1065895dcbaSAndreas Gohr            'title' => $title
1075895dcbaSAndreas Gohr        ];
108b82f24afSAndreas Gohr    }
109b82f24afSAndreas Gohr
110b82f24afSAndreas Gohr    /** @inheritdoc */
111b82f24afSAndreas Gohr    public function render($mode, Doku_Renderer $R, $data)
112b82f24afSAndreas Gohr    {
113b82f24afSAndreas Gohr        if ($mode != 'xhtml') return false;
114b82f24afSAndreas Gohr        if (is_null($data)) return false;
115b82f24afSAndreas Gohr
116b82f24afSAndreas Gohr        if (is_a($R, 'renderer_plugin_dw2pdf')) {
117b82f24afSAndreas Gohr            $R->doc .= $this->pdf($data);
118b82f24afSAndreas Gohr        } else {
119b82f24afSAndreas Gohr            $R->doc .= $this->iframe($data, $this->getConf('gdpr') ? 'div' : 'iframe');
120b82f24afSAndreas Gohr        }
121b82f24afSAndreas Gohr        return true;
122b82f24afSAndreas Gohr    }
123b82f24afSAndreas Gohr
124b82f24afSAndreas Gohr    /**
125b82f24afSAndreas Gohr     * Prepare the HTML for output of the embed iframe
126b82f24afSAndreas Gohr     * @param array $data
127b82f24afSAndreas Gohr     * @param string $element Can be used to not directly embed the iframe
128b82f24afSAndreas Gohr     * @return string
129b82f24afSAndreas Gohr     */
130b82f24afSAndreas Gohr    public function iframe($data, $element = 'iframe')
131b82f24afSAndreas Gohr    {
132548f7f69SAndreas Gohr        $attributes = [
133b82f24afSAndreas Gohr            'src' => $data['url'],
134b82f24afSAndreas Gohr            'width' => $data['width'],
135b82f24afSAndreas Gohr            'height' => $data['height'],
136b82f24afSAndreas Gohr            'style' => $this->sizeToStyle($data['width'], $data['height']),
137b82f24afSAndreas Gohr            'class' => 'vshare vshare__' . $data['align'],
138b82f24afSAndreas Gohr            'allowfullscreen' => '',
139b82f24afSAndreas Gohr            'frameborder' => 0,
140b82f24afSAndreas Gohr            'scrolling' => 'no',
141b82f24afSAndreas Gohr            'data-domain' => $data['domain'],
142e7e00d33SAndreas Gohr            'loading' => 'lazy',
143548f7f69SAndreas Gohr        ];
144548f7f69SAndreas Gohr        if ($this->getConf('extrahard')) {
145548f7f69SAndreas Gohr            $attributes = array_merge($attributes, $this->hardenedIframeAttributes());
146548f7f69SAndreas Gohr        }
147548f7f69SAndreas Gohr
148548f7f69SAndreas Gohr        return "<$element "
149548f7f69SAndreas Gohr            . buildAttributes($attributes)
150b82f24afSAndreas Gohr            . '><h3>' . hsc($data['title']) . "</h3></$element>";
151b82f24afSAndreas Gohr    }
152b82f24afSAndreas Gohr
153b82f24afSAndreas Gohr    /**
154b82f24afSAndreas Gohr     * Create a style attribute for the given size
155b82f24afSAndreas Gohr     *
156b82f24afSAndreas Gohr     * @param int|string $width
157b82f24afSAndreas Gohr     * @param int|string $height
158b82f24afSAndreas Gohr     * @return string
159b82f24afSAndreas Gohr     */
160b82f24afSAndreas Gohr    public function sizeToStyle($width, $height)
161b82f24afSAndreas Gohr    {
162b82f24afSAndreas Gohr        // no unit? use px
163b82f24afSAndreas Gohr        if ($width && $width == (int)$width) {
1645895dcbaSAndreas Gohr            $width .= 'px';
165b82f24afSAndreas Gohr        }
166b82f24afSAndreas Gohr        // no unit? use px
167b82f24afSAndreas Gohr        if ($height && $height == (int)$height) {
1685895dcbaSAndreas Gohr            $height .= 'px';
169b82f24afSAndreas Gohr        }
170b82f24afSAndreas Gohr
171b82f24afSAndreas Gohr        $style = '';
172b82f24afSAndreas Gohr        if ($width) $style .= 'width:' . $width . ';';
173b82f24afSAndreas Gohr        if ($height) $style .= 'height:' . $height . ';';
174b82f24afSAndreas Gohr        return $style;
175b82f24afSAndreas Gohr    }
176b82f24afSAndreas Gohr
177b82f24afSAndreas Gohr    /**
178b82f24afSAndreas Gohr     * Prepare the HTML for output in PDF exports
179b82f24afSAndreas Gohr     *
180b82f24afSAndreas Gohr     * @param array $data
181b82f24afSAndreas Gohr     * @return string
182b82f24afSAndreas Gohr     */
183b82f24afSAndreas Gohr    public function pdf($data)
184b82f24afSAndreas Gohr    {
185b82f24afSAndreas Gohr        $html = '<div class="vshare vshare__' . $data['align'] . '"
186b82f24afSAndreas Gohr                      width="' . $data['width'] . '"
187b82f24afSAndreas Gohr                      height="' . $data['height'] . '">';
188b82f24afSAndreas Gohr
189b82f24afSAndreas Gohr        $html .= '<a href="' . $data['url'] . '" class="vshare">';
190b82f24afSAndreas Gohr        $html .= '<img src="' . DOKU_BASE . 'lib/plugins/vshare/video.png" />';
191b82f24afSAndreas Gohr        $html .= '</a>';
192b82f24afSAndreas Gohr
193b82f24afSAndreas Gohr        $html .= '<br />';
194b82f24afSAndreas Gohr
195b82f24afSAndreas Gohr        $html .= '<a href="' . $data['url'] . '" class="vshare">';
196b82f24afSAndreas Gohr        $html .= ($data['title'] ? hsc($data['title']) : 'Video');
197b82f24afSAndreas Gohr        $html .= '</a>';
198b82f24afSAndreas Gohr
199b82f24afSAndreas Gohr        $html .= '</div>';
200b82f24afSAndreas Gohr
201b82f24afSAndreas Gohr        return $html;
202b82f24afSAndreas Gohr    }
203b82f24afSAndreas Gohr
204b82f24afSAndreas Gohr    /**
205b82f24afSAndreas Gohr     * Fill the placeholders in the given URL
206b82f24afSAndreas Gohr     *
207b82f24afSAndreas Gohr     * @param string $url
208b82f24afSAndreas Gohr     * @param string $vid
209b82f24afSAndreas Gohr     * @param int|string $width
210b82f24afSAndreas Gohr     * @param int|string $height
211b82f24afSAndreas Gohr     * @return string
212b82f24afSAndreas Gohr     */
213b82f24afSAndreas Gohr    public function insertPlaceholders($url, $vid, $width, $height)
214b82f24afSAndreas Gohr    {
215b82f24afSAndreas Gohr        global $INPUT;
216b82f24afSAndreas Gohr        $url = str_replace('@VIDEO@', rawurlencode($vid), $url);
217b82f24afSAndreas Gohr        $url = str_replace('@DOMAIN@', rawurlencode($INPUT->server->str('HTTP_HOST')), $url);
218b82f24afSAndreas Gohr        $url = str_replace('@WIDTH@', $width, $url);
219b82f24afSAndreas Gohr        $url = str_replace('@HEIGHT@', $height, $url);
220b82f24afSAndreas Gohr
221b82f24afSAndreas Gohr        return $url;
222b82f24afSAndreas Gohr    }
223b82f24afSAndreas Gohr
224b82f24afSAndreas Gohr    /**
225b82f24afSAndreas Gohr     * Extract the wanted size from the parameter list
226b82f24afSAndreas Gohr     *
227b82f24afSAndreas Gohr     * @param array $params
228b82f24afSAndreas Gohr     * @return int[]
229b82f24afSAndreas Gohr     */
230b82f24afSAndreas Gohr    public function parseSize(&$params)
231b82f24afSAndreas Gohr    {
2325895dcbaSAndreas Gohr        $known = implode('|', array_keys($this->sizes));
233b82f24afSAndreas Gohr
2345895dcbaSAndreas Gohr        foreach (array_keys($params) as $key) {
235b82f24afSAndreas Gohr            if (preg_match("/^((\d+)x(\d+))|($known)\$/i", $key, $m)) {
236b82f24afSAndreas Gohr                unset($params[$key]);
237b82f24afSAndreas Gohr                if (isset($m[4])) {
238b82f24afSAndreas Gohr                    return $this->sizes[strtolower($m[4])];
239b82f24afSAndreas Gohr                } else {
240b82f24afSAndreas Gohr                    return [$m[2], $m[3]];
241b82f24afSAndreas Gohr                }
242b82f24afSAndreas Gohr            }
243b82f24afSAndreas Gohr        }
244b82f24afSAndreas Gohr
245b82f24afSAndreas Gohr        // default
246b82f24afSAndreas Gohr        return $this->sizes['medium'];
247b82f24afSAndreas Gohr    }
248548f7f69SAndreas Gohr
249548f7f69SAndreas Gohr    /**
250548f7f69SAndreas Gohr     * Get additional attributes to set on the iframe to harden
251548f7f69SAndreas Gohr     *
252548f7f69SAndreas Gohr     * @link https://dustri.org/b/youtube-video-embedding-harm-reduction.html
253548f7f69SAndreas Gohr     * @return array
254548f7f69SAndreas Gohr     */
255548f7f69SAndreas Gohr    protected function hardenedIframeAttributes()
256548f7f69SAndreas Gohr    {
257548f7f69SAndreas Gohr        $disallow = [
258548f7f69SAndreas Gohr            'accelerometer',
259548f7f69SAndreas Gohr            'ambient-light-sensor',
260548f7f69SAndreas Gohr            'autoplay',
261548f7f69SAndreas Gohr            'battery',
262548f7f69SAndreas Gohr            'browsing-topics',
263548f7f69SAndreas Gohr            'camera',
264548f7f69SAndreas Gohr            'display-capture',
265548f7f69SAndreas Gohr            'domain-agent',
266548f7f69SAndreas Gohr            'document-domain',
267548f7f69SAndreas Gohr            'encrypted-media',
268548f7f69SAndreas Gohr            'execution-while-not-rendered',
269548f7f69SAndreas Gohr            'execution-while-out-of-viewport',
270548f7f69SAndreas Gohr            'gamepad',
271548f7f69SAndreas Gohr            'geolocation',
272548f7f69SAndreas Gohr            'gyroscope',
273548f7f69SAndreas Gohr            'hid',
274548f7f69SAndreas Gohr            'identity-credentials-get',
275548f7f69SAndreas Gohr            'idle-detection',
276548f7f69SAndreas Gohr            'local-fonts',
277548f7f69SAndreas Gohr            'magnetometer',
278548f7f69SAndreas Gohr            'microphone',
279548f7f69SAndreas Gohr            'midi',
280548f7f69SAndreas Gohr            'otp-credentials',
281548f7f69SAndreas Gohr            'payment',
282548f7f69SAndreas Gohr            'picture-in-picture',
283548f7f69SAndreas Gohr            'publickey-credentials-create',
284548f7f69SAndreas Gohr            'publickey-credentials-get',
285548f7f69SAndreas Gohr            'screen-wake-lock',
286548f7f69SAndreas Gohr            'serial',
287548f7f69SAndreas Gohr            'speaker-selection',
288548f7f69SAndreas Gohr            'usb',
289548f7f69SAndreas Gohr            'window-management',
290548f7f69SAndreas Gohr            'xr-spatial-tracking',
291548f7f69SAndreas Gohr        ];
292548f7f69SAndreas Gohr
2935895dcbaSAndreas Gohr        $disallow = implode('; ', array_map(static fn($v) => "$v 'none'", $disallow));
294548f7f69SAndreas Gohr
295548f7f69SAndreas Gohr        return [
296548f7f69SAndreas Gohr            'credentialless' => '',
297548f7f69SAndreas Gohr            'sandbox' => 'allow-scripts allow-same-origin',
298548f7f69SAndreas Gohr            'allow' => $disallow,
299*08b6034eSAndreas Gohr            'csp' => 'sandbox allow-scripts allow-same-origin',
300*08b6034eSAndreas Gohr            'referrerpolicy' => 'no-referrer',
301548f7f69SAndreas Gohr        ];
302548f7f69SAndreas Gohr    }
303b82f24afSAndreas Gohr}
304