)|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->colorsAdapter = new GlobalColorsAdapter(); $this->variablesAdapter = new GlobalVariablesAdapter(); $this->themeOptionsAdapter = new ThemeOptionsAdapter(); $this->fontCatalog = new FontCatalog(); $this->fontInstaller = new FontInstaller(); $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(); } public function compatibilities(): array { return [$this->profile()]; } /** * 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 tab "Compatibility" (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 WP-CLI command (spec §7) and the Divi additions to the admin tab * "Compatibility" (spec §6; the check route itself is generic, Admin\CheckRoute). */ 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); }); // The admin check runs as a generic REST route (Admin\CheckRoute, registered by Plugin); a stored check or a // release changes the state, so the gate forgets its status of this request. add_action(CheckRoute::CHANGED_ACTION, static function (string $profileId): void { if ($profileId === DiviProfile::ID) { ReleaseGate::invalidate(); } }); // Spec §7: the same check as WP-CLI command. Its short form has no Tools-tab gate: the check tests Divi, // not this site's tool switches. $profile = $this->profile(); DiviCheckCommand::register(static fn (): CompatibilityCheck => CompatibilityCheck::forProfile($profile)); // No tab of its own: notice, orphan cleanup, columns and notes join the tab "Compatibility". (new DiviAdminExtras($this->featureOverview()))->register(); } public function abilities(): array { $loader = $this->loader(); $writer = $this->writer($loader); $customCss = new CustomCssStore(); 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(), loader: $loader ), 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->designChange(), new SnapshotStore(), new PresetRestorePlanner($this->designWriter, $this->globalUsages(), DesignRights::unfilteredHtml()), new ColorRestorePlanner($this->designWriter, $this->colorsAdapter, $this->globalUsages()), new VariableRestorePlanner($this->designWriter, $this->variablesAdapter, $this->globalUsages()), new ThemeOptionsRestorePlanner($this->themeOptionsAdapter), new CustomCssRestorePlanner($customCss, $this->themeOptionsAdapter) ), new DiviPresetsAssignAbility( $this->designWriter, $this->pageRewriter($loader), $this->presetAssignment(...) ), new DiviDesignDetachAbility( $this->designWriter, $this->variablesAdapter, $this->designChange(), $this->globalUsages(), $this->pageRewriter($loader), DesignRights::unfilteredHtml() ), new DiviColorsSetAbility( $this->designWriter, $this->colorsAdapter, $this->designChange(), $this->globalUsages() ), new DiviVariablesSetAbility( $this->designWriter, $this->variablesAdapter, $this->designChange(), $this->globalUsages(), $this->fontCatalog ), new DiviThemeOptionsGetAbility($this->themeOptionsAdapter), new DiviThemeOptionsUpdateAbility( $this->themeOptionsAdapter, $this->fontCatalog, $this->designChange() ), new DiviCustomCssGetAbility($customCss, $this->themeOptionsAdapter), new DiviCustomCssUpdateAbility( $customCss, $this->themeOptionsAdapter, $this->designChange(), new CustomCssGuard() ), new DiviFontsUploadAbility( $this->fontCatalog, $this->fontInstaller, new RemoteFileFetcher(), $this->log, new Limits() ), ]; } /** * 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 */ /** The Divi profile; the check's write test composes through the short form without a Tools-tab gate. */ private function profile(): Profile { return DiviProfile::profile(fn (): DiviContentComposer => $this->shortForm(null)); } 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); } }