jonnybarnes.uk/app/Services/Micropub/Data/EntryData.php
Jonny Barnes a8004f2958
Bring Micropub endpoint closer to the spec
- Detect form-encoded action=... requests, so form-encoded updates work
  and unhandled actions are no longer created as notes
- Default to h-entry when no type is given
- Read form-encoded photos from `photo` rather than `photos`
- Pass `category` through EntryData so bookmark tags are saved
- Accept single values for form-encoded multi-value properties
- Return 403 for insufficient_scope and 401 for invalid tokens
- Return 400 invalid_request for unsupported post types, update
  operations and models instead of reporting them as 500s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 22:26:41 +01:00

144 lines
5.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Micropub\Data;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class EntryData extends MicropubData
{
public function __construct(
public readonly array $tokenData,
public readonly ?string $content,
public readonly ?string $inReplyTo,
public readonly ?string $published,
public readonly mixed $location,
public readonly ?string $bookmarkOf,
public readonly ?string $likeOf,
public readonly ?array $mpSyndicateTo,
public readonly ?string $name,
public readonly ?string $description,
public readonly mixed $geo,
public readonly mixed $checkin,
public readonly mixed $syndication,
public readonly ?array $photos,
public readonly ?string $postStatus,
public readonly ?array $category,
) {}
public static function fromRequest(Request $request): static
{
if ($request->isJson()) {
$data = $request->json()->all();
$rawContent = Arr::get($data, 'properties.content.0');
$content = is_array($rawContent) ? ($rawContent['html'] ?? $rawContent['value'] ?? null) : $rawContent;
return new static(
tokenData: $data['token_data'],
content: $content,
inReplyTo: Arr::get($data, 'properties.in-reply-to.0'),
published: Arr::get($data, 'properties.published.0'),
location: self::extractLocationData($data),
bookmarkOf: Arr::get($data, 'properties.bookmark-of.0'),
likeOf: Arr::get($data, 'properties.like-of.0'),
mpSyndicateTo: Arr::get($data, 'properties.mp-syndicate-to'),
name: Arr::get($data, 'properties.name.0'),
description: Arr::get($data, 'properties.description.0'),
geo: Arr::get($data, 'properties.geo.0'),
checkin: Arr::get($data, 'properties.checkin.0'),
syndication: Arr::get($data, 'properties.syndication.0'),
photos: Arr::get($data, 'properties.photo'),
postStatus: Arr::get($data, 'properties.post-status.0'),
category: Arr::get($data, 'properties.category'),
);
}
return new static(
tokenData: $request->input('token_data'),
content: $request->input('content'),
inReplyTo: $request->input('in-reply-to'),
published: $request->input('published'),
location: $request->input('location'),
bookmarkOf: $request->input('bookmark-of'),
likeOf: $request->input('like-of'),
mpSyndicateTo: self::wrapFormValue($request->input('mp-syndicate-to')),
name: $request->input('name'),
description: $request->input('description'),
geo: $request->input('geo'),
checkin: $request->input('checkin'),
syndication: $request->input('syndication'),
photos: self::wrapFormValue($request->input('photo')),
postStatus: $request->input('post-status'),
category: self::wrapFormValue($request->input('category')),
);
}
/**
* Form-encoded multi-value properties may be sent with or without the []
* suffix, so a single value arrives as a string rather than an array.
*/
private static function wrapFormValue(mixed $value): ?array
{
return $value === null ? null : Arr::wrap($value);
}
private static function extractLocationData(array $data): array|string|null
{
if (! Arr::has($data, 'properties.location')) {
return null;
}
if (Arr::has($data, 'properties.location.0')) {
return Arr::get($data, 'properties.location.0');
}
return Arr::get($data, 'properties.location');
}
public static function fromArray(array $data): static
{
return new static(
tokenData: $data['token_data'],
content: $data['content'] ?? null,
inReplyTo: $data['in-reply-to'] ?? null,
published: $data['published'] ?? null,
location: $data['location'] ?? null,
bookmarkOf: $data['bookmark-of'] ?? null,
likeOf: $data['like-of'] ?? null,
mpSyndicateTo: $data['mp-syndicate-to'] ?? null,
name: $data['name'] ?? null,
description: $data['description'] ?? null,
geo: $data['geo'] ?? null,
checkin: $data['checkin'] ?? null,
syndication: $data['syndication'] ?? null,
photos: $data['photos'] ?? null,
postStatus: $data['post-status'] ?? null,
category: $data['category'] ?? null,
);
}
public function toArray(): array
{
return [
'token_data' => $this->tokenData,
'content' => $this->content,
'in-reply-to' => $this->inReplyTo,
'published' => $this->published,
'location' => $this->location,
'bookmark-of' => $this->bookmarkOf,
'like-of' => $this->likeOf,
'mp-syndicate-to' => $this->mpSyndicateTo,
'name' => $this->name,
'description' => $this->description,
'geo' => $this->geo,
'checkin' => $this->checkin,
'syndication' => $this->syndication,
'photos' => $this->photos,
'post-status' => $this->postStatus,
'category' => $this->category,
];
}
}