/home/egir5919/public_html/wp-content/plugins/surerank/inc/third-party-integrations/divi.php
<?php
/**
 * Divi Builder Integration
 *
 * @package SureRank\Inc\ThirdPartyIntegrations
 */

namespace SureRank\Inc\ThirdPartyIntegrations;

use SureRank\Inc\Admin\Dashboard;
use SureRank\Inc\Admin\Seo_Popup as Admin_Seo_Popup;
use SureRank\Inc\Frontend\Image_Seo;
use SureRank\Inc\Functions\Get;
use SureRank\Inc\Traits\Get_Instance;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

/**
 * Divi Builder Integration Class
 *
 * Handles both Divi 4 (et_pb_* shortcodes) and Divi 5 (WordPress blocks).
 *
 * DIVI 5 — native block rendering:
 *   Divi 5 registers all modules as WordPress block types, and explicitly
 *   enables this registration for REST API requests (see builder-5/server/
 *   bootstrap.php). WordPress core's do_blocks() invokes Divi's own
 *   server-side render callbacks for every module type automatically.
 *
 * DIVI 4 — et_builder_render_layout():
 *   Divi's own render function applies its full filter chain (do_blocks at
 *   priority 9, do_shortcode at priority 11), producing fully rendered HTML
 *   for every Divi 4 module type.
 */
class Divi {

	use Get_Instance;

	/**
	 * Constructor
	 */
	public function __construct() {

		if ( ! $this->is_active() ) {
			return;
		}

		add_filter( 'surerank_post_analyzer_content', [ $this, 'process_divi_content' ], 10, 2 );
		add_filter( 'surerank_meta_variable_post_content', [ $this, 'process_divi_content' ], 10, 2 );

		// Divi Frontend Visual Builder (?et_fb=1) — enqueue popup on the frontend.
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_for_visual_builder' ], 101 );

		// Enqueue surerank_globals on FVB frontend (same guard as enqueue_for_visual_builder).
		// Mirrors the Bricks pattern: hooked separately so jquery handle is reliably enqueued.
		add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_globals_for_visual_builder' ], 999 );

		// Add is_divi flag to surerank_globals (mirrors Bricks add_localization_vars).
		add_filter( 'surerank_globals_localization_vars', [ $this, 'add_localization_vars' ] );

		// Admin bar button + click handler for any Divi admin editing context
		// (block editor, classic editor, BFB). Fires on admin_enqueue_scripts so the
		// inline script is appended after seo-popup is already in the queue.
		add_action( 'admin_enqueue_scripts', [ $this, 'maybe_add_divi_admin_click_handler' ], 99999 );

		// admin_bar_menu fires on both admin and frontend, so this handles both
		// the FVB top window (frontend) and all admin editor contexts in one hook.
		add_action( 'admin_bar_menu', [ $this, 'maybe_add_divi_admin_bar_menu' ], 100 );

		// Override editor type to 'divi' when Divi Backend Builder (BFB) is active
		// so the JS skips the classic .wrap > h1 button injection.
		add_filter( 'surerank_detect_editor_type', [ $this, 'filter_bfb_editor_type' ], 10, 2 );
	}

	/**
	 * Check if Divi Builder is active.
	 *
	 * ET_BUILDER_VERSION is defined inside et_setup_builder() which is always
	 * called by et-pagebuilder.php via the init hook, regardless of request
	 * context (admin, frontend, REST API, AJAX).
	 *
	 * @since 1.7.0
	 * @return bool
	 */
	public function is_active(): bool {
		return defined( 'ET_BUILDER_VERSION' ) || class_exists( 'ET_Builder_Element' );
	}

	/**
	 * Route content to the correct renderer based on Divi version.
	 *
	 * Detection strategy:
	 *   - Divi 5 pages store content as <!-- wp:divi/... --> block comments.
	 *     We detect this from the content itself because _et_pb_use_builder is
	 *     NOT reliably set for Divi 5 pages (it is only force-set to 'on' during
	 *     the Divi layout-block preview request, not on regular page saves).
	 *   - Divi 4 pages are detected via the _et_pb_use_builder = 'on' meta key,
	 *     which Divi 4 always writes when the builder is active for a post.
	 *
	 * @since 1.7.0
	 * @param string   $content Raw post_content.
	 * @param \WP_Post $post    Post being analyzed.
	 * @return string Rendered HTML for XPath analysis.
	 */
	public function process_divi_content( string $content, $post ): string {
		if ( ! $post instanceof \WP_Post ) {
			return $content;
		}

		// Divi 5 content always contains the wp:divi/ block namespace.
		if ( false !== strpos( $content, '<!-- wp:divi/' ) ) {
			return $this->process_divi5( $content );
		}

		// Divi 4 pages set this meta key when the visual builder is active.
		if ( 'on' !== get_post_meta( $post->ID, '_et_pb_use_builder', true ) ) {
			return $content;
		}

		return $this->process_divi4( $content );
	}

