xref: /plugin/vshare/syntax/video.php (revision 548f7f6934f507677afe803134fcc97d5772b852)
1b82f24afSAndreas Gohr<?php
2b82f24afSAndreas Gohr
3b82f24afSAndreas Gohr/**
4b82f24afSAndreas Gohr * Easily embed videos from various Video Sharing sites
5b82f24afSAndreas Gohr *
6b82f24afSAndreas Gohr * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7b82f24afSAndreas Gohr * @author     Andreas Gohr <andi@splitbrain.org>
8b82f24afSAndreas Gohr */
9b82f24afSAndreas Gohrclass syntax_plugin_vshare_video extends DokuWiki_Syntax_Plugin
10b82f24afSAndreas Gohr{
11b82f24afSAndreas Gohr    protected $sites;
12b82f24afSAndreas Gohr
13b82f24afSAndreas Gohr    protected $sizes = [
14b82f24afSAndreas Gohr        'small' => [255, 143],
15b82f24afSAndreas Gohr        'medium' => [425, 239],
16b82f24afSAndreas Gohr        'large' => [520, 293],
17b82f24afSAndreas Gohr        'full' => ['100%', ''],
18b82f24afSAndreas Gohr        'half' => ['50%', ''],
19b82f24afSAndreas Gohr    ];
20b82f24afSAndreas Gohr
21b82f24afSAndreas Gohr    protected $alignments = [
22b82f24afSAndreas Gohr        0 => 'none',
23b82f24afSAndreas Gohr        1 => 'right',
24b82f24afSAndreas Gohr        2 => 'left',
25b82f24afSAndreas Gohr        3 => 'center',
26b82f24afSAndreas Gohr    ];
27b82f24afSAndreas Gohr
28b82f24afSAndreas Gohr    /**
29b82f24afSAndreas Gohr     * Constructor.
30b82f24afSAndreas Gohr     * Intitalizes the supported video sites
31b82f24afSAndreas Gohr     */
32b82f24afSAndreas Gohr    public function __construct()
33b82f24afSAndreas Gohr    {
34b82f24afSAndreas Gohr        $this->sites = helper_plugin_vshare::loadSites();
35b82f24afSAndreas Gohr    }
36b82f24afSAndreas Gohr
37b82f24afSAndreas Gohr    /** @inheritdoc */
38b82f24afSAndreas Gohr    public function getType()
39b82f24afSAndreas Gohr    {
40b82f24afSAndreas Gohr        return 'substition';
41b82f24afSAndreas Gohr    }
42b82f24afSAndreas Gohr
43b82f24afSAndreas Gohr    /** @inheritdoc */
44b82f24afSAndreas Gohr    public function getPType()
45b82f24afSAndreas Gohr    {
46b82f24afSAndreas Gohr        return 'block';
47b82f24afSAndreas Gohr    }
48b82f24afSAndreas Gohr
49b82f24afSAndreas Gohr    /** @inheritdoc */
50b82f24afSAndreas Gohr    public function getSort()
51b82f24afSAndreas Gohr    {
52b82f24afSAndreas Gohr        return 159;
53b82f24afSAndreas Gohr    }
54b82f24afSAndreas Gohr
55b82f24afSAndreas Gohr    /** @inheritdoc */
56b82f24afSAndreas Gohr    public function connectTo($mode)
57b82f24afSAndreas Gohr    {
58b82f24afSAndreas Gohr        $pattern = join('|', array_keys($this->sites));
59b82f24afSAndreas Gohr        $this->Lexer->addSpecialPattern('\{\{\s?(?:' . $pattern . ')>[^}]*\}\}', $mode, 'plugin_vshare_video');
60b82f24afSAndreas Gohr    }
61b82f24afSAndreas Gohr
62b82f24afSAndreas Gohr    /** @inheritdoc */
63b82f24afSAndreas Gohr    public function handle($match, $state, $pos, Doku_Handler $handler)
64b82f24afSAndreas Gohr    {
65b82f24afSAndreas Gohr        $command = substr($match, 2, -2);
66b82f24afSAndreas Gohr
67b82f24afSAndreas Gohr        // title
68b82f24afSAndreas Gohr        list($command, $title) = array_pad(explode('|', $command), 2, '');
69b82f24afSAndreas Gohr        $title = trim($title);
70b82f24afSAndreas Gohr
71b82f24afSAndreas Gohr        // alignment
72b82f24afSAndreas Gohr        $align = 0;
73b82f24afSAndreas Gohr        if (substr($command, 0, 1) == ' ') $align += 1;
74b82f24afSAndreas Gohr        if (substr($command, -1) == ' ') $align += 2;
75b82f24afSAndreas Gohr        $command = trim($command);
76b82f24afSAndreas Gohr
77b82f24afSAndreas Gohr        // get site and video
78b82f24afSAndreas Gohr        list($site, $vid) = explode('>', $command);
79b82f24afSAndreas Gohr        if (!$this->sites[$site]) return null; // unknown site
80b82f24afSAndreas Gohr        if (!$vid) return null; // no video!?
81b82f24afSAndreas Gohr
82b82f24afSAndreas Gohr        // what size?
83b82f24afSAndreas Gohr        list($vid, $pstr) = array_pad(explode('?', $vid, 2), 2, '');
84b82f24afSAndreas Gohr        parse_str($pstr, $userparams);
85b82f24afSAndreas Gohr        list($width, $height) = $this->parseSize($userparams);
86b82f24afSAndreas Gohr
87b82f24afSAndreas Gohr        // get URL
88b82f24afSAndreas Gohr        $url = $this->insertPlaceholders($this->sites[$site]['url'], $vid, $width, $height);
89b82f24afSAndreas Gohr        list($url, $urlpstr) = array_pad(explode('?', $url, 2), 2, '');
90b82f24afSAndreas Gohr        parse_str($urlpstr, $urlparams);
91b82f24afSAndreas Gohr
92b82f24afSAndreas Gohr        // merge parameters
93b82f24afSAndreas Gohr        $params = array_merge($urlparams, $userparams);
94b82f24afSAndreas Gohr        $url = $url . '?' . buildURLparams($params, '&');
95b82f24afSAndreas Gohr
96b82f24afSAndreas Gohr        return array(
97b82f24afSAndreas Gohr            'site' => $site,
98b82f24afSAndreas Gohr            'domain' => parse_url($url, PHP_URL_HOST),
99b82f24afSAndreas Gohr            'video' => $vid,
100b82f24afSAndreas Gohr            'url' => $url,
101b82f24afSAndreas Gohr            'align' => $this->alignments[$align],
102b82f24afSAndreas Gohr            'width' => $width,
103b82f24afSAndreas Gohr            'height' => $height,
104b82f24afSAndreas Gohr            'title' => $title,
105b82f24afSAndreas Gohr        );
106b82f24afSAndreas Gohr    }
107b82f24afSAndreas Gohr
108b82f24afSAndreas Gohr    /** @inheritdoc */
109b82f24afSAndreas Gohr    public function render($mode, Doku_Renderer $R, $data)
110b82f24afSAndreas Gohr    {
111b82f24afSAndreas Gohr        if ($mode != 'xhtml') return false;
112b82f24afSAndreas Gohr        if (is_null($data)) return false;
113b82f24afSAndreas Gohr
114b82f24afSAndreas Gohr        if (is_a($R, 'renderer_plugin_dw2pdf')) {
115b82f24afSAndreas Gohr            $R->doc .= $this->pdf($data);
116b82f24afSAndreas Gohr        } else {
117b82f24afSAndreas Gohr            $R->doc .= $this->iframe($data, $this->getConf('gdpr') ? 'div' : 'iframe');
118b82f24afSAndreas Gohr        }
119b82f24afSAndreas Gohr        return true;
120b82f24afSAndreas Gohr    }
121b82f24afSAndreas Gohr
122b82f24afSAndreas Gohr    /**
123b82f24afSAndreas Gohr     * Prepare the HTML for output of the embed iframe
124b82f24afSAndreas Gohr     * @param array $data
125b82f24afSAndreas Gohr     * @param string $element Can be used to not directly embed the iframe
126b82f24afSAndreas Gohr     * @return string
127b82f24afSAndreas Gohr     */
128b82f24afSAndreas Gohr    public function iframe($data, $element = 'iframe')
129b82f24afSAndreas Gohr    {
130*548f7f69SAndreas Gohr        $attributes = [
131b82f24afSAndreas Gohr            'src' => $data['url'],
132b82f24afSAndreas Gohr            'width' => $data['width'],
133b82f24afSAndreas Gohr            'height' => $data['height'],
134b82f24afSAndreas Gohr            'style' => $this->sizeToStyle($data['width'], $data['height']),
135b82f24afSAndreas Gohr            'class' => 'vshare vshare__' . $data['align'],
136b82f24afSAndreas Gohr            'allowfullscreen' => '',
137b82f24afSAndreas Gohr            'frameborder' => 0,
138b82f24afSAndreas Gohr            'scrolling' => 'no',
139b82f24afSAndreas Gohr            'data-domain' => $data['domain'],
140*548f7f69SAndreas Gohr            'referrerpolicy' => 'no-referrer',
141*548f7f69SAndreas Gohr        ];
142*548f7f69SAndreas Gohr        if($this->getConf('extrahard')) {
143*548f7f69SAndreas Gohr            $attributes = array_merge($attributes, $this->hardenedIframeAttributes());
144*548f7f69SAndreas Gohr        }
145*548f7f69SAndreas Gohr
146*548f7f69SAndreas Gohr        return "<$element "
147*548f7f69SAndreas Gohr            . buildAttributes($attributes)
148b82f24afSAndreas Gohr            . '><h3>' . hsc($data['title']) . "</h3></$element>";
149b82f24afSAndreas Gohr    }
150b82f24afSAndreas Gohr
151b82f24afSAndreas Gohr    /**
152b82f24afSAndreas Gohr     * Create a style attribute for the given size
153b82f24afSAndreas Gohr     *
154b82f24afSAndreas Gohr     * @param int|string $width
155b82f24afSAndreas Gohr     * @param int|string $height
156b82f24afSAndreas Gohr     * @return string
157b82f24afSAndreas Gohr     */
158b82f24afSAndreas Gohr    public function sizeToStyle($width, $height)
159b82f24afSAndreas Gohr    {
160b82f24afSAndreas Gohr        // no unit? use px
161b82f24afSAndreas Gohr        if ($width && $width == (int)$width) {
162b82f24afSAndreas Gohr            $width = $width . 'px';
163b82f24afSAndreas Gohr        }
164b82f24afSAndreas Gohr        // no unit? use px
165b82f24afSAndreas Gohr        if ($height && $height == (int)$height) {
166b82f24afSAndreas Gohr            $height = $height . 'px';
167b82f24afSAndreas Gohr        }
168b82f24afSAndreas Gohr
169b82f24afSAndreas Gohr        $style = '';
170b82f24afSAndreas Gohr        if ($width) $style .= 'width:' . $width . ';';
171b82f24afSAndreas Gohr        if ($height) $style .= 'height:' . $height . ';';
172b82f24afSAndreas Gohr        return $style;
173b82f24afSAndreas Gohr    }
174b82f24afSAndreas Gohr
175b82f24afSAndreas Gohr    /**
176b82f24afSAndreas Gohr     * Prepare the HTML for output in PDF exports
177b82f24afSAndreas Gohr     *
178b82f24afSAndreas Gohr     * @param array $data
179b82f24afSAndreas Gohr     * @return string
180b82f24afSAndreas Gohr     */
181b82f24afSAndreas Gohr    public function pdf($data)
182b82f24afSAndreas Gohr    {
183b82f24afSAndreas Gohr        $html = '<div class="vshare vshare__' . $data['align'] . '"
184b82f24afSAndreas Gohr                      width="' . $data['width'] . '"
185b82f24afSAndreas Gohr                      height="' . $data['height'] . '">';
186b82f24afSAndreas Gohr
187b82f24afSAndreas Gohr        $html .= '<a href="' . $data['url'] . '" class="vshare">';
188b82f24afSAndreas Gohr        $html .= '<img src="' . DOKU_BASE . 'lib/plugins/vshare/video.png" />';
189b82f24afSAndreas Gohr        $html .= '</a>';
190b82f24afSAndreas Gohr
191b82f24afSAndreas Gohr        $html .= '<br />';
192b82f24afSAndreas Gohr
193b82f24afSAndreas Gohr        $html .= '<a href="' . $data['url'] . '" class="vshare">';
194b82f24afSAndreas Gohr        $html .= ($data['title'] ? hsc($data['title']) : 'Video');
195b82f24afSAndreas Gohr        $html .= '</a>';
196b82f24afSAndreas Gohr
197b82f24afSAndreas Gohr        $html .= '</div>';
198b82f24afSAndreas Gohr
199b82f24afSAndreas Gohr        return $html;
200b82f24afSAndreas Gohr    }
201b82f24afSAndreas Gohr
202b82f24afSAndreas Gohr    /**
203b82f24afSAndreas Gohr     * Fill the placeholders in the given URL
204b82f24afSAndreas Gohr     *
205b82f24afSAndreas Gohr     * @param string $url
206b82f24afSAndreas Gohr     * @param string $vid
207b82f24afSAndreas Gohr     * @param int|string $width
208b82f24afSAndreas Gohr     * @param int|string $height
209b82f24afSAndreas Gohr     * @return string
210b82f24afSAndreas Gohr     */
211b82f24afSAndreas Gohr    public function insertPlaceholders($url, $vid, $width, $height)
212b82f24afSAndreas Gohr    {
213b82f24afSAndreas Gohr        global $INPUT;
214b82f24afSAndreas Gohr        $url = str_replace('@VIDEO@', rawurlencode($vid), $url);
215b82f24afSAndreas Gohr        $url = str_replace('@DOMAIN@', rawurlencode($INPUT->server->str('HTTP_HOST')), $url);
216b82f24afSAndreas Gohr        $url = str_replace('@WIDTH@', $width, $url);
217b82f24afSAndreas Gohr        $url = str_replace('@HEIGHT@', $height, $url);
218b82f24afSAndreas Gohr
219b82f24afSAndreas Gohr        return $url;
220b82f24afSAndreas Gohr    }
221b82f24afSAndreas Gohr
222b82f24afSAndreas Gohr    /**
223b82f24afSAndreas Gohr     * Extract the wanted size from the parameter list
224b82f24afSAndreas Gohr     *
225b82f24afSAndreas Gohr     * @param array $params
226b82f24afSAndreas Gohr     * @return int[]
227b82f24afSAndreas Gohr     */
228b82f24afSAndreas Gohr    public function parseSize(&$params)
229b82f24afSAndreas Gohr    {
230b82f24afSAndreas Gohr        $known = join('|', array_keys($this->sizes));
231b82f24afSAndreas Gohr
232b82f24afSAndreas Gohr        foreach ($params as $key => $value) {
233b82f24afSAndreas Gohr            if (preg_match("/^((\d+)x(\d+))|($known)\$/i", $key, $m)) {
234b82f24afSAndreas Gohr                unset($params[$key]);
235b82f24afSAndreas Gohr                if (isset($m[4])) {
236b82f24afSAndreas Gohr                    return $this->sizes[strtolower($m[4])];
237b82f24afSAndreas Gohr                } else {
238b82f24afSAndreas Gohr                    return [$m[2], $m[3]];
239b82f24afSAndreas Gohr                }
240b82f24afSAndreas Gohr            }
241b82f24afSAndreas Gohr        }
242b82f24afSAndreas Gohr
243b82f24afSAndreas Gohr        // default
244b82f24afSAndreas Gohr        return $this->sizes['medium'];
245b82f24afSAndreas Gohr    }
246*548f7f69SAndreas Gohr
247*548f7f69SAndreas Gohr    /**
248*548f7f69SAndreas Gohr     * Get additional attributes to set on the iframe to harden
249*548f7f69SAndreas Gohr     *
250*548f7f69SAndreas Gohr     * @link https://dustri.org/b/youtube-video-embedding-harm-reduction.html
251*548f7f69SAndreas Gohr     * @return array
252*548f7f69SAndreas Gohr     */
253*548f7f69SAndreas Gohr    protected function hardenedIframeAttributes()
254*548f7f69SAndreas Gohr    {
255*548f7f69SAndreas Gohr        $disallow = [
256*548f7f69SAndreas Gohr            'accelerometer',
257*548f7f69SAndreas Gohr            'ambient-light-sensor',
258*548f7f69SAndreas Gohr            'autoplay',
259*548f7f69SAndreas Gohr            'battery',
260*548f7f69SAndreas Gohr            'browsing-topics',
261*548f7f69SAndreas Gohr            'camera',
262*548f7f69SAndreas Gohr            'display-capture',
263*548f7f69SAndreas Gohr            'domain-agent',
264*548f7f69SAndreas Gohr            'document-domain',
265*548f7f69SAndreas Gohr            'encrypted-media',
266*548f7f69SAndreas Gohr            'execution-while-not-rendered',
267*548f7f69SAndreas Gohr            'execution-while-out-of-viewport',
268*548f7f69SAndreas Gohr            'gamepad',
269*548f7f69SAndreas Gohr            'geolocation',
270*548f7f69SAndreas Gohr            'gyroscope',
271*548f7f69SAndreas Gohr            'hid',
272*548f7f69SAndreas Gohr            'identity-credentials-get',
273*548f7f69SAndreas Gohr            'idle-detection',
274*548f7f69SAndreas Gohr            'local-fonts',
275*548f7f69SAndreas Gohr            'magnetometer',
276*548f7f69SAndreas Gohr            'microphone',
277*548f7f69SAndreas Gohr            'midi',
278*548f7f69SAndreas Gohr            'otp-credentials',
279*548f7f69SAndreas Gohr            'payment',
280*548f7f69SAndreas Gohr            'picture-in-picture',
281*548f7f69SAndreas Gohr            'publickey-credentials-create',
282*548f7f69SAndreas Gohr            'publickey-credentials-get',
283*548f7f69SAndreas Gohr            'screen-wake-lock',
284*548f7f69SAndreas Gohr            'serial',
285*548f7f69SAndreas Gohr            'speaker-selection',
286*548f7f69SAndreas Gohr            'usb',
287*548f7f69SAndreas Gohr            'window-management',
288*548f7f69SAndreas Gohr            'xr-spatial-tracking',
289*548f7f69SAndreas Gohr        ];
290*548f7f69SAndreas Gohr
291*548f7f69SAndreas Gohr        $disallow = join('; ', array_map(static fn($v) => "$v 'none'", $disallow));
292*548f7f69SAndreas Gohr
293*548f7f69SAndreas Gohr        return [
294*548f7f69SAndreas Gohr            'credentialless' => '',
295*548f7f69SAndreas Gohr            'sandbox' => 'allow-scripts allow-same-origin',
296*548f7f69SAndreas Gohr            'allow' => $disallow,
297*548f7f69SAndreas Gohr            'csp' => 'sandbox allow-scripts allow-same-origin'
298*548f7f69SAndreas Gohr        ];
299*548f7f69SAndreas Gohr    }
300b82f24afSAndreas Gohr}
301