From 1872adf1af79b375748730fd5c7e7684183458c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20de=20Oliveira?= Date: Mon, 26 Oct 2026 19:58:30 -0400 Subject: [PATCH 1/2] Build an img code for oEmbed photo responses --- src/Detectors/Code.php | 29 ++++- tests/OEmbedPhotoCodeTest.php | 106 ++++++++++++++++++ ...unka.deviantart.com.art-cope-145564099.php | 7 +- .../www.23hq.com.zzleeper-photo-16600737.php | 7 +- ...www.deviantart.com.art-misty-510056679.php | 7 +- 5 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 tests/OEmbedPhotoCodeTest.php diff --git a/src/Detectors/Code.php b/src/Detectors/Code.php index 870c82c9..994bada6 100644 --- a/src/Detectors/Code.php +++ b/src/Detectors/Code.php @@ -38,7 +38,7 @@ private function detectFromEmbed(): ?EmbedCode $html = $oembed->html('html'); if ($html === null) { - return null; + return $this->detectFromOEmbedPhoto(); } return new EmbedCode( @@ -48,6 +48,33 @@ private function detectFromEmbed(): ?EmbedCode ); } + private function detectFromOEmbedPhoto(): ?EmbedCode + { + $oembed = $this->extractor->getOEmbed(); + + if ($oembed->str('type') !== 'photo') { + return null; + } + + $url = $oembed->url('url'); + + if ($url === null) { + return null; + } + + $width = $oembed->int('width'); + $height = $oembed->int('height'); + + $code = html('img', [ + 'src' => $url, + 'alt' => $oembed->str('title'), + 'width' => $width, + 'height' => $height, + ]); + + return new EmbedCode($code, $width, $height); + } + private function detectFromOpenGraph(): ?EmbedCode { $metas = $this->extractor->getMetas(); diff --git a/tests/OEmbedPhotoCodeTest.php b/tests/OEmbedPhotoCodeTest.php new file mode 100644 index 00000000..cd53f589 --- /dev/null +++ b/tests/OEmbedPhotoCodeTest.php @@ -0,0 +1,106 @@ +embed([ + 'type' => 'photo', + 'version' => '1.0', + 'title' => 'Funny "cat" GIF', + 'url' => 'https://media.public.test/cat.gif', + 'width' => 480, + 'height' => 270, + ])->get('http://public.test/gifs/cat'); + + $code = $info->code; + $this->assertNotNull($code); + $this->assertSame( + 'Funny "cat" GIF', + $code->html + ); + $this->assertSame(480, $code->width); + $this->assertSame(270, $code->height); + } + + public function testPhotoWithoutSizeOmitsDimensions(): void + { + $info = $this->embed([ + 'type' => 'photo', + 'version' => '1.0', + 'url' => 'https://media.public.test/cat.gif', + ])->get('http://public.test/gifs/cat'); + + $code = $info->code; + $this->assertNotNull($code); + $this->assertSame('', $code->html); + $this->assertNull($code->width); + $this->assertNull($code->height); + } + + public function testHtmlStillWinsOverPhotoUrl(): void + { + $info = $this->embed([ + 'type' => 'photo', + 'version' => '1.0', + 'url' => 'https://media.public.test/cat.gif', + 'html' => '
cat
', + ])->get('http://public.test/gifs/cat'); + + $code = $info->code; + $this->assertNotNull($code); + $this->assertSame('
cat
', $code->html); + } + + public function testNonPhotoWithoutHtmlHasNoCode(): void + { + $info = $this->embed([ + 'type' => 'link', + 'version' => '1.0', + 'url' => 'https://media.public.test/cat.gif', + ])->get('http://public.test/gifs/cat'); + + $this->assertNull($info->code); + } + + /** + * @param array $oembed + */ + private function embed(array $oembed): Embed + { + $json = (string) json_encode($oembed, JSON_UNESCAPED_SLASHES); + $page = 'Page title' + .'' + .''; + + $client = new RecordingClient(static function (RequestInterface $request) use ($json, $page): ResponseInterface { + $factory = new Psr17Factory(); + $isOEmbed = $request->getUri()->getPath() === '/oembed'; + + return $factory->createResponse(200) + ->withHeader('Content-Type', $isOEmbed ? 'application/json' : 'text/html') + ->withBody($factory->createStream($isOEmbed ? $json : $page)); + }); + + $crawler = new Crawler($client); + $crawler->setUrlPolicy(UrlPolicy::default()->withResolver(static function (string $host): array { + return ['1.1.1.1']; + })); + + return new Embed($crawler); + } +} diff --git a/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php b/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php index c67c22f7..754bebcb 100644 --- a/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php +++ b/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php @@ -5,7 +5,12 @@ 'authorName' => 'pachunka', 'authorUrl' => 'https://www.deviantart.com/pachunka', 'cms' => null, - 'code' => null, + 'code' => [ + 'html' => 'Cope', + 'width' => 448, + 'height' => 672, + 'ratio' => 150.0 + ], 'description' => null, 'favicon' => 'https://st.deviantart.net/eclipse/icons/da_favicon_v2.ico', 'feeds' => [], diff --git a/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php b/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php index 04207dcb..582a593d 100644 --- a/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php +++ b/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php @@ -5,7 +5,12 @@ 'authorName' => 'Zzleeper', 'authorUrl' => 'https://www.23hq.com/Zzleeper/', 'cms' => null, - 'code' => null, + 'code' => [ + 'html' => 'På god väns inrådan kommer det även bultförband vid pilarna. Låter bra tycker jag. (Better safe than sorry)', + 'width' => 756, + 'height' => 567, + 'ratio' => 75.0 + ], 'description' => 'På god väns inrådan kommer det även bultförband vid pilarna. Låter bra tycker jag. (Better safe than sorry)', 'favicon' => 'https://www.23hq.com/favicon.ico', 'feeds' => [], diff --git a/tests/fixtures/www.deviantart.com.art-misty-510056679.php b/tests/fixtures/www.deviantart.com.art-misty-510056679.php index 040b3629..fcc56cb1 100644 --- a/tests/fixtures/www.deviantart.com.art-misty-510056679.php +++ b/tests/fixtures/www.deviantart.com.art-misty-510056679.php @@ -5,7 +5,12 @@ 'authorName' => 'sakimichan', 'authorUrl' => 'https://www.deviantart.com/sakimichan', 'cms' => null, - 'code' => null, + 'code' => [ + 'html' => 'Misty', + 'width' => 695, + 'height' => 900, + 'ratio' => 129.496 + ], 'description' => null, 'favicon' => 'https://st.deviantart.net/eclipse/icons/da_favicon_v2.ico', 'feeds' => [], From 2360fbfe75190da5376a382514f4b741c24be04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADtor=20de=20Oliveira?= Date: Tue, 27 Oct 2026 15:38:12 -0400 Subject: [PATCH 2/2] Document oEmbed photo embed code in CHANGELOG and README --- CHANGELOG.md | 5 +++++ README.md | 2 ++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9399a889..65e407fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [4.5.2] - 2026-10-27 +### Added +- oEmbed responses of type `photo` without `html` now produce an `` embed code from their `url`, `width` and `height` [#584] + ## [4.5.1] - 2026-10-26 ### Security - JSON-LD remote contexts are never fetched. A page-supplied `@context` is not requested, so it cannot bypass the URL policy or follow redirects. schema.org contexts are resolved from a bundled vocabulary; any other remote context is skipped [#573] @@ -319,6 +323,7 @@ Full library refactoring. [#571]: https://github.com/oscarotero/Embed/issues/571 [#572]: https://github.com/oscarotero/Embed/issues/572 [#573]: https://github.com/oscarotero/Embed/issues/573 +[#584]: https://github.com/php-embed/Embed/issues/584 [#574]: https://github.com/oscarotero/Embed/issues/574 [#577]: https://github.com/oscarotero/Embed/issues/577 diff --git a/README.md b/README.md index b5c323e5..7e6679d8 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,8 @@ $oembed->int('width'); //Return the value as integer $oembed->url('url'); //Return the value as full url (converts relative urls to absolutes) ``` +When an oEmbed response has type `photo` and no `html`, `$info->code` is built as an `` tag from its `url` (with `width` and `height` when available), so photo providers like GIPHY or Flickr also return embed code. + Additional oEmbed parameters (like instagrams `hidecaption`) can also be provided: ```php $embed = new Embed();