# zip-ai/0.0.4/classes/react/react-manager.php

ZIP AI – AI Website Builder &amp; AI Agent (Beta), version 0.0.4. 530 lines.

- Page: https://pluginprobe.com/plugins/zip-ai/0.0.4/code/classes/react/react-manager.php
- Raw: https://pluginprobe.com/plugins/zip-ai/0.0.4/raw/classes/react/react-manager.php
- Modified: 2026-07-02T12:36:30+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/zip-ai/0.0.4/code/classes/react/react-manager.php#L10-L20`.

```php
<?php
/**
 * React Manager - Renders chat assistant directly in WordPress (no iframe)
 *
 * @package zip-ai
 */

namespace ZipAI\Classes\React;

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

use ZipAI\Classes\Core\Helper;
use ZipAI\Classes\Traits\Enqueue;

/**
 * The React_Manager Class.
 * Handles rendering of the React chat assistant directly in WordPress.
 */
class React_Manager {

	use Enqueue;

	/**
	 * Constructor of this class.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	public function __construct() {
		$this->enqueue_scripts();
		$this->enqueue_scripts_admin();
		add_action( 'admin_menu', array( $this, 'register_admin_page' ) );
		add_action( 'admin_footer', array( $this, 'render_container' ) );
		add_action( 'wp_footer', array( $this, 'render_container' ) );

		// Collapse WP sidebar and tag body on the dedicated full-page screen.
		if ( is_admin() ) {
			$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			if ( 'zip-ai-assistant' === $page ) {
				add_filter( 'admin_body_class', array( $this, 'add_fullpage_body_classes' ) );
				// Remove all admin notices on the fullpage assistant screen.
				add_action( 'in_admin_header', array( $this, 'remove_admin_notices' ) );
			}
		}
	}

	/**
	 * Add body classes for the dedicated full-page assistant screen.
	 * `folded`                – collapses WP admin sidebar to icon-only mode.
	 * `zip-ai-fullpage-page` – lets CSS target this page precisely.
	 *
	 * @since 0.0.1
	 * @param string $classes Existing body classes.
	 * @return string
	 */
	public function add_fullpage_body_classes( $classes ) {
		return $classes . ' folded zip-ai-fullpage-page';
	}

	/**
	 * Remove all admin notices on the fullpage assistant screen.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	public function remove_admin_notices() {
		remove_all_actions( 'admin_notices' );
		remove_all_actions( 'all_admin_notices' );
	}

	/**
	 * Register dedicated full-page assistant screen in WP admin.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	public function register_admin_page() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		add_options_page(
			__( 'Zip AI', 'zip-ai' ),
			__( 'Zip AI', 'zip-ai' ),
			'manage_options',
			'zip-ai-assistant',
			array( $this, 'render_fullpage_screen' )
		);
	}

	/**
	 * Check if we should use source (non-minified) scripts.
	 *
	 * @since 0.0.1
	 * @return bool
	 */
	private function use_source_scripts() {
		return ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ||
				( defined( 'ZIP_AI_DEBUG' ) && ZIP_AI_DEBUG );
	}

	/**
	 * Frontend enqueue callback (registered by trait).
	 *
	 * @since 0.0.1
	 * @return void
	 */
	public function wp_enqueue_scripts() {
		$this->enqueue_all_assets();
	}

	/**
	 * Admin enqueue callback (registered by trait).
	 *
	 * @since 0.0.1
	 * @return void
	 */
	public function admin_enqueue_scripts() {
		$this->enqueue_all_assets();
	}

	/**
	 * Enqueue all scripts and styles for the React chat assistant.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	private function enqueue_all_assets() {
		if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$use_source = $this->use_source_scripts();
		$auth_url   = $this->get_auth_url();

		// ── Bridge scripts (tool hooks, context, bridge host) ──
		if ( $use_source ) {
			$this->enqueue_source_scripts();
		} else {
			$this->enqueue_minified_scripts();
		}

		// ── React app bundle (read .asset.php for React dependencies) ──
		$asset_file = $this->build_path . 'js/dist/chat-assistant.asset.php';
		$asset      = file_exists( $asset_file ) ? require $asset_file : array(
			'dependencies' => array(),
			'version'      => ZIP_AI_VERSION,
		);
		$react_deps = array_merge( $asset['dependencies'], array( $this->enqueue_prefix . '-bridge-host' ) );

		$this->script_operations(
			'chat-assistant',
			$this->build_url . 'js/dist/chat-assistant.js',
			$react_deps,
			array(),
			$asset['version']
		);

		// ── React app styles ──
		$this->style_operations(
			'chat-assistant',
			$this->build_url . 'css/dist/chat-assistant.css'
		);

		// ── Gutenberg editor plugin (block editor only) ──
		if ( is_admin() ) {
			$screen = get_current_screen();
			if ( $screen && $screen->is_block_editor() ) {
				$editor_file = $use_source ? 'js/editor/editor-plugin.js' : 'js/dist/zip-ai-editor.min.js';
				$this->script_operations(
					'editor-plugin',
					$this->build_url . $editor_file,
					array( 'wp-plugins', 'wp-element', 'wp-i18n', 'wp-components', 'wp-data', 'wp-edit-post', 'wp-editor' )
				);
			}
		}

		// ── Localize bridge config ──
		$this->localize_script(
			'tool-hooks',
			'zipAiIframeConfig',
			array(
				'nonce'                 => wp_create_nonce( 'zip_ai_iframe' ),
				'restNonce'             => wp_create_nonce( 'wp_rest' ),
				'ajaxUrl'               => admin_url( 'admin-ajax.php' ),
				'displayMode'           => $this->is_fullpage_screen() ? 'fullpage' : 'sidebar',
				'adminHomeUrl'          => admin_url(),
				'userId'                => get_current_user_id(),
				'authUrl'               => $auth_url,
				'websiteContext'        => array(
					'site_url'     => get_site_url(),
					'admin_url'    => admin_url(),
					'site_title'   => get_bloginfo( 'name' ),
					'site_tagline' => get_bloginfo( 'description' ),
					'language'     => get_bloginfo( 'language' ),
					'timezone'     => wp_timezone_string(),
					'date_format'  => get_option( 'date_format' ),
					'time_format'  => get_option( 'time_format' ),
					'is_multisite' => is_multisite(),
				),
				'pageContext'           => $this->get_current_page_context(),
				'themeContext'          => array(
					'color_palette' => $this->get_theme_color_palette(),
				),
				'installedPlugins'      => $this->get_installed_plugins_versions(),
				'blockScannerTextLimit' => apply_filters( 'zip_ai_block_scanner_text_limit', 100 ),
			)
		);

		// ── Localize React app config ──
		$this->localize_script(
			'chat-assistant',
			'ZIP_AI_CONFIG',
			array(
				'apiUrl'          => rtrim( ZIP_AI_CREDIT_SERVER_API, '/' ),
				'token'           => Helper::get_decrypted_auth_token(),
				'isAuthenticated' => Helper::is_authorized(),
				'displayMode'     => $this->is_fullpage_screen() ? 'fullpage' : 'sidebar',
				'fullPageUrl'     => admin_url( 'options-general.php?page=zip-ai-assistant' ),
				'adminHomeUrl'    => admin_url(),
				'userId'          => get_current_user_id(),
				'domain'          => wp_parse_url( home_url(), PHP_URL_HOST ),
				'user'            => array(
					'id'    => get_current_user_id(),
					'email' => Helper::get_setting( 'user_email', '' ),
					'name'  => Helper::get_setting( 'user_name', '' ),
				),
				'isFreshSite'     => (bool) get_option( 'fresh_site', false ),
				'nonce'           => wp_create_nonce( 'zip_ai_iframe' ),
				'ajaxUrl'         => admin_url( 'admin-ajax.php' ),
				'authUrl'         => $auth_url,
			)
		);
	}

	/**
	 * Enqueue individual source scripts for development/debugging.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	private function enqueue_source_scripts() {
		$this->script_operations(
			'tool-hooks',
			$this->build_url . 'js/core/tool-hooks-registry.js',
			array()
		);

		// tool-context-provider-registry.js was removed in the page-delivery refactor.
		// Only enqueue if it still exists on disk; bridge-host's dependency on it
		// is stripped below when missing.
		$context_registry_path = $this->build_path . 'js/core/tool-context-provider-registry.js';
		$has_context_registry  = file_exists( $context_registry_path );
		if ( $has_context_registry ) {
			$this->script_operations(
				'tool-context-registry',
				$this->build_url . 'js/core/tool-context-provider-registry.js',
				array()
			);
		}

		$this->script_operations(
			'create-page-handler',
			$this->build_url . 'js/tools/zip-ai/create-page/handler.js',
			array( $this->enqueue_prefix . '-tool-hooks' )
		);

		$this->script_operations(
			'block-context-picker',
			$this->build_url . 'js/core/block-context-picker.js',
			array( $this->enqueue_prefix . '-tool-hooks' )
		);

		$bridge_deps = array(
			$this->enqueue_prefix . '-tool-hooks',
			$this->enqueue_prefix . '-block-context-picker',
		);
		if ( $has_context_registry ) {
			$bridge_deps[] = $this->enqueue_prefix . '-tool-context-registry';
		}
		$this->script_operations(
			'popover-drag',
			$this->build_url . 'js/core/popover-drag.js',
			array()
		);

		$bridge_deps[] = $this->enqueue_prefix . '-popover-drag';
		$this->script_operations(
			'bridge-host',
			$this->build_url . 'js/core/wp-bridge-host.js',
			$bridge_deps
		);

		// Block scanner and intent classifier — core orchestrator utilities.
		$this->script_operations(
			'block-scanner',
			$this->build_url . 'js/core/block-scanner.js',
			array( $this->enqueue_prefix . '-bridge-host' )
		);

		$this->script_operations(
			'intent-classifier',
			$this->build_url . 'js/core/intent-classifier.js',
			array( $this->enqueue_prefix . '-block-scanner' )
		);
	}

	/**
	 * Enqueue combined minified script for production.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	private function enqueue_minified_scripts() {
		$this->script_operations(
			'tool-hooks',
			$this->build_url . 'js/dist/zip-ai.min.js',
			array()
		);

		// Tool context registry is bundled in the same file — virtual handle.
		$this->register_script( 'tool-context-registry', false, array( $this->enqueue_prefix . '-tool-hooks' ) );
		$this->enqueue_script( 'tool-context-registry' );

		// Bridge host is also bundled — virtual handle for dependency chain.
		$this->register_script( 'bridge-host', false, array( $this->enqueue_prefix . '-tool-hooks' ) );
		$this->enqueue_script( 'bridge-host' );
	}

	/**
	 * Get current page/post context from PHP.
	 *
	 * @since 0.0.1
	 * @return array
	 */
	private function get_current_page_context() {
		global $post;

		$context = array(
			'post_id'     => null,
			'post_type'   => null,
			'post_title'  => null,
			'post_status' => null,
		);

		if ( $post instanceof \WP_Post ) {
			$context['post_id']     = $post->ID;
			$context['post_type']   = $post->post_type;
			$context['post_title']  = $post->post_title;
			$context['post_status'] = $post->post_status;
			return $context;
		}

		if ( is_admin() ) {
			$post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			if ( $post_id ) {
				$admin_post = get_post( $post_id );
				if ( $admin_post instanceof \WP_Post ) {
					$context['post_id']     = $admin_post->ID;
					$context['post_type']   = $admin_post->post_type;
					$context['post_title']  = $admin_post->post_title;
					$context['post_status'] = $admin_post->post_status;
				}
			}
		}

		return $context;
	}

	/**
	 * Get authentication URL with CSRF protection for ZipWP OAuth callback.
	 *
	 * @since 0.0.1
	 * @return string
	 */
	private function get_auth_url() {
		$transient_key = 'zip_ai_oauth_state_' . get_current_user_id();

		// Reuse existing state if still valid — prevents overwriting during auth popup flow.
		$state = get_transient( $transient_key );
		if ( empty( $state ) ) {
			$state = wp_generate_password( 32, false );
			set_transient( $transient_key, $state, 10 * MINUTE_IN_SECONDS );
		}

		$redirect_url = add_query_arg(
			array(
				'nonce'       => wp_create_nonce( 'zip_ai_auth_nonce' ),
				'state'       => $state,
				'zip-ai-auth' => 'true',
			),
			admin_url( 'options-general.php?page=zip-ai-assistant' )
		);

		$auth_middleware = ZIP_AI_MIDDLEWARE;

		$auth_url = add_query_arg(
			array(
				'type'         => 'token',
				'redirect_url' => rawurlencode( $redirect_url ),
				'state'        => $state,
			),
			$auth_middleware
		);

		return $auth_url;
	}

	/**
	 * Get theme color palette formatted as CSS variables.
	 *
	 * @since 0.0.1
	 * @return array
	 */
	private function get_theme_color_palette() {
		if ( ! function_exists( 'astra_get_palette_colors' ) ) {
			return array();
		}

		$palette_data    = astra_get_palette_colors();
		$current_palette = $palette_data['currentPalette'] ?? '';
		$palettes        = $palette_data['palettes'] ?? array();

		if ( empty( $current_palette ) || empty( $palettes[ $current_palette ] ) ) {
			return array();
		}

		$colors            = $palettes[ $current_palette ];
		$formatted_palette = array();

		foreach ( $colors as $index => $color ) {
			$formatted_palette[ '--ast-global-color-' . $index ] = $color;
		}

		return $formatted_palette;
	}

	/**
	 * Get installed plugins with their versions.
	 *
	 * @since 0.0.1
	 * @return array
	 */
	private function get_installed_plugins_versions() {
		if ( ! function_exists( 'get_plugins' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$all_plugins     = get_plugins();
		$plugin_versions = array();

		foreach ( $all_plugins as $plugin_file => $plugin_data ) {
			$slug = dirname( $plugin_file );
			if ( '.' === $slug ) {
				$slug = basename( $plugin_file, '.php' );
			}
			$plugin_versions[ $slug ] = $plugin_data['Version'] ?? '0.0.0';
		}

		return $plugin_versions;
	}

	/**
	 * Render the assistant container HTML.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	public function render_container() {
		if ( ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
			return;
		}

		// On dedicated full-page screen we render a different container.
		if ( is_admin() && $this->is_fullpage_screen() ) {
			return;
		}

		?>
		<div id="zip-ai-assistant-container" class="zip-ai-panel-container" inert>
			<!-- React app mounts here (trigger + resize handle rendered by React via portals) -->
			<div id="chat-assistant-root"></div>
		</div>
		<?php
	}

	/**
	 * Render dedicated full-page assistant admin screen.
	 *
	 * @since 0.0.1
	 * @return void
	 */
	public function render_fullpage_screen() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		// Note: no .wrap class — we bypass WP's default margin/padding for a true full-bleed layout.
		?>
		<div class="zip-ai-fullpage-screen">
			<div id="zip-ai-fullpage-container" class="zip-ai-fullpage-container">
				<div id="chat-assistant-root"></div>
			</div>
		</div>
		<?php
	}

	/**
	 * Check if current admin page is dedicated full-page assistant.
	 *
	 * @since 0.0.1
	 * @return bool
	 */
	private function is_fullpage_screen() {
		if ( ! is_admin() ) {
			return false;
		}

		$screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
		if ( $screen && 'settings_page_zip-ai-assistant' === $screen->id ) {
			return true;
		}

		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return 'zip-ai-assistant' === $page;
	}
}

```
