plugin = $plugin; } /** Register Builder and authenticated AJAX hooks. */ public function register() { if ( ! $this->plugin->is_power_enabled( 'builder_bridge' ) ) { return; } add_action( 'divi_visual_builder_assets_before_enqueue_scripts', array( $this, 'register_builder_asset' ), 15 ); add_action( 'wp_enqueue_scripts', array( $this, 'render_builder_config' ), 5 ); add_action( 'wp_ajax_divi_powers_builder_get_context', array( $this, 'ajax_get_context' ) ); add_action( 'wp_ajax_divi_powers_builder_update_selection', array( $this, 'ajax_update_selection' ) ); add_filter( 'divi_powers_capability_definitions', array( $this, 'register_capability_definitions' ) ); } /** Register the app-window bridge through Divi 5's package manager. */ public function register_builder_asset() { if ( ! function_exists( 'et_builder_d5_enabled' ) || ! function_exists( 'et_core_is_fb_enabled' ) || ! et_builder_d5_enabled() || ! et_core_is_fb_enabled() || ! class_exists( '\\ET\\Builder\\VisualBuilder\\Assets\\PackageBuildManager' ) ) { return; } \ET\Builder\VisualBuilder\Assets\PackageBuildManager::register_package_build( array( 'name' => 'divi-powers-builder-bridge', 'version' => DIVI_POWERS_VERSION, 'script' => array( 'src' => DIVI_POWERS_URL . 'assets/js/builder-bridge.js', 'deps' => array( 'wp-i18n' ), 'enqueue_top_window' => false, 'enqueue_app_window' => true, 'args' => array( 'in_footer' => true ), ), ) ); wp_set_script_translations( 'divi-powers-builder-bridge', '5d-powers-for-divi' ); } /** Place authenticated bridge configuration into the Builder page. */ public function render_builder_config() { if ( ! $this->is_builder_request() || ! current_user_can( 'edit_posts' ) ) { return; } $post_id = absint( get_queried_object_id() ); if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) { return; } $config = array( 'version' => DIVI_POWERS_VERSION, 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'divi_powers_builder_bridge' ), 'postId' => $post_id, 'pageTitle' => get_the_title( $post_id ), 'contextEvent' => 'divi-powers:builder-context', 'assistantEvent' => 'divi-powers:assistant-open', ); $this->remember_active_page( $post_id ); wp_register_script( 'fivedp-bridge-config', false, array(), DIVI_POWERS_VERSION, false ); wp_enqueue_script( 'fivedp-bridge-config' ); wp_add_inline_script( 'fivedp-bridge-config', 'window.diviPowersBuilderBridge=' . wp_json_encode( $config, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT ) . ';' ); } /** Return the saved page plus the most recent live selection. */ public function ajax_get_context() { check_ajax_referer( 'divi_powers_builder_bridge', 'nonce' ); $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error( array( 'message' => __( 'You cannot inspect this page.', '5d-powers-for-divi' ) ), 403 ); } wp_send_json_success( $this->page_context( $post_id, true ) ); } /** Save a short-lived snapshot of the currently selected Builder element. */ public function ajax_update_selection() { check_ajax_referer( 'divi_powers_builder_bridge', 'nonce' ); $post_id = isset( $_POST['post_id'] ) ? absint( $_POST['post_id'] ) : 0; if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error( array( 'message' => __( 'You cannot update context for this page.', '5d-powers-for-divi' ) ), 403 ); } $raw = isset( $_POST['selection'] ) ? wp_unslash( $_POST['selection'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $selection = json_decode( (string) $raw, true ); $selection = $this->sanitize_selection( is_array( $selection ) ? $selection : array() ); $snapshot = array( 'post_id' => $post_id, 'user_id' => get_current_user_id(), 'updated_at' => gmdate( 'c' ), 'selection' => $selection, ); set_transient( $this->transient_key( get_current_user_id(), $post_id ), $snapshot, self::CONTEXT_TTL ); set_transient( $this->active_key( get_current_user_id() ), $snapshot, self::CONTEXT_TTL ); wp_send_json_success( array( 'stored' => true, 'updated_at' => $snapshot['updated_at'] ) ); } /** * Return the page most recently kept alive by this editor's Builder. * * @return int */ public function latest_post_id() { $user_id = get_current_user_id(); $active = $user_id ? get_transient( $this->active_key( $user_id ) ) : false; $post_id = is_array( $active ) && isset( $active['post_id'] ) ? absint( $active['post_id'] ) : 0; if ( ! $post_id || ! get_post( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) { return 0; } return $post_id; } /** * Server-readable context seam for local Builder AI operations. * * @param int $post_id Page ID. * @param bool $include_content Include saved Divi content. * @return array */ public function page_context( $post_id, $include_content = false ) { $post = get_post( $post_id ); if ( ! $post ) { return array(); } $content = (string) $post->post_content; $context = array( 'contract_version' => '1.0', 'post_id' => (int) $post->ID, 'title' => get_the_title( $post ), 'status' => (string) $post->post_status, 'url' => get_permalink( $post ), 'modified_gmt' => (string) $post->post_modified_gmt, 'builder' => false !== strpos( $content, '