fix: detach meldet nicht „alles erledigt“, solange nicht lesbare Fundstellen Verweise enthalten können; PageRewriter::apply liefert hidden_count; Tests für Sperre durch nicht lesbare Seiten und post_ids über der Obergrenze
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
b805ed64dd
commit
06398242d6
@@ -223,8 +223,10 @@ final class DiviDesignDetachAbility implements Ability
|
|||||||
$target['name']
|
$target['name']
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$done = self::finished($target, $preview['skipped'], $preview['hidden_count']);
|
||||||
|
|
||||||
return PageRewriteReport::nothingToDo(
|
return PageRewriteReport::nothingToDo(
|
||||||
$summary . (self::finished($target, $preview['skipped']) ? ' ' . self::DELETE_HINT : ''),
|
$summary . ($done ? ' ' . self::DELETE_HINT : ''),
|
||||||
$preview,
|
$preview,
|
||||||
$notes
|
$notes
|
||||||
);
|
);
|
||||||
@@ -343,7 +345,8 @@ final class DiviDesignDetachAbility implements Ability
|
|||||||
if ($wrotePresets) {
|
if ($wrotePresets) {
|
||||||
$summary = $presets['summary'] . ' ' . $summary;
|
$summary = $presets['summary'] . ' ' . $summary;
|
||||||
}
|
}
|
||||||
if ($pages['remaining'] === 0 && self::finished($target, $pages['skipped'])) {
|
$done = self::finished($target, $pages['skipped'], $pages['hidden_count']);
|
||||||
|
if ($pages['remaining'] === 0 && $done) {
|
||||||
$summary .= ' ' . self::DELETE_HINT;
|
$summary .= ' ' . self::DELETE_HINT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,6 +357,7 @@ final class DiviDesignDetachAbility implements Ability
|
|||||||
->with('saved', $pages['saved'])
|
->with('saved', $pages['saved'])
|
||||||
->with('remaining', $pages['remaining'])
|
->with('remaining', $pages['remaining'])
|
||||||
->with('skipped', $pages['skipped'])
|
->with('skipped', $pages['skipped'])
|
||||||
|
->with('hidden_count', $pages['hidden_count'])
|
||||||
->with('diff', $presets['diff'] ?? [])
|
->with('diff', $presets['diff'] ?? [])
|
||||||
->with('design_rev', $presets['design_rev'] ?? null)
|
->with('design_rev', $presets['design_rev'] ?? null)
|
||||||
->toArray();
|
->toArray();
|
||||||
@@ -399,14 +403,15 @@ final class DiviDesignDetachAbility implements Ability
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether nothing is left to detach before lit/divi-presets-delete: all usages, nothing skipped.
|
* Whether nothing is left to detach before lit/divi-presets-delete: all usages, nothing skipped, and no usage
|
||||||
|
* this account may not read (delete would still answer in_use).
|
||||||
*
|
*
|
||||||
* @param array<string, mixed> $target
|
* @param array<string, mixed> $target
|
||||||
* @param list<array<string, mixed>> $skipped
|
* @param list<array<string, mixed>> $skipped
|
||||||
*/
|
*/
|
||||||
private static function finished(array $target, array $skipped): bool
|
private static function finished(array $target, array $skipped, int $hidden): bool
|
||||||
{
|
{
|
||||||
return $target['post_ids'] === null && $skipped === [];
|
return $target['post_ids'] === null && $skipped === [] && $hidden === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ final class DiviPresetsAssignAbility implements Ability
|
|||||||
->with('saved', $result['saved'])
|
->with('saved', $result['saved'])
|
||||||
->with('remaining', $result['remaining'])
|
->with('remaining', $result['remaining'])
|
||||||
->with('skipped', $result['skipped'])
|
->with('skipped', $result['skipped'])
|
||||||
|
->with('hidden_count', $result['hidden_count'])
|
||||||
->toArray();
|
->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,22 @@ final class PageRewriteReport
|
|||||||
if ($result['skipped'] !== []) {
|
if ($result['skipped'] !== []) {
|
||||||
return $summary . sprintf(' Übersprungen: %d (siehe skipped).', count($result['skipped']));
|
return $summary . sprintf(' Übersprungen: %d (siehe skipped).', count($result['skipped']));
|
||||||
}
|
}
|
||||||
|
if (($result['hidden_count'] ?? 0) > 0) {
|
||||||
|
return $summary . ' ' . self::hiddenNote($result['hidden_count']);
|
||||||
|
}
|
||||||
|
|
||||||
return $summary . ' Alle Fundstellen sind erledigt.';
|
return $summary . ' Alle Fundstellen sind erledigt.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The caveat when posts this account may not read could still carry references. */
|
||||||
|
public static function hiddenNote(int $hidden): string
|
||||||
|
{
|
||||||
|
return sprintf(
|
||||||
|
'Hinweis: %d für diesen Zugang nicht sichtbare Fundstellen können noch Verweise enthalten.',
|
||||||
|
$hidden
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $preview
|
* @param array<string, mixed> $preview
|
||||||
* @param list<string> $warnings
|
* @param list<string> $warnings
|
||||||
@@ -97,6 +109,10 @@ final class PageRewriteReport
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
'remaining' => ['type' => 'integer'],
|
'remaining' => ['type' => 'integer'],
|
||||||
|
'hidden_count' => [
|
||||||
|
'type' => 'integer',
|
||||||
|
'description' => 'Fundstellen, die dieses Konto nicht lesen darf (nicht geändert).',
|
||||||
|
],
|
||||||
'skipped' => [
|
'skipped' => [
|
||||||
'type' => 'array',
|
'type' => 'array',
|
||||||
'items' => [
|
'items' => [
|
||||||
|
|||||||
@@ -123,8 +123,9 @@ class PageRewriter
|
|||||||
* @param Closure(): NodeRewrite $rewrite A fresh strategy per post.
|
* @param Closure(): NodeRewrite $rewrite A fresh strategy per post.
|
||||||
* @param list<int>|null $postIds Only these posts; null = every usage.
|
* @param list<int>|null $postIds Only these posts; null = every usage.
|
||||||
* @return array{saved: list<array{post_id: int, revision_id: ?int, nodes: int}>, remaining: int,
|
* @return array{saved: list<array{post_id: int, revision_id: ?int, nodes: int}>, remaining: int,
|
||||||
* warnings: list<string>, skipped: list<array{post_id: int, reason: string}>}|WP_Error
|
* warnings: list<string>, skipped: list<array{post_id: int, reason: string}>, hidden_count: int}|WP_Error
|
||||||
* unsupported (above HARD_CAP), forbidden (blocked), partial_write (data.saved)
|
* hidden_count = usages this account may not read (never rewritten, as in preview(); 0 with post_ids);
|
||||||
|
* errors: unsupported (above HARD_CAP), forbidden (blocked), partial_write (data.saved)
|
||||||
* @throws WriteFailed When saving the first post fails.
|
* @throws WriteFailed When saving the first post fails.
|
||||||
* @throws DiviLocked When Divi is not released – before the first post is written.
|
* @throws DiviLocked When Divi is not released – before the first post is written.
|
||||||
*/
|
*/
|
||||||
@@ -136,7 +137,8 @@ class PageRewriter
|
|||||||
string $summary
|
string $summary
|
||||||
): array|WP_Error {
|
): array|WP_Error {
|
||||||
$ids = self::ids($postIds);
|
$ids = self::ids($postIds);
|
||||||
$refusal = $this->refusal($this->usages->reportFor($query), $ids);
|
$report = $this->usages->reportFor($query);
|
||||||
|
$refusal = $this->refusal($report, $ids);
|
||||||
if ($refusal !== null) {
|
if ($refusal !== null) {
|
||||||
return $refusal;
|
return $refusal;
|
||||||
}
|
}
|
||||||
@@ -158,7 +160,13 @@ class PageRewriter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['saved' => $saved, 'remaining' => $remaining, 'warnings' => $warnings, 'skipped' => $skipped];
|
return [
|
||||||
|
'saved' => $saved,
|
||||||
|
'remaining' => $remaining,
|
||||||
|
'warnings' => $warnings,
|
||||||
|
'skipped' => $skipped,
|
||||||
|
'hidden_count' => $ids === null ? $report->hidden : 0,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -303,6 +303,36 @@ final class DiviDesignDetachAbilityTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUnreadablePostsKeepTheDeleteHintBack(): void
|
||||||
|
{
|
||||||
|
$pages = Mockery::mock(PageRewriter::class);
|
||||||
|
$pages->expects('apply')->andReturn(PageRewriteReportTest::applied(1, 0, [], 3));
|
||||||
|
|
||||||
|
$result = $this->ability($pages)->execute([
|
||||||
|
'preset' => ['module' => 'button', 'preset' => 'CTA primär'],
|
||||||
|
'confirm' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertStringNotContainsString('lit/divi-presets-delete', $result['summary']);
|
||||||
|
self::assertStringNotContainsString('erledigt', $result['summary']);
|
||||||
|
self::assertStringContainsString('3 für diesen Zugang nicht sichtbare Fundstellen', $result['summary']);
|
||||||
|
self::assertSame(3, $result['hidden_count']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNoUnreadablePostsKeepTheDeleteHint(): void
|
||||||
|
{
|
||||||
|
$pages = Mockery::mock(PageRewriter::class);
|
||||||
|
$pages->expects('apply')->andReturn(PageRewriteReportTest::applied(1, 0));
|
||||||
|
|
||||||
|
$result = $this->ability($pages)->execute([
|
||||||
|
'preset' => ['module' => 'button', 'preset' => 'CTA primär'],
|
||||||
|
'confirm' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertStringContainsString('lit/divi-presets-delete', $result['summary']);
|
||||||
|
self::assertStringNotContainsString('nicht sichtbare', $result['summary']);
|
||||||
|
}
|
||||||
|
|
||||||
public function testRemainingPagesAskForAnotherCallWithoutTheDeleteHint(): void
|
public function testRemainingPagesAskForAnotherCallWithoutTheDeleteHint(): void
|
||||||
{
|
{
|
||||||
$pages = Mockery::mock(PageRewriter::class);
|
$pages = Mockery::mock(PageRewriter::class);
|
||||||
|
|||||||
@@ -45,14 +45,20 @@ final class PageRewriteReportTest extends TestCase
|
|||||||
* @param list<array{post_id: int, reason: string}> $skipped
|
* @param list<array{post_id: int, reason: string}> $skipped
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public static function applied(int $saved, int $remaining, array $skipped = []): array
|
public static function applied(int $saved, int $remaining, array $skipped = [], int $hidden = 0): array
|
||||||
{
|
{
|
||||||
$rows = [];
|
$rows = [];
|
||||||
for ($i = 0; $i < $saved; ++$i) {
|
for ($i = 0; $i < $saved; ++$i) {
|
||||||
$rows[] = ['post_id' => 12 + $i, 'revision_id' => 900 + $i, 'nodes' => 2];
|
$rows[] = ['post_id' => 12 + $i, 'revision_id' => 900 + $i, 'nodes' => 2];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['saved' => $rows, 'remaining' => $remaining, 'warnings' => [], 'skipped' => $skipped];
|
return [
|
||||||
|
'saved' => $rows,
|
||||||
|
'remaining' => $remaining,
|
||||||
|
'warnings' => [],
|
||||||
|
'skipped' => $skipped,
|
||||||
|
'hidden_count' => $hidden,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testEmptyMeansNothingToWriteNothingBlockedNothingHidden(): void
|
public function testEmptyMeansNothingToWriteNothingBlockedNothingHidden(): void
|
||||||
|
|||||||
@@ -618,5 +618,60 @@ final class PageRewriterTest extends TestCase
|
|||||||
self::assertSame(1, $preview['hidden_count']);
|
self::assertSame(1, $preview['hidden_count']);
|
||||||
self::assertSame([[101, SaveScope::Page]], $this->saved);
|
self::assertSame([[101, SaveScope::Page]], $this->saved);
|
||||||
self::assertSame([], $result['skipped']);
|
self::assertSame([], $result['skipped']);
|
||||||
|
self::assertSame(1, $result['hidden_count'], 'detach must not say "all done" with unreadable usages');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Unreadable live usages the caller may not edit refuse the call (forbidden) as a count; preview lists them. */
|
||||||
|
public function testUnreadableBlockedLivePagesRefuseTheCallAsACount(): void
|
||||||
|
{
|
||||||
|
$report = new UsageReport(2, [], 1, 2, [102], 2, false);
|
||||||
|
$rewriter = $this->rewriter(
|
||||||
|
[
|
||||||
|
self::page(101, self::layout(self::BUTTON), ['post_status' => 'publish']),
|
||||||
|
self::page(102, self::layout(self::BUTTON), ['post_status' => 'publish']),
|
||||||
|
],
|
||||||
|
$report
|
||||||
|
);
|
||||||
|
|
||||||
|
$preview = $rewriter->preview(self::query(), self::assign(), null);
|
||||||
|
self::assertSame([102], $preview['blocked_ids']);
|
||||||
|
self::assertSame(2, $preview['blocked_hidden_count']);
|
||||||
|
|
||||||
|
$error = self::assertToolError(
|
||||||
|
$rewriter->apply(self::ABILITY, self::query(), self::assign(), null, self::SUMMARY),
|
||||||
|
'forbidden'
|
||||||
|
);
|
||||||
|
self::assertSame([102], $error['blocked_ids']);
|
||||||
|
self::assertSame(2, $error['blocked_hidden_count']);
|
||||||
|
self::assertStringContainsString('2 Einträge, die dieses Konto nicht lesen darf', $error['message']);
|
||||||
|
self::assertSame([], $this->saved);
|
||||||
|
self::assertSame([], $this->log->records);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Above HARD_CAP live usages the per-row edit check does not run (blocked stays empty): post_ids are not refused,
|
||||||
|
* and a named live page the caller may not edit is skipped by loadWritable() instead of forbidden.
|
||||||
|
*/
|
||||||
|
public function testPostIdsAboveTheHardCapAreNotRefusedAndAnUneditablePageIsSkipped(): void
|
||||||
|
{
|
||||||
|
$rewriter = $this->rewriter(
|
||||||
|
[
|
||||||
|
self::page(101, self::layout(self::BUTTON), ['post_status' => 'publish']),
|
||||||
|
self::page(102, self::layout(self::BUTTON), ['post_status' => 'publish']),
|
||||||
|
],
|
||||||
|
self::report(GlobalUsages::HARD_CAP + 1, [], GlobalUsages::HARD_CAP + 1, true)
|
||||||
|
);
|
||||||
|
Functions\when('current_user_can')->alias(
|
||||||
|
static fn (string $cap, mixed ...$args): bool => ! ($cap === 'edit_post' && ($args[0] ?? null) === 101)
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertToolError($rewriter->preview(self::query(), self::assign(), null), 'unsupported');
|
||||||
|
$preview = $rewriter->preview(self::query(), self::assign(), [101, 102]);
|
||||||
|
$result = $rewriter->apply(self::ABILITY, self::query(), self::assign(), [101, 102], self::SUMMARY);
|
||||||
|
|
||||||
|
self::assertSame([], $preview['blocked_ids']);
|
||||||
|
self::assertSame([102], array_column($result['saved'], 'post_id'));
|
||||||
|
self::assertSame([101], array_column($result['skipped'], 'post_id'));
|
||||||
|
self::assertSame([[102, SaveScope::Page]], $this->saved);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user