	/**
	 * Enqueue SEO popup for Divi Frontend Visual Builder (frontend ?et_fb=1).
	 *
	 * Fires on wp_enqueue_scripts at priority 101 (after Divi's own scripts).
	 * Calls Enqueue trait methods directly to avoid the is_admin_bar_showing()
	 * guard inside frontend_enqueue_scripts(), which can return false in
	 * certain Divi 5 rendering contexts even when the admin bar is visible.
	 *
	 * Skips the Divi 5 app-window iframe (?et_fb=1&app_window=1) — scripts
	 * should only load once, in the top-level builder window.
	 *
	 * @since 1.6.0
	 * @return void
	 */
	public function enqueue_for_visual_builder(): void {
		if ( ! function_exists( 'et_core_is_fb_enabled' ) || ! et_core_is_fb_enabled() ) {
			return;
		}

		// Divi 5 loads the page in two iframes: the outer builder shell (?et_fb=1)
		// and an inner content preview (?et_fb=1&app_window=1). Only enqueue in
		// the outer window to avoid double-loading assets.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! empty( $_GET['app_window'] ) ) {
			return;
		}

		if ( ! current_user_can( 'surerank_content_setting' ) ) {
			return;
		}

		$post_id = get_the_ID();
		if ( ! $post_id ) {
			return;
		}

		$seo_popup = Admin_Seo_Popup::get_instance();

		wp_enqueue_media();
		$seo_popup->enqueue_vendor_and_common_assets();

		$seo_popup->build_assets_operations(
			'seo-popup',
			[
				'hook'        => 'seo-popup',
				'object_name' => 'seo_popup',
				'data'        => [
					'admin_assets_url'   => SURERANK_URL . 'inc/admin/assets',
					'site_icon_url'      => get_site_icon_url( 16 ),
					'editor_type'        => 'divi',
					'post_type'          => get_post_type( $post_id ) ? get_post_type( $post_id ) : '',
					'is_taxonomy'        => false,
					'description_length' => Get::description_length(),
					'title_length'       => Get::title_length(),
					'keyword_checks'     => $seo_popup->keyword_checks(),
					'page_checks'        => $seo_popup->page_checks(),
					'image_seo'          => Image_Seo::get_instance()->status(),
					'is_frontend'        => true,
					'post_id'            => $post_id,
					'link'               => get_the_permalink( $post_id ),
				],
			]
		);

		$seo_popup->build_assets_operations(
			'front-end-meta-box',
			[
				'hook'        => 'front-end-meta-box',
				'object_name' => 'front_end_meta_box',
				'data'        => [],
			]
		);

		// Admin bar node is registered by maybe_add_divi_admin_bar_menu() which is
		// hooked on admin_bar_menu in the constructor and covers both FVB (frontend)
		// and all admin editing contexts.

