getMimeType() === 'video/vimeo' || $file->getExtension() === 'vimeo') && $this->getOnlineMediaHelper($file) !== false; } /** * Get online media helper * * @return false|OnlineMediaHelperInterface */ protected function getOnlineMediaHelper(FileInterface $file) { if ($this->onlineMediaHelper === null) { $orgFile = $file; if ($orgFile instanceof FileReference) { $orgFile = $orgFile->getOriginalFile(); } if ($orgFile instanceof File) { $this->onlineMediaHelper = GeneralUtility::makeInstance(OnlineMediaHelperRegistry::class)->getOnlineMediaHelper($orgFile); } else { $this->onlineMediaHelper = false; } } return $this->onlineMediaHelper; } /** * Render for given File(Reference) html output * * @param int|string $width TYPO3 known format; examples: 220, 200m or 200c * @param int|string $height TYPO3 known format; examples: 220, 200m or 200c */ public function render(FileInterface $file, int|string $width, int|string $height, array $options = []): string { $options = $this->collectOptions($options, $file); $src = $this->createVimeoUrl($options, $file); if ($src === '') { return ''; } $attributes = $this->collectIframeAttributes($width, $height, $options); return sprintf( '', $options['srcAttribute'] ?? 'src', htmlspecialchars($src, ENT_QUOTES | ENT_HTML5), empty($attributes) ? '' : ' ' . $this->implodeAttributes($attributes) ); } /** * @return array */ protected function collectOptions(array $options, FileInterface $file) { // Check for an autoplay option at the file reference itself, if not overridden yet. if (!isset($options['autoplay']) && $file instanceof FileReference) { $autoplay = $file->getProperty('autoplay'); if ($autoplay !== null) { $options['autoplay'] = $autoplay; } } if (!isset($options['allow'])) { $options['allow'] = 'fullscreen'; if (!empty($options['autoplay'])) { $options['allow'] = 'autoplay; fullscreen'; } } return $options; } protected function createVimeoUrl(array $options, FileInterface $file): string { $videoIdRaw = $this->getVideoIdFromFile($file); $videoIdRaw = GeneralUtility::trimExplode('/', $videoIdRaw, true); $videoId = $videoIdRaw[0] ?? ''; if (empty($videoId)) { return ''; } $hash = $videoIdRaw[1] ?? null; $urlParams = []; if (!empty($hash)) { $urlParams[] = 'h=' . $hash; } if (!empty($options['autoplay'])) { $urlParams[] = 'autoplay=1'; // If autoplay is enabled, enforce muted=1, see https://developer.chrome.com/blog/autoplay/ $urlParams[] = 'muted=1'; } if (!empty($options['loop'])) { $urlParams[] = 'loop=1'; } if (!empty($options['background'])) { $urlParams[] = 'background=1'; } if (isset($options['api']) && (int)$options['api'] === 1) { $urlParams[] = 'api=1'; } if (!isset($options['no-cookie']) || !empty($options['no-cookie'])) { $urlParams[] = 'dnt=1'; } $urlParams[] = 'title=' . (int)!empty($options['showinfo']); $urlParams[] = 'byline=' . (int)!empty($options['showinfo']); $urlParams[] = 'portrait=0'; return sprintf('https://player.vimeo.com/video/%s?%s', $videoId, implode('&', $urlParams)); } /** * @return string */ protected function getVideoIdFromFile(FileInterface $file) { if ($file instanceof FileReference) { $orgFile = $file->getOriginalFile(); } else { $orgFile = $file; } return $this->getOnlineMediaHelper($file)->getOnlineMediaId($orgFile); } /** * @param int|string $width * @param int|string $height * @return array pairs of key/value; not yet html-escaped */ protected function collectIframeAttributes($width, $height, array $options) { $attributes = []; $attributes['allowfullscreen'] = true; if (isset($options['additionalAttributes']) && is_array($options['additionalAttributes'])) { $attributes = array_merge($attributes, $options['additionalAttributes']); } if (isset($options['data']) && is_array($options['data'])) { array_walk( $options['data'], static function (string $value, string|int $key) use (&$attributes): void { $attributes['data-' . $key] = $value; } ); } if ((int)$width > 0) { $attributes['width'] = (int)$width; } if ((int)$height > 0) { $attributes['height'] = (int)$height; } if ($this->shouldIncludeFrameBorderAttribute()) { $attributes['frameborder'] = 0; } foreach (['class', 'dir', 'id', 'lang', 'style', 'title', 'accesskey', 'tabindex', 'onclick', 'allow'] as $key) { if (!empty($options[$key])) { $attributes[$key] = $options[$key]; } } return $attributes; } /** * @internal */ protected function implodeAttributes(array $attributes): string { $attributeList = []; foreach ($attributes as $name => $value) { $name = preg_replace('/[^\p{L}0-9_.-]/u', '', $name); if ($value === true) { $attributeList[] = $name; } else { $attributeList[] = $name . '="' . htmlspecialchars($value, ENT_QUOTES | ENT_HTML5) . '"'; } } return implode(' ', $attributeList); } /** * HTML5 deprecated the "frameborder" attribute as everything should be done via styling. * * @todo: This renderer has a dependency to Request / TypoScript. Model this explicitly. */ protected function shouldIncludeFrameBorderAttribute(): bool { return DocType::createFromRequest($GLOBALS['TYPO3_REQUEST'] ?? null)->shouldIncludeFrameBorderAttribute(); } }