From 1fd289c25bc3e6ce415a7cf77fce0475fc102db3 Mon Sep 17 00:00:00 2001 From: toc-assistant Date: Sat, 7 Feb 2026 11:36:26 +0000 Subject: [PATCH 1/2] feat: improve spec completeness - Add 'additions' property to TextComponent for link ranges - Add WebP support to MultipartBuilder MIME detection - Add 'explicitContent' property to Logo component - Add ArticleThumbnail component for custom feed images - Add ArticleTitle component with enhanced formatting - Add test for TextComponent additions These changes improve Apple News Format spec compliance. --- src/Document/Components/ArticleThumbnail.php | 137 ++++++++++++++++++ src/Document/Components/ArticleTitle.php | 25 ++++ src/Document/Components/Logo.php | 22 +++ src/Document/Components/TextComponent.php | 23 +++ src/Request/MultipartBuilder.php | 1 + .../Components/TextComponentAdditionsTest.php | 41 ++++++ 6 files changed, 249 insertions(+) create mode 100644 src/Document/Components/ArticleThumbnail.php create mode 100644 src/Document/Components/ArticleTitle.php create mode 100644 tests/Document/Components/TextComponentAdditionsTest.php diff --git a/src/Document/Components/ArticleThumbnail.php b/src/Document/Components/ArticleThumbnail.php new file mode 100644 index 0000000..7bf6c7f --- /dev/null +++ b/src/Document/Components/ArticleThumbnail.php @@ -0,0 +1,137 @@ +caption = $caption; + return $this; + } + + /** + * Set the accessibility caption for VoiceOver. + * + * @param string $caption The accessibility caption. + * + * @return $this + */ + public function setAccessibilityCaption(string $caption): self + { + $this->accessibilityCaption = $caption; + return $this; + } + + /** + * Set whether the image contains explicit content. + * + * @param bool $explicit Whether the content is explicit. + * + * @return $this + */ + public function setExplicitContent(bool $explicit): self + { + $this->explicitContent = $explicit; + return $this; + } + + /** + * {@inheritdoc} + * + * @return array + */ + public function jsonSerialize(): array + { + $data = $this->getBaseProperties(); + $data['URL'] = $this->url; + + if ($this->caption !== null) { + $data['caption'] = $this->caption; + } + + if ($this->accessibilityCaption !== null) { + $data['accessibilityCaption'] = $this->accessibilityCaption; + } + + if ($this->explicitContent !== null) { + $data['explicitContent'] = $this->explicitContent; + } + + return $data; + } +} diff --git a/src/Document/Components/ArticleTitle.php b/src/Document/Components/ArticleTitle.php new file mode 100644 index 0000000..cd859d3 --- /dev/null +++ b/src/Document/Components/ArticleTitle.php @@ -0,0 +1,25 @@ +explicitContent = $explicit; + return $this; + } + /** * {@inheritdoc} * @@ -112,6 +130,10 @@ public function jsonSerialize(): array $data['accessibilityCaption'] = $this->accessibilityCaption; } + if ($this->explicitContent !== null) { + $data['explicitContent'] = $this->explicitContent; + } + return $data; } } diff --git a/src/Document/Components/TextComponent.php b/src/Document/Components/TextComponent.php index 6437119..a535fb1 100644 --- a/src/Document/Components/TextComponent.php +++ b/src/Document/Components/TextComponent.php @@ -20,6 +20,9 @@ abstract class TextComponent extends Component /** @var string|null The text format (none, html, markdown). */ protected ?string $format = null; + /** @var array>|null Additions (links) applied to text ranges. */ + protected ?array $additions = null; + /** * @param string $text The raw text content. */ @@ -61,6 +64,22 @@ public function setFormat(string $format): static return $this; } + /** + * Set additions (link ranges) for the text component. + * + * Additions allow you to apply links to specific ranges of text. + * Note: Additions are not supported when format is 'html' or 'markdown'. + * + * @param array> $additions Array of addition objects with rangeStart, rangeLength, and link. + * @return static + * @see https://developer.apple.com/documentation/applenewsformat/addition + */ + public function setAdditions(array $additions): static + { + $this->additions = $additions; + return $this; + } + /** * @return array */ @@ -81,6 +100,10 @@ public function jsonSerialize(): array $data['format'] = $this->format; } + if ($this->additions !== null) { + $data['additions'] = $this->additions; + } + return $data; } } diff --git a/src/Request/MultipartBuilder.php b/src/Request/MultipartBuilder.php index 660fbbb..392ad4b 100644 --- a/src/Request/MultipartBuilder.php +++ b/src/Request/MultipartBuilder.php @@ -210,6 +210,7 @@ private function guessImageContentType(string $filename): string 'jpg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', + 'webp' => 'image/webp', default => 'application/octet-stream', }; } diff --git a/tests/Document/Components/TextComponentAdditionsTest.php b/tests/Document/Components/TextComponentAdditionsTest.php new file mode 100644 index 0000000..f499885 --- /dev/null +++ b/tests/Document/Components/TextComponentAdditionsTest.php @@ -0,0 +1,41 @@ + 'link', + 'rangeStart' => 19, + 'rangeLength' => 4, + 'URL' => 'https://example.com', + ], + ]; + + $body->setAdditions($additions); + + $data = $body->jsonSerialize(); + + $this->assertArrayHasKey('additions', $data); + $this->assertEquals($additions, $data['additions']); + } + + public function testAdditionsNotIncludedWhenNull(): void + { + $body = new Body('Simple text without links'); + + $data = $body->jsonSerialize(); + + $this->assertArrayNotHasKey('additions', $data); + } +} From 0590d0848034533daa3cf9b01da726e1b87f9485 Mon Sep 17 00:00:00 2001 From: toc-assistant Date: Sat, 7 Feb 2026 11:39:16 +0000 Subject: [PATCH 2/2] fix: use truly unknown extension in test The testAddImageFileWithUnknownExtension was using .webp which is now a known extension after adding WebP support. Changed to .xyz which is genuinely unknown. Also added explicit test for WebP MIME type detection. --- tests/Request/MultipartBuilderFileTest.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/Request/MultipartBuilderFileTest.php b/tests/Request/MultipartBuilderFileTest.php index b7f0ec7..5418412 100644 --- a/tests/Request/MultipartBuilderFileTest.php +++ b/tests/Request/MultipartBuilderFileTest.php @@ -73,6 +73,18 @@ public function testAddImageFileWithGif(): void } public function testAddImageFileWithUnknownExtension(): void + { + $filePath = $this->tempDir . '/image.xyz'; + file_put_contents($filePath, 'fake-content'); + + $builder = new MultipartBuilder('test-boundary'); + $builder->addImageFile('unknown', $filePath); + $body = $builder->build(); + + $this->assertStringContainsString('Content-Type: application/octet-stream', $body); + } + + public function testAddImageFileWithWebpExtension(): void { $filePath = $this->tempDir . '/image.webp'; file_put_contents($filePath, 'fake-webp-content'); @@ -81,7 +93,7 @@ public function testAddImageFileWithUnknownExtension(): void $builder->addImageFile('webp', $filePath); $body = $builder->build(); - $this->assertStringContainsString('Content-Type: application/octet-stream', $body); + $this->assertStringContainsString('Content-Type: image/webp', $body); } public function testAddImageFileWithUppercaseExtension(): void