feat: change log tab with filters, revision links and clear button

ChangeLog gains query()/abilities()/clear() (filters: ability, user,
result, date range - all optional and additive). LogTab/LogListTable
render the "Protokoll" admin tab: WP_List_Table with pagination, links
to the post/attachment/nav_menu edit screen by object_type, a revision
link shown only when the user may edit the revision's parent post, and
a nonce+manage_options-gated "Protokoll leeren" with a JS confirmation
and highlighted 'partial' rows.

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:
2026-09-26 04:12:13 +02:00
co-authored by Claude Opus 5.5
parent 9104989c43
commit 522f5bbd19
9 changed files with 940 additions and 0 deletions
+66
View File
@@ -126,4 +126,70 @@ final class ChangeLogTest extends TestCase
self::assertSame(3, (new ChangeLog($db))->purgeOlderThan(90));
}
public function testQueryFiltersAndPaginatesNewestFirst(): void
{
$db = Mockery::mock(\wpdb::class)->makePartial();
$db->expects('get_var')
->with(Mockery::on(static fn (string $sql): bool => $sql
=== "SELECT COUNT(*) FROM wp_lit_mcp_log WHERE 1 = 1 AND ability = 'lit/content-update'"
. " AND user_id = 3"))
->andReturn('12');
$db->expects('get_results')
->with(
Mockery::on(static fn (string $sql): bool => str_ends_with($sql, 'ORDER BY id DESC LIMIT 50 OFFSET 50')),
'ARRAY_A'
)
->andReturn([['id' => '7', 'ability' => 'lit/content-update']]);
$result = (new ChangeLog($db))->query(['ability' => 'lit/content-update', 'user_id' => 3], 50, 50);
self::assertSame(12, $result['total']);
self::assertSame('7', $result['rows'][0]['id']);
}
public function testQueryFiltersByResultAndDateRange(): void
{
$db = Mockery::mock(\wpdb::class)->makePartial();
$db->expects('get_var')
->with(Mockery::on(static fn (string $sql): bool => $sql
=== "SELECT COUNT(*) FROM wp_lit_mcp_log WHERE 1 = 1 AND result = 'partial'"
. " AND created_at >= '2026-09-01 00:00:00' AND created_at <= '2026-09-25 23:59:59'"))
->andReturn('2');
$db->expects('get_results')->andReturn([]);
$result = (new ChangeLog($db))->query(
['result' => 'partial', 'date_from' => '2026-09-01', 'date_to' => '2026-09-25'],
50,
0
);
self::assertSame(2, $result['total']);
self::assertSame([], $result['rows']);
}
public function testQueryWithoutFiltersUsesTheBaseCondition(): void
{
$db = Mockery::mock(\wpdb::class)->makePartial();
$db->expects('get_var')
->with('SELECT COUNT(*) FROM wp_lit_mcp_log WHERE 1 = 1')
->andReturn('0');
$db->expects('get_results')->andReturn(null);
$result = (new ChangeLog($db))->query([], 50, 0);
self::assertSame(0, $result['total']);
self::assertSame([], $result['rows']);
}
public function testAbilitiesAndClear(): void
{
$db = Mockery::mock(\wpdb::class)->makePartial();
$db->expects('get_col')->with('SELECT DISTINCT ability FROM wp_lit_mcp_log ORDER BY ability')->andReturn(['lit/a']);
$db->expects('query')->with('DELETE FROM wp_lit_mcp_log')->andReturn(4);
$log = new ChangeLog($db);
self::assertSame(['lit/a'], $log->abilities());
$log->clear();
}
}