fix(compat): Bereinigung liegengebliebener Prüf-Einträge löschte laufende Prüfungen

Entwürfe haben in WordPress kein gültiges GMT-Datum (0000-00-00), deshalb hielt die Orphan-Bereinigung jeden markierten Prüf-Eintrag für alt und konnte eine laufende Divi-Prüfung abbrechen. Der neue Kern-Baustein ThrowawayPost setzt beim Anlegen ein echtes GMT-Datum, prüft das Alter über post_modified_gmt und kapselt Anlegen, Prüfen und Löschen (Anlegen jetzt innerhalb von try). WriteProbe nutzt ihn; Fehlermeldungen von WordPress gehen nur noch ins Error-Log. Live in DDEV belegt: frischer Eintrag bleibt, 11 Minuten alter wird gelöscht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-11-06 14:33:05 +01:00
co-authored by Claude Opus 5.5
parent 239d35c198
commit 42aa4f8dc9
4 changed files with 408 additions and 75 deletions
+17 -64
View File
@@ -5,11 +5,10 @@ declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release;
use LIT\MCP\Abilities\Errors;
use LIT\MCP\Compatibility\ThrowawayPost;
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
use LIT\MCP\Integrations\WordPress\Content\ContentComposer;
use LIT\MCP\Integrations\WordPress\Render\PageRenderer;
use LIT\MCP\Log;
use Throwable;
use WP_Error;
use WP_Post;
@@ -20,7 +19,7 @@ defined('ABSPATH') || exit;
* → Serializer → DiviSaver, with the release lock opened for this page only), checked (content, builder flags,
* Divi renders every module without a PHP warning from Divi's files) and always deleted for good – in finally,
* not to the trash, with its revisions and meta. Pages a dead check left behind carry MARKER; removeOrphans()
* deletes them. No change log entry. Not final so CompatibilityCheck can be tested with a mock.
* deletes them (both in ThrowawayPost). No change log entry. Not final so CompatibilityCheck can be tested with a mock.
*/
class WriteProbe
{
@@ -70,9 +69,6 @@ class WriteProbe
'litcheck-accordion', 'litcheck-tab', 'litcheck-icon-list',
];
/** Only pages older than this are orphans – a running check's page is younger. */
private const ORPHAN_AGE = '10 minutes ago';
/**
* @param string|null $diviDir PHP warnings from files below it fail the check; null = DiviEnvironment::themeDir().
*/
@@ -86,66 +82,28 @@ class WriteProbe
/** @return list<string> German problems; [] = green. */
public function run(): array
{
$id = wp_insert_post([
'post_type' => 'page',
'post_status' => 'draft',
'post_title' => self::TITLE . ' ' . gmdate('Y-m-d H:i'),
'post_content' => '',
'meta_input' => [self::MARKER => '1'],
], true);
if ($id instanceof WP_Error || (int) $id <= 0) {
return ['Die Testseite konnte nicht angelegt werden: '
. ($id instanceof WP_Error ? $id->get_error_message() : 'keine ID')];
}
$id = (int) $id;
try {
$problems = $this->exercise($id);
} catch (Throwable $e) {
// The report reaches MCP clients through the lock reason; the exception text stays in the log.
Log::failure('divi write test', $e);
$problems = ['Schreibtest abgebrochen (Details im PHP-Error-Log).'];
} finally {
if (! self::delete($id)) {
$problems[] = sprintf(
'Die Testseite %d konnte nicht gelöscht werden; bitte unter Seiten endgültig löschen.',
$id
);
}
}
return $problems;
return self::throwaway()->run('page', fn (int $id): array => $this->exercise($id));
}
/**
* @return list<int> IDs of check pages a dead check left behind, deleted now. Only drafts whose title starts
* with self::TITLE are considered – the marker meta alone is not enough (it is only a query hint here).
* @return list<int> IDs of check pages a dead check left behind, deleted now (ThrowawayPost::removeOrphans():
* marked drafts with the check title only).
*/
public static function removeOrphans(): array
{
$posts = get_posts([
'post_type' => 'page',
'post_status' => 'draft',
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- a handful of marked pages at most.
'meta_key' => self::MARKER,
'date_query' => [['before' => self::ORPHAN_AGE, 'column' => 'post_date_gmt']],
'fields' => 'all',
'posts_per_page' => 20,
'no_found_rows' => true,
'suppress_filters' => true,
]);
return self::throwaway()->removeOrphans('page');
}
$removed = [];
foreach (is_array($posts) ? $posts : [] as $post) {
if (! $post instanceof WP_Post || ! str_starts_with((string) $post->post_title, self::TITLE)) {
continue;
}
if (self::delete((int) $post->ID)) {
$removed[] = (int) $post->ID;
}
}
return $removed;
private static function throwaway(): ThrowawayPost
{
return new ThrowawayPost(
self::MARKER,
self::TITLE,
'Schreibtest abgebrochen (Details im PHP-Error-Log).',
'Die Testseite konnte nicht angelegt werden (Details im PHP-Error-Log).',
'Die Testseite %d konnte nicht gelöscht werden; bitte unter Seiten endgültig löschen.',
'divi write test'
);
}
/** @return list<string> */
@@ -219,9 +177,4 @@ class WriteProbe
return [$html, $warnings];
}
private static function delete(int $id): bool
{
return wp_delete_post($id, true) instanceof WP_Post;
}
}