'object', 'properties' => [ 'post_id' => ['type' => 'integer', 'minimum' => 1], 'date' => [ 'type' => 'string', 'pattern' => '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$', 'description' => 'JJJJ-MM-TT HH:MM in der Zeitzone der Website.', ], ], 'required' => ['post_id', 'date'], 'additionalProperties' => false, ]; } public function outputSchema(): ?array { return ToolResult::schema(); } public function annotations(): array { return Annotations::write(true); } public function isDirect(): bool { return false; } public function permission(array $input): bool|WP_Error { $post = $this->load($input); return $post instanceof WP_Error ? $post : true; } public function execute(array $input): mixed { $post = $this->load($input); if ($post instanceof WP_Error) { return $post; } $raw = (string) ($input['date'] ?? ''); $date = DateTimeImmutable::createFromFormat('!' . self::FORMAT, $raw, wp_timezone()); if ($date === false || $date->format(self::FORMAT) !== $raw) { return Errors::invalidInput('Ungültiges Datum.', ['date' => 'Format JJJJ-MM-TT HH:MM erwartet']); } // Within PUBLISH_WINDOW WordPress would publish at once instead of scheduling. if ($date->getTimestamp() <= time() + PostGuard::PUBLISH_WINDOW) { return Errors::invalidInput( 'Der Zeitpunkt muss mindestens eine Minute in der Zukunft liegen.', ['date' => 'liegt nicht (mindestens eine Minute) in der Zukunft'], 'Zum sofortigen Veröffentlichen lit/post-publish (direkt: lit-post-publish) verwenden.' ); } $postId = (int) $post->ID; $data = [ 'ID' => $postId, 'post_status' => 'future', 'post_date' => $date->format('Y-m-d H:i:s'), 'post_date_gmt' => $date->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s'), 'edit_date' => true, ]; $result = StatusWrite::run( static fn () => wp_update_post($data, true), $this->name(), $this->log, $post, 'WordPress hat beim Planen einen Fehler gemeldet.' ); if ($result instanceof WP_Error) { return Errors::invalidInput('WordPress hat das Planen abgelehnt: ' . $result->get_error_message()); } if (get_post_status($postId) !== 'future') { return Errors::unsupported( 'WordPress hat den Eintrag nicht als geplant gespeichert.', 'Eintrag in WordPress prüfen (z. B. Freigabe-Workflow eines anderen Plugins).' ); } // The status change is saved – failures from here on are logged and reported as warnings only. $warnings = $post->post_status === 'private' ? [sprintf( 'Der Eintrag war privat und wird am %s ÖFFENTLICH veröffentlicht; soll er privat bleiben, die Planung ' . 'in WordPress zurücknehmen.', $raw )] : []; $change = (new PostSaveFollowUp($this->name(), $this->log, $postId, 'die Änderung', $warnings)) ->afterSave($input, 'Planen'); $title = (string) $post->post_title; $summary = sprintf('"%s" geplant für %s', $title, $raw) . ($post->post_status === 'publish' ? ' (war veröffentlicht und ist bis dahin offline)' : ''); $change->log((string) $post->post_type, $summary); $updated = $change->reread($post) ?? $post; return $change->result($summary . '.', $updated) ->changed((string) $postId, (string) $post->post_type, 'scheduled', $title) ->urls((string) get_preview_post_link($updated), (string) get_edit_post_link($postId, 'raw')) ->toArray(); } /** @param array $input */ private function load(array $input): WP_Post|WP_Error { $post = PostGuard::editable($input['post_id'] ?? null, $this->schema); if ($post instanceof WP_Error) { return $post; } return current_user_can('publish_post', (int) $post->ID) ? $post : Errors::forbidden('Keine Berechtigung, diesen Eintrag zu veröffentlichen oder zu planen.'); } }