NEW Use embed/embed v4

This commit is contained in:
Steve Boyd 2022-03-24 10:27:15 +13:00
parent 081ec34461
commit 9add508718
14 changed files with 810 additions and 248 deletions

View File

@ -3,4 +3,7 @@ Name: coreoembed
--- ---
SilverStripe\Core\Injector\Injector: SilverStripe\Core\Injector\Injector:
SilverStripe\View\Embed\Embeddable: SilverStripe\View\Embed\Embeddable:
class: SilverStripe\View\Embed\EmbedResource class: SilverStripe\View\Embed\EmbedContainer
Embed\Embed:
constructor:
- '%$Embed\Http\Crawler'

View File

@ -25,8 +25,8 @@
"bramus/monolog-colored-line-formatter": "^2", "bramus/monolog-colored-line-formatter": "^2",
"composer/installers": "^1 || ^2", "composer/installers": "^1 || ^2",
"composer/semver": "^1 || ^3", "composer/semver": "^1 || ^3",
"embed/embed": "^3",
"guzzlehttp/psr7": "^2", "guzzlehttp/psr7": "^2",
"embed/embed": "^4",
"league/csv": "^8 || ^9", "league/csv": "^8 || ^9",
"m1/env": "^2.1", "m1/env": "^2.1",
"monolog/monolog": "^1.16", "monolog/monolog": "^1.16",

View File

@ -0,0 +1,138 @@
<?php
namespace SilverStripe\View\Embed;
use Embed\Extractor;
use Embed\Embed;
use Psr\Http\Message\UriInterface;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Manifest\ModuleResourceLoader;
/**
* This class acts as a wrapper around the third party requirement embed/embed v4
*/
class EmbedContainer implements Embeddable
{
use Injectable;
private static $dependencies = [
'embed' => '%$' . Embed::class,
];
public Embed $embed;
private ?Extractor $extractor = null;
private string $url;
private array $options = [];
public function __construct(string $url)
{
$this->url = $url;
}
/**
* @return int
*/
public function getWidth()
{
$code = $this->getExtractor()->code;
return $code ? ($code->width ?: 100) : 100;
}
/**
* @return int
*/
public function getHeight()
{
$code = $this->getExtractor()->code;
return $code ? ($code->height ?: 100) : 100;
}
/**
* @return string
*/
public function getPreviewURL()
{
$extractor = $this->getExtractor();
// Use thumbnail url
if ($extractor->image) {
return (string) $extractor->image;
}
// Default media
return ModuleResourceLoader::resourceURL(
'silverstripe/asset-admin:client/dist/images/icon_file.png'
);
}
/**
* @return string
*/
public function getName()
{
$extractor = $this->getExtractor();
if ($extractor->title) {
return $extractor->title;
}
if ($extractor->url instanceof UriInterface) {
return basename($extractor->url->getPath());
}
return '';
}
/**
* @return string
*/
public function getType()
{
$html = $this->getExtractor()->code->html ?? '';
if (strpos($html, '<video') !== false) {
return 'video';
}
if (strpos($html, '<audio') !== false) {
return 'audio';
}
foreach (['iframe', 'blockquote', 'pre', 'script', 'style'] as $richTag) {
if (strpos($html, "<{$richTag}") !== false) {
return 'rich';
}
}
if (strpos($html, '<img') !== false) {
return 'photo';
}
return 'link';
}
/**
* @return bool
*/
public function validate()
{
return !empty($this->getExtractor()->code->html ?? '');
}
public function getOptions(): array
{
return $this->options;
}
public function setOptions(array $options): self
{
$this->options = $options;
return $this;
}
/**
* Calling this method will trigger the HTTP call(s) to the remote url
*/
public function getExtractor(): Extractor
{
if (!$this->extractor) {
$this->extractor = $this->embed->get($this->url);
}
return $this->extractor;
}
}

View File

