Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions src/Document/Components/ArticleThumbnail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

declare(strict_types=1);

namespace TomGould\AppleNews\Document\Components;

/**
* Article thumbnail component for custom feed images.
*
* The article_thumbnail component allows you to specify a custom image
* that appears in article feeds and tiles, separate from the main article content.
*
* @see https://developer.apple.com/documentation/applenewsformat/articlethumbnail
*/
final class ArticleThumbnail extends Component
{
/**
* The caption for the thumbnail.
*/
private ?string $caption = null;

/**
* The accessibility caption for VoiceOver users.
*/
private ?string $accessibilityCaption = null;

/**
* Whether the image contains explicit content.
*/
private ?bool $explicitContent = null;

/**
* Create a new ArticleThumbnail component.
*
* @param string $url The URL to the thumbnail image file.
*/
public function __construct(
private readonly string $url
) {
}

/**
* Create an ArticleThumbnail from a URL.
*
* @param string $url The image URL.
*
* @return self A new ArticleThumbnail instance.
*/
public static function fromUrl(string $url): self
{
return new self($url);
}

/**
* Create an ArticleThumbnail from a bundle file reference.
*
* @param string $filename The filename in the article bundle.
*
* @return self A new ArticleThumbnail instance.
*/
public static function fromBundle(string $filename): self
{
return new self('bundle://' . $filename);
}

/**
* {@inheritdoc}
*/
public function getRole(): string
{
return 'article_thumbnail';
}

/**
* Set the thumbnail caption.
*
* @param string $caption The caption text.
*
* @return $this
*/
public function setCaption(string $caption): self
{
$this->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<string, mixed>
*/
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;
}
}
25 changes: 25 additions & 0 deletions src/Document/Components/ArticleTitle.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace TomGould\AppleNews\Document\Components;

/**
* Article title component with enhanced formatting options.
*
* The article_title component is similar to the title component but offers
* additional formatting options and is specifically designed for the main
* article title that appears in feeds.
*
* @see https://developer.apple.com/documentation/applenewsformat/articletitle
*/
final class ArticleTitle extends TextComponent
{
/**
* {@inheritdoc}
*/
public function getRole(): string
{
return 'article_title';
}
}
22 changes: 22 additions & 0 deletions src/Document/Components/Logo.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ final class Logo extends Component
*/
private ?string $accessibilityCaption = null;

/**
* Whether the image contains explicit content.
*/
private ?bool $explicitContent = null;

/**
* Create a new Logo component.
*
Expand Down Expand Up @@ -91,6 +96,19 @@ public function setAccessibilityCaption(string $caption): self
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}
*
Expand All @@ -112,6 +130,10 @@ public function jsonSerialize(): array
$data['accessibilityCaption'] = $this->accessibilityCaption;
}

if ($this->explicitContent !== null) {
$data['explicitContent'] = $this->explicitContent;
}

return $data;
}
}
Expand Down
23 changes: 23 additions & 0 deletions src/Document/Components/TextComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ abstract class TextComponent extends Component
/** @var string|null The text format (none, html, markdown). */
protected ?string $format = null;

/** @var array<array<string, mixed>>|null Additions (links) applied to text ranges. */
protected ?array $additions = null;

/**
* @param string $text The raw text content.
*/
Expand Down Expand Up @@ -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<array<string, mixed>> $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<string, mixed>
*/
Expand All @@ -81,6 +100,10 @@ public function jsonSerialize(): array
$data['format'] = $this->format;
}

if ($this->additions !== null) {
$data['additions'] = $this->additions;
}

return $data;
}
}
1 change: 1 addition & 0 deletions src/Request/MultipartBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
}
Expand Down
41 changes: 41 additions & 0 deletions tests/Document/Components/TextComponentAdditionsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace TomGould\AppleNews\Tests\Document\Components;

use TomGould\AppleNews\Document\Components\Body;
use PHPUnit\Framework\TestCase;

final class TextComponentAdditionsTest extends TestCase
{
public function testAdditionsCanBeSet(): void
{
$body = new Body('Hello world, check this link!');

$additions = [
[
'type' => '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);
}
}
14 changes: 13 additions & 1 deletion tests/Request/MultipartBuilderFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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
Expand Down