Skip to content

Commit

Permalink
refact: Handle generating embed urls for oEmbeds in Municipio instead…
Browse files Browse the repository at this point in the history
… of in the iframe component (#830)

refact: Allow null value for $url
  • Loading branch information
Anna authored Jan 22, 2024
1 parent 6d78991 commit 38305c8
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 48 deletions.
18 changes: 14 additions & 4 deletions library/Content/IframePosterImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@

class IframePosterImage
{
/**
* Constructor method.
* Registers the filter for generating poster images for iframe videos.
*/
public function __construct()
{
add_filter('ComponentLibrary/Iframe/Poster', array($this, 'getPosterForIframeVideo'), 10, 1);
}

public function getPosterForIframeVideo($url) {

/**
* Generates the poster image for the given iframe video URL.
*
* @param string $url The URL of the iframe video.
* @return string The URL of the generated poster image.
*/
public function getPosterForIframeVideo(?string $url)
{
$videoService = new \Municipio\Helper\VideoService($url);
$poster = $videoService->getCoverArt();
$poster = $videoService->getCoverArt();
return $poster;
}

}
72 changes: 46 additions & 26 deletions library/Helper/VideoService.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
<?php

namespace Municipio\Helper;

use Municipio\Helper\File as FileHelper;

/**
* Class VideoService
*
* This class provides methods for getting cover images for videos from different video services.
*/
class VideoService
{
protected $url;
Expand All @@ -13,43 +19,60 @@ class VideoService
protected $fileName;
protected $filePath;
public $coverArt;

private $imageLocations = [
'youtube' => 'https://img.youtube.com/vi/%s/maxresdefault.jpg',
'vimeo' => 'https://vumbnail.com/%s.jpg'
'youtube' => 'https://img.youtube.com/vi/%s/maxresdefault.jpg',
'vimeo' => 'https://vumbnail.com/%s.jpg'
];

public function __construct(string $url)

/**
* Constructor method.
*
* @param string url The url of the video.
*/
public function __construct(?string $url = '')
{
$this->url = $url;
$this->videoId = $this->getVideoId($this->url);

$this->uploadsDir = $this->getUploadsDir();
$this->videoServiceDirname = 'video-service-thumbnails';
$this->uploadsSubDir = $this->uploadsDir . '/' . $this->videoServiceDirname;
$this->fileName = $this->videoId . '.jpg';

$this->filePath = implode("/", [
$this->uploadsSubDir,
$this->fileName
]);

$this->coverArt = $this->getCoverArt();
}
/**
* Takes a url and returns the cover image if it is available.
*
* @param string url The url of the video.
*
* @return The cover image url.
*/
public function getCoverArt(string $url = null)
{
if (empty($url)) {
$url = $this->url;
}

return $this->maybeDownloadCoverArt($url);
}
/**
* Downloads the cover art if it is available.
*
* @access private
*/
private function maybeDownloadCoverArt()
{
if (!FileHelper::fileExists($this->filePath)) {
$this->downloadCoverImage();
}

return $this->getLocalCoverUrl();
}
/**
Expand All @@ -71,7 +94,7 @@ public function detectVideoService(string $url = null)
if (str_contains($url, 'youtu')) { //Matches youtu.be and full domain
return 'youtube';
}

return false;
}
/**
Expand All @@ -88,11 +111,11 @@ public function getVideoId(string $url = null, string $videoService = null)
if (empty($url)) {
$url = $this->url;
}

if (empty($videoService)) {
$videoService = $this->detectVideoService($url);
}

if ($videoService == 'youtube') {
return $this->parseYoutubeId($url);
}
Expand All @@ -116,15 +139,13 @@ private function parseYoutubeId()
$url = $this->url;
$urlParts = parse_url($url);
$hostname = $urlParts['host'];

if ($hostname == 'youtu.be') {
//https://youtu.be/ID
$id = trim(rtrim(parse_url($url, PHP_URL_PATH), "/"), "/");

} elseif (str_contains($urlParts['path'], 'embed')) {
//https://www.youtube.com/embed/ID
$id = trim(rtrim(str_replace('/embed/', '', $urlParts['path'])));

} else {
//https://www.youtube.com/watch?v=ID
parse_str(
Expand All @@ -134,7 +155,6 @@ private function parseYoutubeId()
if (isset($queryParameters['v']) && !empty($queryParameters['v'])) {
$id = $queryParameters['v'];
}

}

return $id;
Expand Down Expand Up @@ -169,7 +189,7 @@ private function downloadCoverImage()
{
$coverUrl = $this->getRemoteCoverUrl();
$fileContent = $this->readRemoteFile($coverUrl);

if ($fileContent) {
return $this->storeImage($fileContent);
}
Expand All @@ -185,13 +205,13 @@ private function storeImage($fileContent = false)
if (empty($fileContent)) {
return false;
}

$fileSystem = $this->initFileSystem();

if (!is_dir($this->uploadsSubDir)) {
$fileSystem->mkdir($this->uploadsSubDir, FS_CHMOD_DIR);
}

if (!FileHelper::fileExists($this->filePath)) {
return $fileSystem->put_contents(
$this->filePath,
Expand All @@ -211,11 +231,11 @@ private function storeImage($fileContent = false)
public function getLocalCoverUrl()
{
$fileSystem = $this->initFileSystem();

if ($fileSystem->is_file($this->filePath)) {
return wp_upload_dir(null, false)['baseurl'] . "/{$this->videoServiceDirname}/{$this->fileName}";
}

return false;
}
/**
Expand All @@ -231,15 +251,15 @@ public function getRemoteCoverUrl(string $videoId = null, string $videoService =
if (empty($videoService) || empty($videoId)) {
$url = $this->url;
}

if (empty($videoService)) {
$videoService = $this->detectVideoService($url);
}

if (empty($videoId)) {
$videoId = $this->getVideoId($url);
}

if (isset($this->imageLocations[$videoService])) {
return sprintf($this->imageLocations[$videoService], $videoId);
}
Expand Down
140 changes: 122 additions & 18 deletions library/Oembed/OembedFilters.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class OembedFilters
*/
public function __construct()
{
add_filter('oembed_dataparse', '\Municipio\Oembed\OembedFilters::oembedDataparse', 1, 3);
add_filter('oembed_dataparse', [$this,'oembedDataparse'], 1, 3);
}

/**
Expand All @@ -29,23 +29,25 @@ public function __construct()
* @param string $url The oEmbed URL.
* @return string The modified oEmbed output.
*/
public static function oembedDataparse($output, $data, $url)
public function oembedDataparse($output, $data, $url)
{
$url = $this->buildEmbedUrl($url);

if (str_contains($output, '<iframe')) {
$data->lang = [
'knownLabels' => [
'title' => __('We need your consent to continue', 'municipio'),
'info' => sprintf(__('This part of the website shows content from %s. By continuing, <a href="%s"> you are accepting GDPR and privacy policy</a>.', 'municipio'), '{SUPPLIER_WEBSITE}', '{SUPPLIER_POLICY}'),
'button' => __('I understand, continue.', 'municipio'),
],

'unknownLabels' => [
'title' => __('We need your consent to continue', 'municipio'),
'info' => sprintf(__('This part of the website shows content from another website (%s). By continuing, you are accepting GDPR and privacy policy.', 'municipio'), '{SUPPLIER_WEBSITE}'),
'button' => __('I understand, continue.', 'municipio'),
],

'infoLabel' => __('Handling of personal data', 'municipio'),
'knownLabels' => [
'title' => __('We need your consent to continue', 'municipio'),
'info' => sprintf(__('This part of the website shows content from %s. By continuing, <a href="%s"> you are accepting GDPR and privacy policy</a>.', 'municipio'), '{SUPPLIER_WEBSITE}', '{SUPPLIER_POLICY}'),
'button' => __('I understand, continue.', 'municipio'),
],

'unknownLabels' => [
'title' => __('We need your consent to continue', 'municipio'),
'info' => sprintf(__('This part of the website shows content from another website (%s). By continuing, you are accepting GDPR and privacy policy.', 'municipio'), '{SUPPLIER_WEBSITE}'),
'button' => __('I understand, continue.', 'municipio'),
],

'infoLabel' => __('Handling of personal data', 'municipio'),
];

$videoService = new \Municipio\Helper\VideoService($url);
Expand All @@ -54,12 +56,114 @@ public static function oembedDataparse($output, $data, $url)
return render_blade_view(
'partials.iframe',
[
'settings' => $data,
'src' => $url,
'poster' => $poster,
'settings' => $data,
'src' => $url,
'poster' => $poster,
]
);
}
return $output;
}
/**
* Build embed url
*
* @param string $src Arbitrary embed url
* @return string $src Correct embed url
*/
protected function buildEmbedUrl($src)
{
$srcParsed = parse_url($src);

$ytParams = 'autoplay=1&showinfo=0&rel=0&mute=1&modestbranding=1&cc_load_policy=1';

switch ($srcParsed['host']) {
case 'youtube.com':
case 'www.youtube.com':
/*
Replacing the path with /embed/ and then
adding the v query parameter to the path
and removing the v parameter from the
query string.
*/
$srcParsed['host'] = 'youtube.com';
$srcParsed['path'] = '/embed/';

if (isset($srcParsed['query'])) {
parse_str($srcParsed['query'], $query);
if (isset($query['v'])) {
$srcParsed['path'] .= $query['v'];
$srcParsed['query'] = $ytParams;
}
}
break;
case 'youtu.be':
$srcParsed['host'] = 'youtube.com';
if (isset($srcParsed['path'])) {
$srcParsed['path'] = '/embed/' . $srcParsed['path'];
$srcParsed['query'] = $ytParams;
}
break;
case 'vimeo.com':
case 'www.vimeo.com':
$srcParsed['host'] = 'player.vimeo.com';
if (isset($srcParsed['path'])) {
$srcParsed['path'] = '/video' . $srcParsed['path'] . "?autoplay=1&autopause=0&muted=1";
}
break;
case 'spotify.com':
case 'www.spotify.com':
case 'open.spotify.com':
$srcParsed['host'] = 'open.spotify.com';
$srcParsed['path'] = '/embed' . $srcParsed['path'];
$srcParsed['query'] = 'utm_source=oembed';
break;
case 'soundcloud.com':
case 'www.soundcloud.com':
$response = wp_remote_get("https://soundcloud.com/oembed?format=json&url={$src}");

if (is_wp_error($response)) {
return $src;
}

$apiResponse = json_decode(wp_remote_retrieve_body($response), true);

$iframeSrc = $this->extractIframeSrc($apiResponse['html'], $srcParsed);
$srcParsed = parse_url($iframeSrc);

break;
default:
break;
}

$scheme = $srcParsed['scheme'] ?? 'https';
$embedUrl = $scheme . '://' . strtolower($srcParsed['host']);

if (isset($srcParsed['path'])) {
$embedUrl .= $srcParsed['path'];
}
if (isset($srcParsed['query'])) {
$embedUrl .= '?' . $srcParsed['query'];
}

return $embedUrl;
}
/**
* Extracts the source URL from an iframe element in the given HTML.
*
* @param string $html The HTML containing the iframe element.
* @return string The source URL of the iframe.
*/
private function extractIframeSrc(string $html)
{
$dom = new \DOMDocument();
// Suppress errors due to malformed HTML
libxml_use_internal_errors(true);
$dom->loadHTML($html);
// Clear errors
libxml_clear_errors();
$xpath = new \DOMXPath($dom);
$src = $xpath->evaluate("string(//iframe/@src)");

return $src;
}
}

0 comments on commit 38305c8

Please sign in to comment.