fix: never publish a missed schedule, refuse trashed posts, fail on meta read errors
- PostGuard::editable() refuses trashed entries unless the caller opts in (content-get, revisions-list, post-trash); publish/schedule/unpublish/update/ restore/duplicate/replace now refuse them. - PostGuard::statusStable(): content-update, revision-restore and replace-with-copy refuse a future post whose date has passed (any save would publish it); the content-update rollback and PostCloner row writes re-check. - PostCloner::ownMeta() treats a failed SELECT as an error; replace() reads the copy before its first write. - replace-with-copy needs create_posts (backup clone) and reports backup_id in write_failed/partial_write. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -16,8 +16,17 @@ use WP_Post;
|
||||
*/
|
||||
final class PostGuard
|
||||
{
|
||||
public static function editable(mixed $postId, ContentSchema $schema, string $field = 'post_id'): WP_Post|WP_Error
|
||||
{
|
||||
/**
|
||||
* Loads an entry the current user may edit. Trashed entries are refused (every write
|
||||
* on them would be surprising: publishing, scheduling or updating a trashed entry);
|
||||
* readers and lit/post-trash pass $allowTrashed.
|
||||
*/
|
||||
public static function editable(
|
||||
mixed $postId,
|
||||
ContentSchema $schema,
|
||||
string $field = 'post_id',
|
||||
bool $allowTrashed = false
|
||||
): WP_Post|WP_Error {
|
||||
$id = self::id($postId);
|
||||
if ($id === null) {
|
||||
return Errors::invalidInput(
|
||||
@@ -35,9 +44,55 @@ final class PostGuard
|
||||
return Errors::forbidden(sprintf('Keine Berechtigung, Eintrag %d zu bearbeiten.', $id));
|
||||
}
|
||||
|
||||
if (! $allowTrashed && $post->post_status === 'trash') {
|
||||
return Errors::invalidInput(
|
||||
sprintf('Eintrag %d liegt im Papierkorb.', $id),
|
||||
[$field => 'Eintrag liegt im Papierkorb'],
|
||||
'Eintrag zuerst in WordPress wiederherstellen.'
|
||||
);
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* A scheduled entry whose publication time has passed (WordPress' cron missed it).
|
||||
* wp_update_post() publishes such an entry on any save, so writes must not touch it.
|
||||
*/
|
||||
public static function missedSchedule(WP_Post $post): bool
|
||||
{
|
||||
$gmt = (string) $post->post_date_gmt;
|
||||
if ($post->post_status !== 'future' || $gmt === '' || $gmt === '0000-00-00 00:00:00') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time = strtotime($gmt . ' UTC');
|
||||
|
||||
return $time !== false && $time <= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refuses a write that would change the status as a side effect (spec §2: status is only
|
||||
* changed on explicit request) – currently a missed schedule, which any save would publish.
|
||||
*/
|
||||
public static function statusStable(WP_Post $post, string $field = 'post_id'): ?WP_Error
|
||||
{
|
||||
if (! self::missedSchedule($post)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Errors::invalidInput(
|
||||
sprintf(
|
||||
'Eintrag %d ist geplant, der Veröffentlichungszeitpunkt ist aber schon verstrichen; jedes Speichern '
|
||||
. 'würde ihn sofort veröffentlichen. Nichts wurde gespeichert.',
|
||||
(int) $post->ID
|
||||
),
|
||||
[$field => 'geplanter Zeitpunkt verstrichen'],
|
||||
'Beim Nutzer nachfragen: jetzt veröffentlichen (lit-post-publish) oder neu planen (lit/post-schedule '
|
||||
. 'über mcp-adapter-execute-ability); danach die Änderung wiederholen.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The brief declares this `: object|WP_Error`, but `object` already covers every
|
||||
* class type, so PHP refuses `object` inside a union ("Type object cannot be
|
||||
|
||||
Reference in New Issue
Block a user