@ -6,11 +6,16 @@ use Embed\Adapters\Adapter;
use Embed\Embed; use Embed\Embed;
use Embed\Http\DispatcherInterface; use Embed\Http\DispatcherInterface;
use SilverStripe\Core\Manifest\ModuleResourceLoader; use SilverStripe\Core\Manifest\ModuleResourceLoader;
use SilverStripe\Dev\Deprecation;
/** /**
* This is a deprecated class that was compatible with embed/embed v3
* This has been replaced with EmbedContainer which is embed/embed v4 compatible
*
* Encapsulation of an embed tag, linking to an external media source. * Encapsulation of an embed tag, linking to an external media source.
* *
* @see Embed * @see Embed
* @deprecated 4.11..5.0 Use EmbedContainer instead
*/ */
class EmbedResource implements Embeddable class EmbedResource implements Embeddable
{ {
@ -41,6 +46,7 @@ class EmbedResource implements Embeddable
*/ */
public function __construct($url) public function __construct($url)
{ {
Deprecation::notice('4.11', 'Use EmbedContainer instead');
$this->url = $url; $this->url = $url;
} }

View File

@ -5,7 +5,7 @@ namespace SilverStripe\View\Embed;
/** /**
* Abstract interface for an embeddable resource * Abstract interface for an embeddable resource
* *
* @see EmbedResource * @see EmbedContainer
*/ */
interface Embeddable interface Embeddable
{ {

View File

@ -2,23 +2,22 @@
namespace SilverStripe\View\Shortcodes; namespace SilverStripe\View\Shortcodes;
use Embed\Http\DispatcherInterface; use Embed\Http\NetworkException;
use Embed\Http\RequestException;
use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\CacheInterface;
use Psr\SimpleCache\InvalidArgumentException; use Psr\SimpleCache\InvalidArgumentException;
use SilverStripe\Core\Convert; use SilverStripe\Core\Convert;
use SilverStripe\Core\Extensible;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\View\ArrayData; use SilverStripe\View\ArrayData;
use SilverStripe\View\Embed\Embeddable; use SilverStripe\View\Embed\Embeddable;
use SilverStripe\View\Embed\EmbedResource;
use SilverStripe\View\HTML; use SilverStripe\View\HTML;
use SilverStripe\View\Parsers\ShortcodeHandler; use SilverStripe\View\Parsers\ShortcodeHandler;
use Embed\Adapters\Adapter;
use Embed\Exceptions\InvalidUrlException;
use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\View\Parsers\ShortcodeParser;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Dev\Deprecation;
use SilverStripe\View\Embed\EmbedContainer;
/** /**
* Provider for the [embed] shortcode tag used by the embedding service * Provider for the [embed] shortcode tag used by the embedding service
@ -84,32 +83,23 @@ class EmbedShortcodeProvider implements ShortcodeHandler
$serviceArguments['min_image_height'] = $arguments['height']; $serviceArguments['min_image_height'] = $arguments['height'];
} }
/** @var EmbedResource $embed */ /** @var EmbedContainer $embeddable */
$embed = Injector::inst()->create(Embeddable::class, $serviceURL); $embeddable = Injector::inst()->create(Embeddable::class, $serviceURL);
// Only EmbedContainer is currently supported
if (!($embeddable instanceof EmbedContainer)) {
throw new \RuntimeException('Emeddable must extend EmbedContainer');
}
if (!empty($serviceArguments)) { if (!empty($serviceArguments)) {
$embed->setOptions(array_merge($serviceArguments, (array) $embed->getOptions())); $embeddable->setOptions(array_merge($serviceArguments, (array) $embeddable->getOptions()));
}
// Allow resolver to be mocked
$dispatcher = null;
if (isset($extra['resolver'])) {
$dispatcher = Injector::inst()->create(
$extra['resolver']['class'],
$serviceURL,
$extra['resolver']['config']
);
} elseif (Injector::inst()->has(DispatcherInterface::class)) {
$dispatcher = Injector::inst()->get(DispatcherInterface::class);
}
if ($dispatcher) {
$embed->setDispatcher($dispatcher);
} }
// Process embed // Process embed
try { try {
$embed = $embed->getEmbed(); // this will trigger a request/response which will then be cached within $embeddable
} catch (InvalidUrlException $e) { $embeddable->getExtractor();
} catch (NetworkException | RequestException $e) {
$message = (Director::isDev()) $message = (Director::isDev())
? $e->getMessage() ? $e->getMessage()
: _t(__CLASS__ . '.INVALID_URL', 'There was a problem loading the media.'); : _t(__CLASS__ . '.INVALID_URL', 'There was a problem loading the media.');
@ -127,30 +117,54 @@ class EmbedShortcodeProvider implements ShortcodeHandler
} }
// Convert embed object into HTML // Convert embed object into HTML
if ($embed && $embed instanceof Adapter) { $html = static::embeddableToHtml($embeddable, $arguments);
$result = static::embedForTemplate($embed, $arguments);
}
// Fallback to link to service // Fallback to link to service
if (!$result) { if (!$html) {
$result = static::linkEmbed($arguments, $serviceURL, $serviceURL); $result = static::linkEmbed($arguments, $serviceURL, $serviceURL);
} }
// Cache result // Cache result
if ($result) { if ($html) {
try { try {
$cache->set($key, $result); $cache->set($key, $html);
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
} }
} }
return $result; return $html;
}
public static function embeddableToHtml(Embeddable $embeddable, array $arguments): string
{
// Only EmbedContainer is supported
if (!($embeddable instanceof EmbedContainer)) {
return '';
}
$extractor = $embeddable->getExtractor();
$type = $embeddable->getType();
if ($type === 'video' || $type === 'rich') {
// Attempt to inherit width (but leave height auto)
if (empty($arguments['width']) && $embeddable->getWidth()) {
$arguments['width'] = $embeddable->getWidth();
}
return static::videoEmbed($arguments, $extractor->code->html);
}
if ($type === 'photo') {
return static::photoEmbed($arguments, (string) $extractor->url);
}
if ($type === 'link') {
return static::linkEmbed($arguments, (string) $extractor->url, $extractor->title);
}
return '';
} }
/** /**
* @param Adapter $embed * @param Adapter $embed
* @param array $arguments Additional shortcode params * @param array $arguments Additional shortcode params
* @return string * @return string
* @deprecated 4.11..5.0 Use embeddableToHtml instead
*/ */
public static function embedForTemplate($embed, $arguments) public static function embedForTemplate($embed, $arguments)
{ {
Deprecation::notice('4.11', 'Use embeddableToHtml() instead');
switch ($embed->getType()) { switch ($embed->getType()) {
case 'video': case 'video':
case 'rich': case 'rich':

View File

@ -0,0 +1,134 @@
<?php
namespace SilverStripe\View\Tests\Embed;
use Embed\Extractor;
use Embed\Http\Crawler;
use SilverStripe\View\Embed\EmbedContainer;
use SilverStripe\AssetAdmin\Controller\AssetAdmin;
class EmbedContainerTest extends EmbedUnitTest
{
public function testGetDimensions()
{
$container = $this->getEmbedContainer();
$this->assertSame(480, $container->getWidth());
$this->assertSame(270, $container->getHeight());
$container = $this->getFallbackEmbedContainer();
$this->assertSame(100, $container->getWidth());
$this->assertSame(100, $container->getHeight());
}
public function testGetPreviewURL()
{
$container = $this->getEmbedContainer();
$this->assertSame('https://www.youtube.com/watch?v=iRXJXaLV0n4', $container->getPreviewURL());
$container = $this->getFallbackEmbedContainer();
if (class_exists(AssetAdmin::class)) {
$this->assertStringContainsString('client/dist/images/icon_file.png', $container->getPreviewURL());
}
}
public function testGetName()
{
$container = $this->getEmbedContainer();
$this->assertSame('Try to stay SERIOUS -The most popular CAT videos', $container->getName());
}
public function testGetType()
{
$container = $this->getEmbedContainer();
$this->assertSame('rich', $container->getType());
$container = $this->getEmbedContainer(
<<<EOT
<video width="320" height="240" controls>
<source src="movie.ogg" type="video/ogg">
Your browser does not support the video tag.
</video>
EOT
);
$this->assertSame('video', $container->getType());
$container = $this->getEmbedContainer(
<<<EOT
<audio controls>
<source src="horse.ogg" type="audio/ogg">
Your browser does not support the audio element.
</audio>
EOT
);
$this->assertSame('audio', $container->getType());
$container = $this->getEmbedContainer(
<<<EOT
<a data-flickr-embed="true" href="https://www.flickr.com/photos/philocycler/32119532132/"><img src="https://live.staticflickr.com/759/32119532132_50c3f7933f_b.jpg" width="1024" height="742" alt="bird"></a>
EOT
);
$this->assertSame('photo', $container->getType());
$container = $this->getEmbedContainer('<p>Lorem ipsum</p>');
$this->assertSame('link', $container->getType());
}
public function testValidate()
{
$container = $this->getEmbedContainer();
$this->assertTrue($container->validate());
$container = $this->getFallbackEmbedContainer();
$this->assertFalse($container->validate());
}
public function testOptions()
{
$options = ['foo' => 'bar'];
$container = $this->getEmbedContainer();
$this->assertSame([], $container->getOptions());
$container->setOptions($options);
$this->assertSame($options, $container->getOptions());
}
public function testGetExtractor()
{
$container = $this->getEmbedContainer();
$extractor = $container->getExtractor();
$this->assertTrue($extractor instanceof Extractor);
$this->assertSame('Try to stay SERIOUS -The most popular CAT videos', $extractor->title);
}
private function getFallbackEmbedContainer()
{
return $this->createEmbedContainer('', '', '', '');
}
private function getEmbedContainer(string $htmlOverride = '')
{
$html = $htmlOverride ?: implode('', [
'<iframe width="480" height="270" src="https://www.youtube.com/embed/iRXJXaLV0n4?feature=oembed" ',
'frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>'
]);
$url = 'https://www.youtube.com/watch?v=iRXJXaLV0n4';
return $this->createEmbedContainer(
$url,
$url,
implode('', [
'<html><link rel="alternate" type="application/json+oembed" ',
'href="https://www.youtube.com/oembed?format=json&amp;',
'url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DiRXJXaLV0n4" ',
'title="Try to stay SERIOUS -The most popular CAT videos"></html>'
]),
json_encode([
'author_url' => 'https://www.youtube.com/channel/UCR2KG2dK1tAkwZZjm7rAiSg',
'thumbnail_width' => 480,
'title' => 'Try to stay SERIOUS -The most popular CAT videos',
'width' => 480,
'provider_name' => 'YouTube',
'author_name' => 'Tiger Funnies',
'height' => 270,
'version' => '1.0',
'type' => 'video',
// phpcs:ignore
'html' => $html,
'provider_url' => 'https://www.youtube.com/',
'thumbnail_height' => 360,
'thumbnail_url' => 'https://i.ytimg.com/vi/iRXJXaLV0n4/hqdefault.jpg',
])
);
}
}

View File

@ -1,65 +0,0 @@
<?php
namespace SilverStripe\View\Tests\Embed;
use Embed\Adapters\Adapter;
use Embed\Http\DispatcherInterface;
use Embed\Http\Response;
use Embed\Http\Url;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\View\Embed\EmbedResource;
class EmbedResourceTest extends SapphireTest
{
public function testGetEmbed()
{
$dispatcherMock = $this->createMock(DispatcherInterface::class);
$dispatcherMock->expects($this->atLeastOnce())->method('dispatch')->willReturn($this->mockResponse());
/** @var EmbedResource $embed */
$embed = Injector::inst()->create(EmbedResource::class, 'https://www.youtube.com/watch?v=iRXJXaLV0n4');
$this->assertEmpty($embed->getOptions());
$this->assertEmpty($embed->getDispatcher());
$embed->setOptions(['foo' => 'bar']);
$embed->setDispatcher($dispatcherMock);
$adapter = $embed->getEmbed();
$this->assertInstanceOf(Adapter::class, $adapter);
$this->assertSame('Try to stay SERIOUS -The most popular CAT videos', $adapter->getTitle());
}
/**
* Generate a mock Response object suitable for Embed
*
* @return Response
*/
private function mockResponse()
{
$url = Url::create('https://www.youtube.com/watch?v=iRXJXaLV0n4');
return new Response(
$url,
$url,
200,
'application/json',
json_encode([
'author_url' => 'https://www.youtube.com/channel/UCR2KG2dK1tAkwZZjm7rAiSg',
'thumbnail_width' => 480,
'title' => 'Try to stay SERIOUS -The most popular CAT videos',
'width' => 480,
'provider_name' => 'YouTube',
'author_name' => 'Tiger Funnies',
'height' => 270,
'version' => '1.0',
'type' => 'video',
// phpcs:ignore
'html' => '<iframe width="480" height="270" src="https://www.youtube.com/embed/iRXJXaLV0n4?feature=oembed" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>',
'provider_url' => 'https://www.youtube.com/',
'thumbnail_height' => 360,
'thumbnail_url' => 'https://i.ytimg.com/vi/iRXJXaLV0n4/hqdefault.jpg',
]),
[]
);
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace SilverStripe\View\Tests\Embed;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\View\Embed\EmbedContainer;
use SilverStripe\View\Embed\Embeddable;
use Embed\Http\Crawler;
use Embed\Embed;
/**
* Special unit test class to faciliate mock embed/embed requests
*/
class EmbedUnitTest extends SapphireTest
{
private bool $firstRequest = true;
public function getFirstRequest(): bool
{
return $this->firstRequest;
}
public function setFirstRequest(bool $b): void
{
$this->firstRequest = $b;
}
protected function createEmbedContainer(
string $urlA,
string $urlB,
string $firstResponse,
string $secondResponse
): EmbedContainer {
$this->registerCrawlerService($urlA, $urlB, $firstResponse, $secondResponse);
$embedContainer = EmbedContainer::create($urlA);
return $embedContainer;
}
private function registerCrawlerService(
string $urlA,
string $urlB,
string $firstResponse,
string $secondResponse
): void {
$mockUriA = new MockUri($urlA);
$mockUriB = new MockUri($urlB);
$crawlerMock = $this->createMock(Crawler::class);
$crawlerMock->method('getResponseUri')->willReturn($mockUriA);
$crawlerMock->method('createUri')->willReturn($mockUriB);
$crawlerMock->method('sendRequest')->willReturn(new MockResponse($this, $firstResponse, $secondResponse));
$crawlerMock->method('createRequest')->willReturn(new MockRequest($this, $mockUriA));
Injector::inst()->registerService($crawlerMock, Crawler::class);
// replace the existing registered Embed singleton with a new singleton that is
// created using $crawlerMock as the the __constructor argument - see oembed.yml
$embed = Injector::inst()->create(Embed::class, $crawlerMock);
Injector::inst()->registerService($embed, Embed::class);
}
/**
* This is to prevent the following warning:
* No tests found in class "SilverStripe\View\Tests\Embed\EmbedUnitTest".
*/
public function testPass()
{
$this->assertTrue(true);
}
}

View File

@ -0,0 +1,97 @@
<?php
namespace SilverStripe\View\Tests\Embed;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\UriInterface;
use Psr\Http\Message\StreamInterface;
class MockRequest implements RequestInterface
{
private EmbedUnitTest $unitTest;
private MockUri $mockUri;
public function __construct(EmbedUnitTest $unitTest, MockUri $mockUri)
{
$this->unitTest = $unitTest;
$this->mockUri = $mockUri;
}
public function getRequestTarget()
{
}
public function getMethod()
{
}
public function getUri()
{
$this->unitTest->setFirstRequest(false);
return $this->mockUri;
}
public function getProtocolVersion()
{
}
public function getHeaders()
{
}
public function getHeader($name)
{
}
public function getHeaderLine($name)
{
}
public function getBody()
{
}
public function hasHeader($name)
{
}
public function withHeader($name, $value)
{
return $this;
}
public function withAddedHeader($name, $value)
{
return $this;
}
public function withoutHeader($name)
{
return $this;
}
public function withBody(StreamInterface $body)
{
return $this;
}
public function withProtocolVersion($version)
{
return $this;
}
public function withRequestTarget($requestTarget)
{
return $this;
}
public function withMethod($method)
{
return $this;
}
public function withUri(UriInterface $uri, $preserveHost = false)
{
return $this;
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace SilverStripe\View\Tests\Embed;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
class MockResponse implements ResponseInterface
{
private EmbedUnitTest $unitTest;
private string $firstReponse;
private string $secondResponse;
public function __construct(EmbedUnitTest $unitTest, string $firstResponse, string $secondResponse)
{
$this->unitTest = $unitTest;
$this->firstResponse = $firstResponse;
$this->secondResponse = $secondResponse;
}
public function getStatusCode()
{
return 200;
}
public function getBody()
{
// first request is to the video HTML to get to find the oembed link
// second request is to the oembed endpoint to fetch JSON
if ($this->unitTest->getFirstRequest()) {
return $this->firstResponse;
} else {
return $this->secondResponse;
}
}
public function getReasonPhrase()
{
}
public function getProtocolVersion()
{
}
public function getHeaders()
{
}
public function getHeader($name)
{
}
public function getHeaderLine($name)
{
}
public function hasHeader($name)
{
}
public function withHeader($name, $value)
{
return $this;
}
public function withAddedHeader($name, $value)
{
return $this;
}
public function withBody(StreamInterface $body)
{
return $this;
}
public function withoutHeader($name)
{
return $this;
}
public function withProtocolVersion($version)
{
return $this;
}
public function withStatus($code, $reasonPhrase = '')
{
return $this;
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace SilverStripe\View\Tests\Embed;
use Psr\Http\Message\UriInterface;
class MockUri implements UriInterface
{
private string $scheme;
private string $host;
private string $path;
private string $query;
public function __construct(string $url)
{
$p = parse_url($url);
$this->scheme = $p['scheme'] ?? '';
$this->host = $p['host'] ?? '';
$this->path = $p['path'] ?? '';
$this->query = $p['query'] ?? '';
}
public function getScheme()
{
return $this->scheme;
}
public function getHost()
{
return $this->host;
}
public function getPath()
{
return $this->path;
}
public function getQuery()
{
return $this->query;
}
public function getPort()
{
}
public function getAuthority()
{
}
public function getUserInfo()
{
}
public function getFragment()
{
}
public function withPath($path)
{
return $this;
}
public function withScheme($scheme)
{
return $this;
}
public function withUserInfo($user, $password = null)
{
return $this;
}
public function withHost($host)
{
return $this;
}
public function withPort($port)
{
return $this;
}
public function withQuery($query)
{
return $this;
}
public function withFragment($fragment)
{
return $this;
}
public function __toString()
{
$query = $this->getQuery();
return sprintf(
'%s://%s%s%s',
$this->getScheme(),
$this->getHost(),
'/' . ltrim($this->getPath(), '/'),
$query ? "?$query" : ''
);
}
}

View File

@ -6,119 +6,154 @@ use Psr\SimpleCache\CacheInterface;
use SilverStripe\View\Parsers\ShortcodeParser; use SilverStripe\View\Parsers\ShortcodeParser;
use SilverStripe\View\Shortcodes\EmbedShortcodeProvider; use SilverStripe\View\Shortcodes\EmbedShortcodeProvider;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\View\Tests\Embed\EmbedUnitTest;
/** class EmbedShortcodeProviderTest extends EmbedUnitTest
* Class EmbedShortcodeProviderTest
*
* Because Embed/Embed does not have a mockup, the tests have to run against a live environment.
* I've tried to fix it by serializing the data to a file, but to no avail.
* Any improvements on not having to call external resources are welcome.
*/
class EmbedShortcodeProviderTest extends SapphireTest
{ {
/**
* @var string test youtube. The SilverStripe Platform promotion by UncleCheese
*/
protected static $test_youtube = 'https://www.youtube.com/watch?v=dM15HfUYwF0';
/**
* @var string test Soundcloud. One of my favorite bands, Delain, Suckerpunch.
*/
protected static $test_soundcloud = 'http://soundcloud.com/napalmrecords/delain-suckerpunch';
public function assertEqualIgnoringWhitespace($a, $b, $message = '') public function assertEqualIgnoringWhitespace($a, $b, $message = '')
{ {
$this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message); $this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message);
} }
private function getShortcodeHtml(
string $urlA,
string $urlB,
string $firstResponse,
string $secondResponse,
array $arguments
): string {
$firstResponse = str_replace("\n", '', $firstResponse);
$secondResponse = str_replace("\n", '', $secondResponse);
$embedContainer = $this->createEmbedContainer($urlA, $urlB, $firstResponse, $secondResponse);
return EmbedShortcodeProvider::handle_shortcode($arguments, '', null, '', ['Embeddable' => $embedContainer]);
}
public function testYoutube() public function testYoutube()
{ {
/** @var string $result */ $url = 'https://www.youtube.com/watch?v=dM15HfUYwF0';
$result = $this->mockRequest( $html = $this->getShortcodeHtml(
$url,
$url,
<<<EOT
<link rel="alternate" type="application/json+oembed" href="https://www.youtube.com/oembed?format=json&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3Da2tDOYkFCYo" title="The flying car completes first ever inter-city flight (Official Video)">
EOT,
<<<EOT
{"title":"The flying car completes first ever inter-city flight (Official Video)","author_name":"KleinVision","author_url":"https://www.youtube.com/channel/UCCHAHvcO7KSNmgXVRIJLNkw","type":"video","height":113,"width":200,"version":"1.0","provider_name":"YouTube","provider_url":"https://www.youtube.com/","thumbnail_height":360,"thumbnail_width":480,"thumbnail_url":"https://i.ytimg.com/vi/a2tDOYkFCYo/hqdefault.jpg","html":"\u003ciframe width=\u0022200\u0022 height=\u0022113\u0022 src=\u0022https://www.youtube.com/embed/a2tDOYkFCYo?feature=oembed\u0022 frameborder=\u00220\u0022 allow=\u0022accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\u0022 allowfullscreen\u003e\u003c/iframe\u003e"}
EOT,
[ [
'url' => static::$test_youtube, 'url' => $url,
'caption' => 'A nice video', 'caption' => 'A nice video',
'width' => 777, 'width' => 777,
'height' => 437, 'height' => 437,
], ],
[
'version' => '1.0',
'provider_url' => 'https://www.youtube.com/',
'title' => 'SilverStripe Platform 2 min introduction',
'html' => '<iframe width="480" height="270" src="https://www.youtube.com/embed/dM15HfUYwF0?feature=oembed" frameborder="0" allowfullscreen></iframe>',
'provider_name' => 'YouTube',
'thumbnail_width' => 480,
'type' => 'video',
'thumbnail_url' => 'https://i.ytimg.com/vi/dM15HfUYwF0/hqdefault.jpg',
'thumbnail_height' => 360,
'width' => 480,
'author_url' => 'https://www.youtube.com/user/SilverStripe',
'author_name' => 'SilverStripe',
'height' => 270,
]
); );
$this->assertEqualIgnoringWhitespace( $this->assertEqualIgnoringWhitespace(
<<<EOS <<<EOT
<div style="width: 777px;"><iframe width="777" height="437" src="https://www.youtube.com/embed/dM15HfUYwF0?feature=oembed" frameborder="0" allowfullscreen></iframe> <div style="width:777px;"><iframe width="777" height="437" src="https://www.youtube.com/embed/a2tDOYkFCYo?feature=oembed" frameborder="0" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen></iframe><p class="caption">A nice video</p></div>
<p class="caption">A nice video</p></div> EOT,
EOS $html
,
$result
); );
} }
public function testSoundcloud() public function testSoundcloud()
{ {
/** @var string $result */ $url = 'https://soundcloud.com/napalmrecords/delain-suckerpunch';
$result = $this->mockRequest( $html = $this->getShortcodeHtml(
['url' => static::$test_soundcloud], $url,
$url,
<<<EOT
<link rel="alternate" type="text/json+oembed" href="https://soundcloud.com/oembed?url=https%3A%2F%2Fsoundcloud.com%2Fnapalmrecords%2Fdelain-suckerpunch&amp;format=json">
EOT,
<<<EOT
{"version":1.0,"type":"rich","provider_name":"SoundCloud","provider_url":"https://soundcloud.com","height":400,"width":"100%","title":"DELAIN - Suckerpunch by Napalm Records","description":"Taken from the EP \"Lunar Prelude\": https://shop.napalmrecords.com/delain","thumbnail_url":"https://i1.sndcdn.com/artworks-000143578557-af0v6l-t500x500.jpg","html":"<iframe width=\"100%\" height=\"400\" scrolling=\"no\" frameborder=\"no\" src=\"https://w.soundcloud.com/player/?visual=true&url=https%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F242518079&show_artwork=true\"></iframe>","author_name":"Napalm Records","author_url":"https://soundcloud.com/napalmrecords"}
EOT,
[ [
'version' => 1, 'url' => $url
'type' => 'rich', ],
'provider_name' => 'SoundCloud',
'provider_url' => 'http://soundcloud.com',
'height' => 400,
'width' => '100%',
'title' => 'DELAIN - Suckerpunch by Napalm Records',
'description' => 'Taken from the EP "Lunar Prelude": http://shop.napalmrecords.com/delain',
'thumbnail_url' => 'http://i1.sndcdn.com/artworks-000143578557-af0v6l-t500x500.jpg',
'html' => '<iframe width="100%" height="400" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?visual=true&url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F242518079&show_artwork=true"></iframe>',
'author_name' => 'Napalm Records',
'author_url' => 'http://soundcloud.com/napalmrecords',
]
); );
$this->assertEqualIgnoringWhitespace( $this->assertEqualIgnoringWhitespace(
<<<EOS <<<EOT
<div style="width: 100px;"><iframe width="100%" height="400" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?visual=true&url=http%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F242518079&show_artwork=true"></iframe></div> <div style="width:100px;"><iframe width="100%" height="400" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?visual=true&url=https%3A%2F%2Fapi.soundcloud.com%2Ftracks%2F242518079&show_artwork=true"></iframe></div>
EOS EOT,
, $html
$result
); );
} }
/** public function testVimeo()
* Mock an oembed request
*
* @param array $arguments Input arguments
* @param array $response JSON response body
* @return string
*/
protected function mockRequest($arguments, $response)
{ {
return EmbedShortcodeProvider::handle_shortcode( $url = 'https://vimeo.com/680885625';
$arguments, $html = $this->getShortcodeHtml(
'', $url,
null, $url,
'embed', <<<EOT
<link rel="alternate" href="https://vimeo.com/api/oembed.json?url=https%3A%2F%2Fvimeo.com%2F680885625%3Fh%3D0cadf1a475" type="application/json+oembed" title="Mount Rainier National Park - 2021 - Episode 01">
EOT,
<<<EOT
{"type":"video","version":"1.0","provider_name":"Vimeo","provider_url":"https:\/\/vimeo.com\/","title":"Mount Rainier National Park - 2021 - Episode 01","author_name":"Altered Stag Productions","author_url":"https:\/\/vimeo.com\/alteredstag","is_plus":"0","account_type":"pro","html":"<iframe src=\"https:\/\/player.vimeo.com\/video\/680885625?h=0cadf1a475&amp;app_id=122963\" width=\"640\" height=\"360\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture\" allowfullscreen title=\"Mount Rainier National Park - 2021 - Episode 01\"><\/iframe>","width":640,"height":360,"duration":60,"description":"Mount Rainier was the first national park I ever visited so it was definitely exciting to be back with refined skills and better equipment. Here is a quick cap of the trip with more segments on the way.\n\nSong: And What Now of the Birds for Ben by David Jennings - March 3, 2021.","thumbnail_url":"https:\/\/i.vimeocdn.com\/video\/1380153025-d3b1840ae521cd936bdaaafaef280b9c0634e729c6b09bca7767792b553a5220-d_640","thumbnail_width":640,"thumbnail_height":360,"thumbnail_url_with_play_button":"https:\/\/i.vimeocdn.com\/filter\/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F1380153025-d3b1840ae521cd936bdaaafaef280b9c0634e729c6b09bca7767792b553a5220-d_640&src1=http%3A%2F%2Ff.vimeocdn.com%2Fp%2Fimages%2Fcrawler_play.png","upload_date":"2022-02-23 08:54:15","video_id":680885625,"uri":"\/videos\/680885625"}
EOT,
[ [
'resolver' => [ 'url' => $url
'class' => MockResolver::class,
'config' => [
'expectedContent' => json_encode($response),
], ],
);
$this->assertEqualIgnoringWhitespace(
<<<EOT
<div style="width: 640px;"><iframe src="https://player.vimeo.com/video/680885625?h=0cadf1a475&amp;app_id=122963" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen title="Mount Rainier National Park - 2021 - Episode 01"></iframe></div>
EOT,
$html
);
}
public function testFlickr()
{
$urlA = 'https://www.flickr.com/photos/philocycler/32119532132/in/photolist-QWhZSL-DFFK9V-JcDYRD-S5ksMB-KPznfz-dT81te-2aqUUb1-Gur1ok-cgfEL1-dUu2Cv-8iqmZ9-z5ktAq-z5mCCE-9FmXnE-UH4Y1d-VZsXJn-22zGNHz-e1mzTR-22uVLSo-VJJWsE-VJJJQG-8in8np-agL5ae-9KKkAe-29if7Rt';
$urlB = 'https://live.staticflickr.com/759/32119532132_50c3f7933f_b.jpg';
$html = $this->getShortcodeHtml(
$urlA,
$urlB,
<<<EOT
<link rel="alternative" type="application/json+oembed" href="https://www.flickr.com/services/oembed?url&#x3D;https://www.flickr.com/photos/philocycler/32119532132&amp;format&#x3D;json" data-dynamic-added-by="bb44774707b5780000000000000000000000000000000" data-dynamic="true" />
EOT,
<<<EOT
{"type":"photo","flickr_type":"photo","title":"bird","author_name":"Philocycler","author_url":"https:\/\/www.flickr.com\/photos\/philocycler\/","width":1024,"height":742,"url":"https:\/\/live.staticflickr.com\/759\/32119532132_50c3f7933f_b.jpg","web_page":"https:\/\/www.flickr.com\/photos\/philocycler\/32119532132\/","thumbnail_url":"https:\/\/live.staticflickr.com\/759\/32119532132_50c3f7933f_q.jpg","thumbnail_width":150,"thumbnail_height":150,"web_page_short_url":"https:\/\/flic.kr\/p\/QWhZSL","license":"All Rights Reserved","license_id":0,"html":"<a data-flickr-embed=\"true\" href=\"https:\/\/www.flickr.com\/photos\/philocycler\/32119532132\/\" title=\"bird by Philocycler, on Flickr\"><img src=\"https:\/\/live.staticflickr.com\/759\/32119532132_50c3f7933f_b.jpg\" width=\"1024\" height=\"742\" alt=\"bird\"><\/a><script async src=\"https:\/\/embedr.flickr.com\/assets\/client-code.js\" charset=\"utf-8\"><\/script>","version":"1.0","cache_age":3600,"provider_name":"Flickr","provider_url":"https:\/\/www.flickr.com\/"}
EOT,
[
'url' => $urlB,
'width' => 1024,
'height' => 742,
'caption' => 'Birdy'
], ],
] );
$this->assertEqualIgnoringWhitespace(
<<<EOT
<div style="width:1024px;"><a data-flickr-embed="true" href="https://www.flickr.com/photos/philocycler/32119532132/" title="birdbyPhilocycler,onFlickr"><img src="https://live.staticflickr.com/759/32119532132_50c3f7933f_b.jpg" width="1024" height="742" alt="bird"></a><script asyncsrc="https://embedr.flickr.com/assets/client-code.js" charset="utf-8"></script><p class="caption">Birdy</p></div>
EOT,
$html
);
}
public function testAudio()
{
// not implemented in Silerstripe so will fallback to a link to $urlA
$urlA = 'https://www.someaudioplace.com/12345';
$urlB = 'https://www.someaudioplace.com/listen/12345';
$html = $this->getShortcodeHtml(
$urlA,
$urlB,
<<<EOT
<link rel="alternative" type="application/json+oembed" href="https://www.someaudioplace.com/oembed?a=12345" data-dynamic="true" />
EOT,
<<<EOT
{"type":"audio","title":"Some music","author_name":"bob","html":"<audio controls><source src="https://www.someaudioplace.com/listen/12345" type="audio/ogg"></audio>"}
EOT,
[
'url' => $urlB,
],
);
$this->assertEqualIgnoringWhitespace(
<<<EOT
<a href="https://www.someaudioplace.com/12345"></a>
EOT,
$html
); );
} }

View File

@ -1,63 +0,0 @@
<?php
namespace SilverStripe\View\Tests\Shortcodes;
use Embed\Http\DispatcherInterface;
use Embed\Http\ImageResponse;
use Embed\Http\Response;
use Embed\Http\Url;
use InvalidArgumentException;
class MockResolver implements DispatcherInterface
{
protected $url = null;
protected $expectedContent = null;
/**
* Constructor. Sets the url.
*
* @param string $url The url value
* @param array $config The resolver configuration
*/
public function __construct($url, array $config)
{
$this->url = $url;
if (empty($config['expectedContent'])) {
throw new InvalidArgumentException("Mock resolvers need expectedContent");
}
$this->expectedContent = $config['expectedContent'];
}
/**
* Dispatch an url.
*
* @param Url $url
*
* @return Response
*/
public function dispatch(Url $url)
{
return new Response(
$url,
$url,
200,
'application/json',
$this->expectedContent,
[],
[]
);
}
/**
* Resolve multiple image urls at once.
*
* @param Url[] $urls
*
* @return ImageResponse[]
*/
public function dispatchImages(array $urls)
{
return [];
}
}