fix: identical means identical bytes – text parts count, pages and posts only (ruling Q25)
- Fingerprint::content() includes non-whitespace text parts (raw HTML between blocks, inner HTML of core blocks) and opaque blocks with all their bytes; structure() unchanged; find-similar "identical" follows - make-global mode 2 also compares Fingerprint::canonical() bytes of each node - sources and targets: post types page and post only (POST_TYPES) - a rolled-back page N>1 keeps DiviSaver's "Revision X bleibt erhalten" - guide: escaped divi/global-layout re-expanded by Divi's do_blocks in shortcode-module and WooCommerce text output documented as residual risk 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:
@@ -40,17 +40,19 @@ defined('ABSPATH') || exit;
|
||||
* instance the Visual Builder writes (GlobalInstance::of). All or nothing: the page is written through
|
||||
* DiviWriter::commit() (tree_rev lock, revision); when it is refused or rolled back, the new entry is deleted
|
||||
* again (NewDraft) – only a page write that may have been saved (partial) keeps the entry, because the page may
|
||||
* already show it. Sources are pages and posts only: no library entries (no nesting, ruling P11) and no Theme
|
||||
* Builder layouts (controller ruling; there lit/library-save and lit-library-insert). Needs the rights of
|
||||
* already show it. Sources are pages and posts only (post types page and post, ruling Q25): no library entries
|
||||
* (no nesting, ruling P11), no Theme Builder layouts (ruling Q24; there lit/library-save and lit-library-insert)
|
||||
* and no other Divi-enabled types. Needs the rights of
|
||||
* lit/library-save with scope "global" and of lit-library-insert, and follows lit-divi-insert (audit M2).
|
||||
*
|
||||
* Second mode (E10, answer F2): library_id plus targets replaces further occurrences – only nodes whose content
|
||||
* fingerprint equals the entry's root ("identical" in lit/library-find-similar) – by instances of that active
|
||||
* fingerprint and normalized bytes (Fingerprint::canonical, ruling Q25: visible text parts included) equal the
|
||||
* entry's root ("identical" in lit/library-find-similar) – by instances of that active
|
||||
* global entry (one readable root, no reference to any entry in any spelling, ruling Q7). A global edit: every
|
||||
* target is checked first (edit_post and tree_rev through DiviWriter::page(), P8a – the account changes only pages
|
||||
* it may edit; identical nodes; the instance passes the page's StoredNodeGuard; pages and posts only, no library
|
||||
* entries or Theme Builder layouts), then a dry run lists the pages (tree_rev, status, live – drafts are listed,
|
||||
* like P8a), and only confirm: true writes. Every page goes through DiviWriter::commit() with its own tree_rev
|
||||
* it may edit; identical nodes; the instance passes the page's StoredNodeGuard; post types page and post only),
|
||||
* then a dry run lists the pages (tree_rev, status, live – drafts are listed, like P8a), and only confirm: true
|
||||
* writes. Every page goes through DiviWriter::commit() with its own tree_rev
|
||||
* lock, revision and change-log row; a live page (publish, private, future) without revisions first gets
|
||||
* VisibleEdits' backup, refused for every page before anything is written where it could not be faithful (P9).
|
||||
* Pages are written in the given order; when page N fails, pages 1..N-1 stay replaced (each with its restore
|
||||
@@ -68,6 +70,9 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
|
||||
private const TYPE_LABELS = ['section' => 'Abschnitt', 'row' => 'Zeile', 'module' => 'Modul'];
|
||||
|
||||
/** Post types that are sources and targets (ruling Q25): pages and posts, no other Divi-enabled type. */
|
||||
public const POST_TYPES = ['page', 'post'];
|
||||
|
||||
/** Inputs of the first mode, refused next to library_id. */
|
||||
private const NODE_INPUT = ['post_id', 'tree_rev', 'id', 'title', 'categories', 'tags'];
|
||||
|
||||
@@ -102,8 +107,9 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
public function description(): string
|
||||
{
|
||||
return 'Wandelt einen Divi-Knoten (post_id, tree_rev und id aus lit-divi-outline, title) auf einer Seite oder '
|
||||
. 'in einem Beitrag in ein globales Modul um: legt einen globalen Bibliothekseintrag mit genau diesem '
|
||||
. 'Inhalt an und ersetzt den Knoten durch die verknüpfte Instanz – alles oder nichts. Danach zeigt die '
|
||||
. 'in einem Beitrag (Inhaltstypen page und post) in ein globales Modul um: legt einen globalen '
|
||||
. 'Bibliothekseintrag mit genau diesem Inhalt an und ersetzt den Knoten durch die verknüpfte Instanz – '
|
||||
. 'alles oder nichts. Danach zeigt die '
|
||||
. 'Seite den Inhalt aus der Bibliothek; Änderungen dort wirken auf alle Seiten mit diesem Modul.'
|
||||
. ' Gleichartige Vorkommen danach ersetzen: library_id plus targets (je post_id, tree_rev und ids '
|
||||
. 'inhaltsgleicher Knoten aus lit/library-find-similar, match "identical"; nur Seiten und Beiträge); das '
|
||||
@@ -370,6 +376,7 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
}
|
||||
|
||||
$fingerprint = Fingerprint::content($root);
|
||||
$canonical = Fingerprint::canonical($root);
|
||||
$plans = [];
|
||||
$errors = [];
|
||||
$seen = [];
|
||||
@@ -383,9 +390,9 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
return $page;
|
||||
}
|
||||
$postId = (int) $page->post->ID;
|
||||
if ($page->isLibraryItem() || $page->isThemeBuilderLayout() || isset($seen[$postId])) {
|
||||
$errors["targets.$i.post_id"] = 'nur Seiten und Beiträge, jede einmal (Theme-Builder-Layouts und '
|
||||
. 'Bibliothekseinträge einzeln mit lit-library-insert)';
|
||||
if (self::sourceRefusal($page) !== null || isset($seen[$postId])) {
|
||||
$errors["targets.$i.post_id"] = 'nur Seiten und Beiträge (page, post), jede einmal (andere Einträge '
|
||||
. 'einzeln mit lit-library-insert)';
|
||||
continue;
|
||||
}
|
||||
$seen[$postId] = true;
|
||||
@@ -397,7 +404,10 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
$nodes = [];
|
||||
foreach ($ids as $j => $id) {
|
||||
$node = is_string($id) ? $page->index->find($id) : null;
|
||||
if ($node === null || $node->isOpaque() || Fingerprint::content($node) !== $fingerprint) {
|
||||
// The hash first (cheap), then the normalized bytes themselves (ruling Q25) – no collision counts.
|
||||
$identical = $node !== null && ! $node->isOpaque() && Fingerprint::content($node) === $fingerprint
|
||||
&& Fingerprint::canonical($node) === $canonical;
|
||||
if (! $identical) {
|
||||
$errors["targets.$i.ids.$j"] = 'kein inhaltsgleiches Vorkommen des Eintrags (nur match "identical" '
|
||||
. 'aus lit/library-find-similar)';
|
||||
continue;
|
||||
@@ -512,9 +522,11 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
return $e->withDetails($details);
|
||||
}
|
||||
|
||||
// DiviSaver's own state stays (e.g. "Revision X bleibt erhalten").
|
||||
$state = sprintf(
|
||||
'Seite %d wurde nicht geändert. Bereits ersetzt (je mit Revision): %s; unverändert: %s.',
|
||||
'Seite %d: %s Bereits ersetzt (je mit Revision): %s; unverändert: %s.',
|
||||
$postId,
|
||||
$e->state ?? 'Die Änderung wurde nicht gespeichert.',
|
||||
implode(', ', $done),
|
||||
$rest === [] ? 'keine weiteren' : implode(', ', $rest)
|
||||
);
|
||||
@@ -579,8 +591,9 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
}
|
||||
|
||||
/**
|
||||
* Only pages and posts: a library entry would nest global modules (P11), a Theme Builder layout is left to
|
||||
* lit/library-save plus lit-library-insert (controller ruling on the plan's open point).
|
||||
* Only pages and posts (POST_TYPES, ruling Q25): a library entry would nest global modules (P11), a Theme
|
||||
* Builder layout is left to lit/library-save plus lit-library-insert (ruling Q24), other Divi-enabled types
|
||||
* are out of scope.
|
||||
*/
|
||||
private static function sourceRefusal(DiviPage $page): ?WP_Error
|
||||
{
|
||||
@@ -600,6 +613,13 @@ final class LibraryMakeGlobalAbility implements Ability
|
||||
);
|
||||
}
|
||||
|
||||
if (! in_array((string) $page->post->post_type, self::POST_TYPES, true)) {
|
||||
return Errors::invalidInput(
|
||||
'lit/library-make-global arbeitet nur auf Seiten und Beiträgen; nichts wurde gespeichert.',
|
||||
['post_id' => 'eine Seite oder ein Beitrag (Inhaltstyp page oder post)']
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user