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:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace LIT\MCP\Integrations\WordPress\Status;
|
||||
|
||||
use LIT\MCP\Abilities\WriteFailed;
|
||||
use LIT\MCP\Integrations\WordPress\PostGuard;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
use WP_Error;
|
||||
@@ -79,7 +80,8 @@ class PostCloner
|
||||
|
||||
/**
|
||||
* Writes title (without " (Kopie)"), content, excerpt, meta and terms of the copy
|
||||
* into the original. Status, slug, date and author of the original stay.
|
||||
* into the original. Status, slug, date and author of the original stay. Everything is
|
||||
* read before the first write, so a failed read leaves the original untouched.
|
||||
*
|
||||
* @throws ReplaceFailed Naming the parts that were (possibly) written.
|
||||
*/
|
||||
@@ -88,14 +90,15 @@ class PostCloner
|
||||
$originalId = (int) $original->ID;
|
||||
$parts = [];
|
||||
try {
|
||||
$meta = $this->ownMeta((int) $copy->ID);
|
||||
$terms = $this->termIds($copy);
|
||||
$this->assertNoMissedSchedule($originalId);
|
||||
$parts[] = 'post';
|
||||
$this->updateRow($originalId, [
|
||||
'post_title' => self::withoutSuffix((string) $copy->post_title),
|
||||
'post_content' => (string) $copy->post_content,
|
||||
'post_excerpt' => (string) $copy->post_excerpt,
|
||||
]);
|
||||
$meta = $this->ownMeta((int) $copy->ID);
|
||||
$terms = $this->termIds($copy);
|
||||
$parts[] = 'meta';
|
||||
$this->replaceMeta($originalId, $meta);
|
||||
$parts[] = 'terms';
|
||||
@@ -225,6 +228,7 @@ class PostCloner
|
||||
*/
|
||||
private function updateRow(int $postId, array $columns): void
|
||||
{
|
||||
$this->assertNoMissedSchedule($postId);
|
||||
$result = wp_update_post(wp_slash(array_merge(['ID' => $postId], $columns)), true);
|
||||
if ($result instanceof WP_Error || ! $result) {
|
||||
$message = $result instanceof WP_Error ? $result->get_error_message() : 'unknown error';
|
||||
@@ -232,14 +236,33 @@ class PostCloner
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wp_update_post() publishes a scheduled post whose date has passed – never rewrite such a row.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function assertNoMissedSchedule(int $postId): void
|
||||
{
|
||||
$post = get_post($postId);
|
||||
if ($post instanceof WP_Post && PostGuard::missedSchedule($post)) {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Post %d not written: saving it now would publish a missed schedule.',
|
||||
$postId
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stored meta of a post without SKIP_META: meta_value strings exactly as in the database.
|
||||
* A failed query is an error – treating it as "no meta" would wipe meta on replace().
|
||||
*
|
||||
* @return array<string, list<string|null>>
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private function ownMeta(int $postId): array
|
||||
{
|
||||
$db = $this->db();
|
||||
$db->last_error = '';
|
||||
$rows = $db->get_results(
|
||||
$db->prepare(
|
||||
"SELECT meta_key, meta_value FROM {$db->postmeta} WHERE post_id = %d ORDER BY meta_id",
|
||||
@@ -247,9 +270,16 @@ class PostCloner
|
||||
),
|
||||
'ARRAY_A'
|
||||
);
|
||||
if (! is_array($rows) || (string) $db->last_error !== '') {
|
||||
throw new RuntimeException(sprintf(
|
||||
'Meta of post %d could not be read: %s',
|
||||
$postId,
|
||||
(string) $db->last_error !== '' ? (string) $db->last_error : 'no result'
|
||||
));
|
||||
}
|
||||
|
||||
$meta = [];
|
||||
foreach ((array) $rows as $row) {
|
||||
foreach ($rows as $row) {
|
||||
$key = (string) $row['meta_key'];
|
||||
if (! in_array($key, self::SKIP_META, true)) {
|
||||
$meta[$key][] = $row['meta_value'] === null ? null : (string) $row['meta_value'];
|
||||
|
||||
Reference in New Issue
Block a user