Publish per-image alt text to Facebook photos
This commit is contained in:
parent
941e517b2e
commit
2c81416bd0
2 changed files with 128 additions and 2 deletions
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
namespace App\Services\Social;
|
||||
|
||||
use App\DataTransferObjects\MediaItem;
|
||||
use App\Enums\PostPlatform\ContentType;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Exceptions\Social\ErrorCategory;
|
||||
use App\Exceptions\Social\FacebookPublishException;
|
||||
use App\Exceptions\Social\SocialPublishException;
|
||||
|
|
@ -134,6 +136,10 @@ private function publishSingleImagePost(string $pageId, string $accessToken, ?st
|
|||
$payload['message'] = $content;
|
||||
}
|
||||
|
||||
if ($alt = $this->altFor($media)) {
|
||||
$payload['alt_text_custom'] = $alt;
|
||||
}
|
||||
|
||||
$response = $this->facebookHttp()->post("{$this->baseUrl}/{$pageId}/photos", $payload);
|
||||
|
||||
if ($response->failed()) {
|
||||
|
|
@ -163,11 +169,17 @@ private function publishMultiImagePost(string $pageId, string $accessToken, ?str
|
|||
continue;
|
||||
}
|
||||
|
||||
$uploadResponse = $this->facebookHttp()->post("{$this->baseUrl}/{$pageId}/photos", [
|
||||
$uploadPayload = [
|
||||
'url' => $this->cropImageForAspectRatio($media->url, $aspectRatio),
|
||||
'published' => 'false',
|
||||
'access_token' => $accessToken,
|
||||
]);
|
||||
];
|
||||
|
||||
if ($alt = $this->altFor($media)) {
|
||||
$uploadPayload['alt_text_custom'] = $alt;
|
||||
}
|
||||
|
||||
$uploadResponse = $this->facebookHttp()->post("{$this->baseUrl}/{$pageId}/photos", $uploadPayload);
|
||||
|
||||
if ($uploadResponse->failed()) {
|
||||
Log::error('Facebook image upload failed', [
|
||||
|
|
@ -412,6 +424,16 @@ private function handleApiError(Response $response): never
|
|||
throw FacebookPublishException::fromApiResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* User-provided alt text for the image, capped to Facebook's accepted length.
|
||||
*/
|
||||
private function altFor(MediaItem $media): ?string
|
||||
{
|
||||
$alt = $media->altText();
|
||||
|
||||
return $alt === null ? null : mb_substr($alt, 0, Platform::Facebook->altTextMaxLength());
|
||||
}
|
||||
|
||||
protected function cropFailureException(string $message): SocialPublishException
|
||||
{
|
||||
return new FacebookPublishException(
|
||||
|
|
|
|||
|
|
@ -807,3 +807,107 @@ function facebookJpegBytes(int $width = 1200, int $height = 800): string
|
|||
Http::assertSent(fn ($request) => str_contains($request->url(), '/page_123/photos')
|
||||
&& $request['url'] === 'https://example.com/media/a.jpg');
|
||||
});
|
||||
|
||||
test('facebook publisher sends capped alt text on single image post', function () {
|
||||
$longAlt = str_repeat('a', 1500);
|
||||
|
||||
$this->post->update([
|
||||
'media' => [
|
||||
[
|
||||
'id' => 'test-media-id',
|
||||
'path' => 'media/2026-01/image.jpg',
|
||||
'url' => 'https://example.com/media/2026-01/image.jpg',
|
||||
'mime_type' => 'image/jpeg',
|
||||
'original_filename' => 'image.jpg',
|
||||
'meta' => ['alt_text' => $longAlt],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*/page_123/photos' => Http::response(['id' => 'photo_123', 'post_id' => 'post_123'], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
$expectedAlt = mb_substr($longAlt, 0, Platform::Facebook->altTextMaxLength());
|
||||
|
||||
Http::assertSent(function ($request) use ($expectedAlt) {
|
||||
return str_contains($request->url(), '/page_123/photos')
|
||||
&& data_get($request->data(), 'alt_text_custom') === $expectedAlt
|
||||
&& strlen($expectedAlt) === Platform::Facebook->altTextMaxLength();
|
||||
});
|
||||
});
|
||||
|
||||
test('facebook publisher sends capped alt text for each image in multi image post', function () {
|
||||
$longAlt1 = str_repeat('b', 1200);
|
||||
$longAlt2 = str_repeat('c', 1200);
|
||||
|
||||
$this->post->update([
|
||||
'media' => [
|
||||
[
|
||||
'id' => 'm1',
|
||||
'path' => 'media/2026-01/image1.jpg',
|
||||
'url' => 'https://example.com/media/2026-01/image1.jpg',
|
||||
'mime_type' => 'image/jpeg',
|
||||
'original_filename' => 'image1.jpg',
|
||||
'meta' => ['alt_text' => $longAlt1],
|
||||
],
|
||||
[
|
||||
'id' => 'm2',
|
||||
'path' => 'media/2026-01/image2.jpg',
|
||||
'url' => 'https://example.com/media/2026-01/image2.jpg',
|
||||
'mime_type' => 'image/jpeg',
|
||||
'original_filename' => 'image2.jpg',
|
||||
'meta' => ['alt_text' => $longAlt2],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*/page_123/photos' => Http::sequence()
|
||||
->push(['id' => 'photo_1'], 200)
|
||||
->push(['id' => 'photo_2'], 200),
|
||||
'*/page_123/feed' => Http::response(['id' => 'multi_post_alt'], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
$expectedAlt1 = mb_substr($longAlt1, 0, Platform::Facebook->altTextMaxLength());
|
||||
$expectedAlt2 = mb_substr($longAlt2, 0, Platform::Facebook->altTextMaxLength());
|
||||
|
||||
Http::assertSent(function ($request) use ($expectedAlt1) {
|
||||
return str_contains($request->url(), '/page_123/photos')
|
||||
&& data_get($request->data(), 'alt_text_custom') === $expectedAlt1;
|
||||
});
|
||||
|
||||
Http::assertSent(function ($request) use ($expectedAlt2) {
|
||||
return str_contains($request->url(), '/page_123/photos')
|
||||
&& data_get($request->data(), 'alt_text_custom') === $expectedAlt2;
|
||||
});
|
||||
});
|
||||
|
||||
test('facebook publisher omits alt_text_custom from photos payload when no alt text is set', function () {
|
||||
$this->post->update([
|
||||
'media' => [
|
||||
[
|
||||
'id' => 'test-media-id',
|
||||
'path' => 'media/2026-01/image.jpg',
|
||||
'url' => 'https://example.com/media/2026-01/image.jpg',
|
||||
'mime_type' => 'image/jpeg',
|
||||
'original_filename' => 'image.jpg',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
Http::fake([
|
||||
'*/page_123/photos' => Http::response(['id' => 'photo_123', 'post_id' => 'post_123'], 200),
|
||||
]);
|
||||
|
||||
$this->publisher->publish($this->postPlatform);
|
||||
|
||||
Http::assertSent(function ($request) {
|
||||
return str_contains($request->url(), '/page_123/photos')
|
||||
&& ! array_key_exists('alt_text_custom', $request->data());
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue