backup = $backup ?? static fn (WP_Post $item): int => (new PostCloner())->backup($item); $this->trashEnabled = $trashEnabled ?? static fn (): bool => ! defined('EMPTY_TRASH_DAYS') || (bool) EMPTY_TRASH_DAYS; } /** * @param array $input * @return array{scope: SaveScope, details: array}|WP_Error * @throws WriteFailed When the backup could not be made (nothing was saved). */ public function prepare(DiviPage $page, array $input): array|WP_Error { $item = $page->post; $id = (int) $item->ID; $global = $this->access->isGlobal($item); try { $before = LibraryContent::roots(LibraryContent::load($this->parser, (string) $item->post_content)); } catch (MalformedMarkup) { $before = []; } $violation = LibraryContent::shapeViolation( $before, LibraryContent::roots($page->document), $this->access->store()->layoutType($id), $global ); if ($violation !== null) { return Errors::invalidInput($violation . ' Nichts wurde gespeichert.', ['post_id' => $violation]); } // Audit L2: Divi renders every entry an instance names, global scope or not. $report = $this->usages->report($id); if (! $global && ! $report->exists()) { return ['scope' => SaveScope::LibraryItem, 'details' => []]; } if (($input['confirm'] ?? false) !== true) { return Errors::confirmRequired( sprintf( '%s: die Änderung wirkt sofort auf %d %s (siehe affected), auch veröffentlichte. Nichts wurde ' . 'gespeichert (Probelauf).', self::label($id, $global), $report->count, self::pages($report->count) ) . self::hiddenNote($report) . self::blockedNote($report), $report->details() ); } $refusal = $this->pagesRefusal(self::label($id, $global), $report) ?? $this->backupRefusal($item); if ($refusal !== null) { return $refusal; } $details = $report->listing(); if (! wp_revisions_enabled($item)) { $details['backup_id'] = ($this->backup)($item); } return ['scope' => SaveScope::GlobalItem, 'details' => $details]; } /** * A tool's own confirm_required (lit-divi-delete) on a global or referenced entry (audit L2), * extended by the affected pages – so the one confirm the client sends covers both questions. */ public function notice(DiviPage $page, WP_Error $error): WP_Error { if (Errors::codeOf($error) !== 'confirm_required') { return $error; } $global = $this->access->isGlobal($page->post); $report = $this->usages->report((int) $page->post->ID); if (! $global && ! $report->exists()) { return $error; } $data = $error->get_error_data(); $details = is_array($data) ? array_diff_key($data, ['status' => true, 'hint' => true]) : []; return Errors::confirmRequired( Errors::clientMessage($error) . sprintf( ' %s: wirkt auf %d %s (siehe affected).', $global ? 'Globaler Bibliothekseintrag' : 'Bibliothekseintrag (als globales Modul verwendet)', $report->count, self::pages($report->count) ) . self::hiddenNote($report) . self::blockedNote($report), array_merge($details, $report->details()) ); } /** * Ruling P8a: a confirmed edit needs edit_post on every live post that shows entry $id; above * GlobalUsages::HARD_CAP live posts it is refused as a whole (audit L1). */ private function pagesRefusal(string $label, UsageReport $report): ?WP_Error { if ($report->tooMany) { return Errors::unsupported( sprintf( '%s erscheint auf %d veröffentlichten, privaten oder geplanten Einträgen – mehr als %d; so große ' . 'Änderungen nimmt LIT MCP nicht vor. Nichts wurde gespeichert.', $label, $report->live, GlobalUsages::HARD_CAP ), 'Den Eintrag im Divi-Builder bearbeiten.' ); } if (! $report->blocks()) { return null; } return Errors::forbidden( sprintf( '%s erscheint auf veröffentlichten, privaten oder geplanten Einträgen, die dieses Konto nicht ' . 'bearbeiten darf (%s); nichts wurde gespeichert.', $label, self::blockedList($report) ), 'Die Änderung von jemandem vornehmen lassen, der alle diese Einträge bearbeiten darf (z. B. Redakteur ' . 'oder Administrator).' ); } /** Audit L4: posts the caller may not read appear only as a count. */ private static function hiddenNote(UsageReport $report): string { return $report->hidden === 0 ? '' : sprintf( ' %d davon darf dieses Konto nicht lesen (nur gezählt, siehe hidden_count).', $report->hidden ); } private static function blockedNote(UsageReport $report): string { if ($report->tooMany) { return sprintf( ' Achtung: Die Bestätigung würde abgelehnt – mehr als %d veröffentlichte, private oder geplante ' . 'Einträge zeigen den Eintrag; ihn im Divi-Builder bearbeiten.', GlobalUsages::HARD_CAP ); } return ! $report->blocks() ? '' : sprintf( ' Achtung: Die Bestätigung würde abgelehnt – veröffentlichte, private oder geplante Einträge, die dieses ' . 'Konto nicht bearbeiten darf, zeigen den Eintrag (%s).', self::blockedList($report) ); } /** Readable blocking posts by ID (at most LISTED_IDS, see blocked_ids), unreadable ones as a count. */ private static function blockedList(UsageReport $report): string { $parts = []; if ($report->blocked !== []) { $parts[] = 'IDs ' . self::listed($report->blocked) . ', siehe blocked_ids'; } if ($report->blockedHidden > 0) { $parts[] = sprintf( '%d %s, die dieses Konto nicht lesen darf', $report->blockedHidden, $report->blockedHidden === 1 ? 'Eintrag' : 'Einträge' ); } return implode('; ', $parts); } /** @param list $ids At most LISTED_IDS of them, then "und N weitere". */ private static function listed(array $ids): string { $more = count($ids) - self::LISTED_IDS; return implode(', ', array_slice($ids, 0, self::LISTED_IDS)) . ($more > 0 ? sprintf(' und %d weitere', $more) : ''); } /** How messages name entry $id: global, or a normal entry that pages show as a global module (L2). */ private static function label(int $id, bool $global): string { return $global ? sprintf('Globaler Bibliothekseintrag %d', $id) : sprintf('Bibliothekseintrag %d (auf Seiten als globales Modul verwendet)', $id); } private static function pages(int $count): string { return $count === 1 ? 'Seite' : 'Seiten'; } /** Ruling P9: a backup (only made when revisions are off) must be faithful and must survive. */ private function backupRefusal(WP_Post $item): ?WP_Error { if (wp_revisions_enabled($item)) { return null; } $hint = 'Revisionen für die Divi-Bibliothek aktivieren (WP_POST_REVISIONS) oder den Eintrag im Divi-Builder ' . 'bearbeiten.'; if (! current_user_can('unfiltered_html')) { return Errors::unsupported( sprintf( 'Revisionen sind für Bibliothekseintrag %d abgeschaltet, und die Sicherungskopie wäre für dieses ' . 'Konto nicht verlustfrei (WordPress filtert HTML); nichts wurde gespeichert.', (int) $item->ID ), $hint ); } if (! ($this->trashEnabled)()) { return Errors::unsupported( sprintf( 'Revisionen sind für Bibliothekseintrag %d abgeschaltet, und der Papierkorb ist deaktiviert ' . '(EMPTY_TRASH_DAYS = 0) – die Sicherungskopie würde sofort gelöscht; nichts wurde ' . 'gespeichert.', (int) $item->ID ), $hint ); } return null; } }