You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

304 lines
8.3 KiB

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