)|null $allAbilities The abilities of every integration (Plugin passes * IntegrationRegistry::abilities()); the tools a Divi feature affects are found among them (E3). */ public function __construct( private ContentSchema $schema, private ChangeLog $log, private ?ToolAvailability $tools = null, private ?Closure $allAbilities = null ) { $this->modules = new DiviModules(); $this->design = new DesignSystemReader(); $this->designWriter = new DesignSystemWriter(); $this->diviWritable = $this->gate(static fn (ToolAvailability $t): bool => $t->allows('lit/divi-insert')); $this->libraryInsertable = $this->gate( static fn (ToolAvailability $t): bool => $t->allows('lit/library-insert') ); $roles = new DiviRoles(); $this->access = new LibraryAccess( new DiviLibraryStore(), $roles, $this->gate(static fn (ToolAvailability $t): bool => $t->groupWritable(LibraryAccess::GROUP)), $this->gate(static fn (ToolAvailability $t): bool => $t->groupEnabled(LibraryAccess::GROUP)) ); $this->themeBuilderAccess = new ThemeBuilderAccess( new DiviThemeBuilder(), $roles, $this->gate(static fn (ToolAvailability $t): bool => $t->groupWritable(ThemeBuilderAccess::GROUP)), $this->gate(static fn (ToolAvailability $t): bool => $t->groupEnabled(ThemeBuilderAccess::GROUP)) ); } public function id(): string { return 'divi'; } /** Ruling P13: whether this connection may see library IDs (library group visible, library right). */ public function libraryIdsVisible(): bool { return $this->access->revealsIds(); } public function label(): string { return __('Divi', 'wp-lit-mcp'); } public function isAvailable(): bool { return DiviModules::isAvailable(); } /** * Divi's own post types (library, Theme Builder, canvases, speculation), which the content tools leave alone: * ContentSchema's excluded post types. Static because the schema is built before this integration (Plugin). * * @return list */ public static function internalPostTypes(): array { return DiviModules::INTERNAL_POST_TYPES; } /** Whether Divi's builder is switched on for $postType: ContentSchema::diviEnabled() (static, like above). */ public static function builderEnabledFor(string $postType): bool { return DiviModules::builderEnabledFor($postType); } /** Port for the content tools: Divi's builder flag and availability (PostPresenter). */ public function builderDetector(): BuilderDetector { return new DiviBuilderDetector(); } /** * Port for the media tools: places outside the library where Divi stores an attachment (its logo). * * @return list */ public function mediaUsageSources(): array { return [new DiviLogoUsage()]; } /** Which tools each Divi feature affects – for lit-site-status and the Divi tab (E3). */ public function featureOverview(): FeatureOverview { return new FeatureOverview($this->allAbilities ?? static fn (): array => []); } /** Port for lit-site-status: Divi's version, missing internals, release status and features. */ public function environmentComponent(): EnvironmentComponent { return new DiviEnvironmentComponent(overview: $this->featureOverview()); } /** * Divi short form for lit/content-create; available() reports whether Divi 5 is loaded, writable() follows * lit/divi-insert's Tools-tab state (security M2). */ public function contentComposer(): ContentComposer { return $this->shortForm($this->diviWritable); } /** * Divi's own boot steps, registered whether or not Divi is loaded yet (Bootable): a theme update checks the * fingerprints again at once (spec §5.4), the admin check as REST route and WP-CLI command (spec §6, §7) and * the tab "Divi" after the plugin's own tabs. */ public function boot(): void { // Spec §5.4: a theme update checks the Divi fingerprints again at once (the state key would, too). add_action('upgrader_process_complete', static function (): void { ReleaseGate::invalidate(mirrors: true); }); // Spec §6: the admin check runs as a REST request (same context as MCP calls). Its short form has no // Tools-tab gate: the check tests Divi, not this site's tool switches. $check = fn (): CompatibilityCheck => CompatibilityCheck::create($this->shortForm(null)); (new DiviCheckRoute($check))->register(); DiviCheckCommand::register($check); add_filter( SettingsPage::TABS_FILTER, fn (array $tabs): array => [...$tabs, new DiviTab(log: $this->log, overview: $this->featureOverview())] ); } public function abilities(): array { $loader = $this->loader(); $writer = $this->writer($loader); return [ new DiviOutlineAbility($loader, $this->design), new DiviNodeGetAbility($loader, $this->design), new DiviCatalogAbility($this->modules), new DiviDesignSystemAbility($this->design), new DiviInsertAbility($writer), new DiviUpdateAbility($writer), new DiviMoveAbility($writer), new DiviDeleteAbility($writer), new DiviDuplicateNodeAbility($writer), new DiviReplaceTextAbility($this->schema, $loader, $writer), new DiviNodeCopyAbility($writer, $loader, $this->diviWritable, $this->libraryInsertable), new DiviDynamicSourcesAbility($this->schema, new DynamicContentMap(), $this->access->roles()), new DiviPresetGetAbility($this->designWriter, $this->globalUsages()), new DiviPresetsSetAbility( $this->designWriter, $this->designChange(), $this->globalUsages(), $loader, $this->presetComposer(...), DesignRights::unfilteredHtml() ), new DiviPresetsDeleteAbility( $this->designWriter, $this->designChange(), $this->globalUsages(), DesignRights::unfilteredHtml() ), new DiviDesignSnapshotsAbility(new SnapshotStore()), new DiviDesignRestoreAbility( $this->designWriter, $this->designChange(), new SnapshotStore(), DesignRights::unfilteredHtml() ), new DiviPresetsAssignAbility( $this->designWriter, $this->pageRewriter($loader), $this->presetAssignment(...) ), new DiviDesignDetachAbility( $this->designWriter, new GlobalVariablesAdapter(), $this->designChange(), $this->globalUsages(), $this->pageRewriter($loader), DesignRights::unfilteredHtml() ), ]; } /** * The group "Divi-Bibliothek" (lit/library-*), sharing this integration's library access and its * $diviWritable gate (lit-library-insert follows lit/divi-insert, audit M2). */ public function library(): LibraryIntegration { $loader = $this->loader(); return new LibraryIntegration( $this->access, $loader, $this->design, $this->writer($loader), new DiviSaver(), $this->log, $this->modules, $this->diviWritable, $this->schema, $this->globalUsages() ); } /** * The usages of library entries with this integration's Theme Builder access (ruling Q17 M1, review I-2): * Theme Builder posts are named only while ThemeBuilderAccess::visible(), and a global edit that changes them * needs the rights of every Theme Builder write (canUse(), in visible()) and a writable "Divi Theme Builder" * group – as the Divi tools on those layouts. */ public function globalUsages(?\wpdb $db = null): GlobalUsages { return new GlobalUsages($db, $this->access->roles(), $this->themeBuilderAccess->usageState(...)); } /** The group "Divi Theme Builder" (lit/tb-*), sharing this integration's Theme Builder access (Etappe 3b). */ public function themeBuilder(): ThemeBuilderIntegration { return new ThemeBuilderIntegration( $this->themeBuilderAccess, $this->composition(), new DiviSaver(), $this->log, new DynamicFields($this->schema) ); } /** * A gate on the Tools-tab state for the classes that take a (Closure(): bool)|null: asked at call time, null * without $tools (always allowed). * * @param Closure(ToolAvailability): bool $ask * @return (Closure(): bool)|null */ private function gate(Closure $ask): ?Closure { $tools = $this->tools; return $tools === null ? null : static fn (): bool => $ask($tools); } /** @param (Closure(): bool)|null $writable */ private function shortForm(?Closure $writable): DiviContentComposer { return new DiviContentComposer($this->schema, $this->composition(), new DiviSaver(), $this->design, $writable); } /** * The one wiring of the composition for every tool that composes (spec D7): presets and Divi's option groups come * from the design system writer, so a stack or group preset in any write tool resolves against the live book. */ private function composition(): DiviComposition { return new DiviComposition($this->modules, $this->design, presetSource: $this->designWriter); } private function writer(DiviPageLoader $loader): DiviWriter { return new DiviWriter( $loader, new DiviSaver(), $this->log, $this->design, $this->composition(), new GlobalEdits($this->access, $this->globalUsages()), new ThemeBuilderEdits($this->themeBuilderAccess, fields: new DynamicFields($this->schema)), new SimilarSections($this->access) ); } private function designChange(): DesignChange { return new DesignChange(new SnapshotStore(), $this->log); } /** * The page rewriter of lit/divi-presets-assign and lit/divi-design-detach (Etappe 4b, spec §6.6): usages with * this integration's Theme Builder access, posts through $loader (shared with the other Divi tools). */ private function pageRewriter(DiviPageLoader $loader): PageRewriter { return new PageRewriter($this->globalUsages(), $loader, new DiviSaver(), $this->log); } private function presetComposer(): PresetComposer { $composition = $this->composition(); return new PresetComposer( $composition->composer(), $composition->storedGuard(), $this->designWriter->presetAttrs(...), $this->designWriter->groupsOf(...), $this->modules->builderVersion(), static fn (): int => (int) floor(microtime(true) * 1000) ); } /** A fresh PresetAssignment (own preset book) per lit/divi-presets-assign call, wired like the Composer's (D7). */ private function presetAssignment(): PresetAssignment { return $this->composition()->presets(); } private function loader(): DiviPageLoader { return new DiviPageLoader($this->schema, new Parser(), $this->access, $this->themeBuilderAccess); } }