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();
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(
+ '
',
+ $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
',
+ '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' => '
',
+ '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' => '
',
+ 'width' => 695,
+ 'height' => 900,
+ 'ratio' => 129.496
+ ],
'description' => null,
'favicon' => 'https://st.deviantart.net/eclipse/icons/da_favicon_v2.ico',
'feeds' => [],