		// Enqueue the Divi page bar integration script (button + status indicator + tooltip).
		$this->enqueue_divi_bar_script();
	}

	/**
	 * Enqueue surerank_globals for Divi Frontend Visual Builder.
	 *
	 * Fires on wp_enqueue_scripts at priority 999 (after Divi's own scripts).
	 * Hooked separately from enqueue_for_visual_builder() so that the jquery
	 * handle is reliably in the queue before wp_localize_script() is called —
	 * mirrors the pattern used by the Bricks integration.
	 *
	 * @since 1.6.0
	 * @return void
	 */
	public function enqueue_globals_for_visual_builder(): void {
		if ( ! function_exists( 'et_core_is_fb_enabled' ) || ! et_core_is_fb_enabled() ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! empty( $_GET['app_window'] ) ) {
			return;
		}

		Dashboard::get_instance()->site_seo_check_enqueue_scripts();
	}

	/**
	 * Add Divi-specific variables to surerank_globals localization.
	 *
	 * Mirrors Bricks::add_localization_vars() so that JS code can detect
	 * the active builder via window.surerank_globals.is_divi.
	 *
	 * @since 1.6.0
	 * @param array<string, mixed> $vars Existing localization variables.
	 * @return array<string, mixed>
	 */
	public function add_localization_vars( array $vars ): array {
		return array_merge( $vars, [ 'is_divi' => true ] );
	}

	/**
	 * Override editor type to 'divi' when Divi Backend Builder is active.
	 *
	 * Hooked into surerank_detect_editor_type, which is applied at the end of
	 * Admin\Seo_Popup::detect_editor_type() so that the JS skips the classic
	 * .wrap > h1 button injection and uses the admin bar button instead.
	 *
	 * @since 1.6.0
	 * @param string          $editor_type Current editor type.
	 * @param \WP_Screen|null $screen      Current screen object.
	 * @return string
	 */
	public function filter_bfb_editor_type( string $editor_type, $screen ): string {
		if ( function_exists( 'et_builder_bfb_enabled' ) && et_builder_bfb_enabled() ) {
			return 'divi';
		}

		return $editor_type;
	}

	/**
	 * Attach an inline admin-bar click handler for any Divi admin editing context.
	 *
	 * Fires late on admin_enqueue_scripts (priority 99999) so surerank-seo-popup
	 * is guaranteed to be enqueued before we append to it. Works for block editor,
	 * classic editor, and BFB — wherever seo-popup scripts are already loaded.
	 *
	 * @since 1.6.0
	 * @return void
	 */
	public function maybe_add_divi_admin_click_handler(): void {
		if ( ! wp_script_is( 'surerank-seo-popup', 'enqueued' ) ) {
			return;
		}

		wp_add_inline_script(
			'surerank-seo-popup',
			"document.addEventListener('click',function(e){if(e.target.closest('#wp-admin-bar-surerank-meta-box')){e.preventDefault();if(window.wp&&window.wp.data){window.wp.data.dispatch('surerank').updateModalState(true);} } });"
		);
	}

	/**
	 * Render admin bar node for any Divi editing context (admin or FVB frontend).
	 *
	 * Delegates to Admin\Seo_Popup::add_admin_bar_menu(), which self-guards:
	 * it only adds the node when surerank-seo-popup is already enqueued.
	 * This makes it safe to hook unconditionally on admin_bar_menu.
	 *
	 * @since 1.6.0
	 * @param \WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance.
	 * @return void
	 */
	public function maybe_add_divi_admin_bar_menu( \WP_Admin_Bar $wp_admin_bar ): void {
		Admin_Seo_Popup::get_instance()->add_admin_bar_menu( $wp_admin_bar );
	}

	/**
	 * Render Divi 5 content using WordPress core's do_blocks().
	 *
	 * Divi 5 registers ALL its modules as WordPress block types with proper
	 * render_callback functions, and this registration happens for REST API
	 * requests (see includes/builder-5/server/bootstrap.php). Calling
	 * do_blocks() invokes Divi's own server-side render callbacks for every
	 * module type with no custom per-module code required on our side.
	 *
	 * @param string $content Raw post_content with divi/* block comments.
	 * @return string Fully rendered HTML from Divi's own render callbacks.
	 */
	private function process_divi5( string $content ): string {
		if ( ! function_exists( 'do_blocks' ) ) {
			return $content;
		}

		$html = do_blocks( $content );

		return $html ? $html : $content;
	}

	/**
	 * Render Divi 4 content using Divi's own render function.
	 *
	 * The et_builder_render_layout() applies Divi's full render filter chain
	 * (do_blocks at priority 9, do_shortcode at priority 11), producing
	 * fully rendered HTML for every Divi 4 module type.
	 *
	 * @param string $content Raw Divi 4 post_content with et_pb_* shortcodes.
	 * @return string Rendered HTML.
	 */
	private function process_divi4( string $content ): string {
		if ( ! function_exists( 'et_builder_render_layout' ) ) {
			return $content;
		}

		return et_builder_render_layout( $content );
	}

	/**
	 * Enqueue the bundled Divi page bar integration script.
	 *
	 * Registers and enqueues the webpack-built divi module which injects a
	 * SureRank button (with status indicator and tooltip) into the Divi VB
	 * page bar. Uses the auto-generated asset file for dependencies and version.
	 *
	 * @since 1.6.5
	 * @return void
	 */
	private function enqueue_divi_bar_script(): void {
		$asset_path = SURERANK_DIR . 'build/divi/index.asset.php';

		if ( ! file_exists( $asset_path ) ) {
			return;
		}

		$asset_info = include $asset_path;

		// Ensure surerank-seo-popup is a dependency so the store is available.
		$asset_info['dependencies'][] = 'surerank-seo-popup';
		$asset_info['dependencies']   = array_unique( $asset_info['dependencies'] );

		wp_register_script(
			'surerank-divi',
			SURERANK_URL . 'build/divi/index.js',
			$asset_info['dependencies'],
			$asset_info['version'],
			false
		);
		wp_enqueue_style(
			'surerank-divi',
			SURERANK_URL . 'build/divi/style.css',
			[],
			$asset_info['version']
		);
		wp_style_add_data( 'surerank-divi', 'rtl', 'replace' );
		wp_enqueue_script( 'surerank-divi' );
	}
}