HEX
Server: nginx
System: Linux pool195-106-36.bur.atomicsites.net 6.12.57+deb12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1~bpo12+1 (2025-11-17) x86_64
User: (0)
PHP: 8.3.32
Disabled: pcntl_fork
Upload Files
File: //wordpress/plugins/wp-cloud-client/1.1.9/src/Plugin.php
<?php

declare(strict_types=1);

namespace VPlugins\WPCloudClient;

use VPlugins\WPCloudClient\Analytics\GoogleAnalytics4;
use VPlugins\WPCloudClient\Cleanup\GtmCleanup;
use VPlugins\WPCloudClient\BusinessProfile\FieldRenderer;
use VPlugins\WPCloudClient\BusinessProfile\ProfileRepository;
use VPlugins\WPCloudClient\BusinessProfile\LegacyShortcodeCompat;
use VPlugins\WPCloudClient\BusinessProfile\ProfileAdminNotice;
use VPlugins\WPCloudClient\BusinessProfile\ProfileBlock;
use VPlugins\WPCloudClient\BusinessProfile\ProfileHelperPage;
use VPlugins\WPCloudClient\BusinessProfile\ProfileShortcode;
use VPlugins\WPCloudClient\Api\Controller\ActionController;
use VPlugins\WPCloudClient\Api\Controller\MailController;
use VPlugins\WPCloudClient\Api\Controller\SubsiteController;
use VPlugins\WPCloudClient\Api\Controller\WooCommerceController;
use VPlugins\WPCloudClient\Api\RestRouter;
use VPlugins\WPCloudClient\Auth\AuthMiddleware;
use VPlugins\WPCloudClient\Auth\ApiKeyAuthenticator;
use VPlugins\WPCloudClient\Access\LoginInterceptor;
use VPlugins\WPCloudClient\Access\LoginPageGuard;
use VPlugins\WPCloudClient\Access\LoginTokenManager;
use VPlugins\WPCloudClient\Handler\CheckUserHandler;
use VPlugins\WPCloudClient\Handler\CheckPluginUpdatesHandler;
use VPlugins\WPCloudClient\Handler\CheckThemeUpdatesHandler;
use VPlugins\WPCloudClient\Handler\GetSiteOptionsHandler;
use VPlugins\WPCloudClient\Handler\HandlerRegistry;
use VPlugins\WPCloudClient\Handler\HealthCheckHandler;
use VPlugins\WPCloudClient\Handler\ListPluginsHandler;
use VPlugins\WPCloudClient\Handler\ListThemesHandler;
use VPlugins\WPCloudClient\Handler\LogoutHandler;
use VPlugins\WPCloudClient\Handler\OneClickLoginHandler;
use VPlugins\WPCloudClient\Handler\PhpVerifyHandler;
use VPlugins\WPCloudClient\Handler\SetSiteOptionsHandler;
use VPlugins\WPCloudClient\Handler\SiteDetailsHandler;
use VPlugins\WPCloudClient\Handler\SiteSummaryHandler;
use VPlugins\WPCloudClient\Handler\SyncProfileHandler;
use VPlugins\WPCloudClient\Handler\ActivatePluginHandler;
use VPlugins\WPCloudClient\Handler\BootstrapSiteHandler;
use VPlugins\WPCloudClient\Handler\TriggerAllPluginUpdatesHandler;
use VPlugins\WPCloudClient\Handler\TriggerAllThemeUpdatesHandler;
use VPlugins\WPCloudClient\Handler\TriggerPluginUpdateHandler;
use VPlugins\WPCloudClient\Handler\TriggerThemeUpdateHandler;
use VPlugins\WPCloudClient\Handler\TriggerJetpackBackupHandler;
use VPlugins\WPCloudClient\Handler\WooCommerceDataHandler;
use VPlugins\WPCloudClient\Debug\WpConfigEditor;
use VPlugins\WPCloudClient\Handler\ConfigureMultisiteHandler;
use VPlugins\WPCloudClient\Handler\GetDebugStatusHandler;
use VPlugins\WPCloudClient\Handler\NetworkInstallHandler;
use VPlugins\WPCloudClient\Handler\SetDebugModeHandler;
use VPlugins\WPCloudClient\Handler\SetLoginVisibilityHandler;
use VPlugins\WPCloudClient\Mail\MailCleaner;
use VPlugins\WPCloudClient\Cli\BackfillCommand;
use VPlugins\WPCloudClient\Integration\WooCommerceHelper;
use VPlugins\WPCloudClient\Integration\WooCommerceOrderCaller;
use VPlugins\WPCloudClient\Integration\WooCommerceProductCaller;
use VPlugins\WPCloudClient\Multisite\DomainProtector;
use VPlugins\WPCloudClient\Multisite\SubsiteLimit;
use VPlugins\WPCloudClient\Multisite\SubsiteSync;
use VPlugins\WPCloudClient\Multisite\SuperAdminManager;
use VPlugins\WPCloudClient\Mail\FormPluginReplyTo;
use VPlugins\WPCloudClient\Mail\MailManager;
use VPlugins\WPCloudClient\Mail\MailSettings;
use VPlugins\WPCloudClient\Mail\MailTable;
use VPlugins\WPCloudClient\Settings\SettingsPage;
use VPlugins\WPCloudClient\SiteOptions\OptionRegistry;
use VPlugins\WPCloudClient\Settings\SettingsRepository;
use VPlugins\WPCloudClient\Support\Clock;
use VPlugins\WPCloudClient\Support\Logger;
use VPlugins\WPCloudClient\Support\RollbackManager;

final class Plugin {

	private static ?self $instance = null;
	private bool $booted           = false;

	private readonly SettingsRepository $settings;
	private readonly HandlerRegistry $handlers;
	private readonly Logger $logger;
	private readonly Clock $clock;
	private readonly MailTable $mailTable;
	private LoginTokenManager $tokenManager;
	private WooCommerceDataHandler $wooCommerceDataHandler;

	/**
	 * Initialize plugin dependencies.
	 */
	private function __construct() {
		$this->settings  = new SettingsRepository();
		$this->handlers  = new HandlerRegistry();
		$this->logger    = new Logger();
		$this->clock     = new Clock();
		$this->mailTable = MailTable::getInstance();
	}

	/**
	 * Retrieve the singleton plugin instance.
	 *
	 * @return self The plugin instance.
	 */
	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Boot the plugin by registering handlers and hooks.
	 *
	 * @return void
	 */
	public function boot(): void {
		if ( $this->booted ) {
			return;
		}

		$this->booted = true;

		// Register the fatal error reporter as early as possible so even a
		// fatal during plugin init is caught and reported to the manager.
		\VPlugins\WPCloudClient\Support\ErrorReporter::getInstance()->register();

		$this->registerHandlers();
		$this->registerHooks();
	}

	/**
	 * Register all action handlers with the handler registry.
	 *
	 * @return void
	 */
	private function registerHandlers(): void {
		$this->tokenManager = new LoginTokenManager();

		// Integration helpers
		$optionRegistry = new OptionRegistry();

		// Core handlers
		$this->handlers->register( new HealthCheckHandler( $this->settings, $this->clock ) );
		$this->handlers->register( new OneClickLoginHandler( $this->tokenManager ) );

		// Site info handlers
		$this->handlers->register( new SiteSummaryHandler( $this->settings ) );
		$this->handlers->register( new SiteDetailsHandler() );
		$this->handlers->register( new CheckUserHandler() );
		$this->handlers->register( new PhpVerifyHandler() );
		$this->handlers->register( new LogoutHandler() );

		// Integration handlers
		$this->wooCommerceDataHandler = new WooCommerceDataHandler( $this->settings );
		$this->handlers->register( $this->wooCommerceDataHandler );
		$this->handlers->register( new SyncProfileHandler() );

		// Jetpack handlers
		$this->handlers->register( new TriggerJetpackBackupHandler( $this->settings, $this->logger ) );

		// Login visibility
		$this->handlers->register( new SetLoginVisibilityHandler( $this->settings ) );

		// Site options handlers
		$this->handlers->register( new GetSiteOptionsHandler( $optionRegistry ) );
		$this->handlers->register( new SetSiteOptionsHandler( $optionRegistry ) );
		$this->handlers->register( new BootstrapSiteHandler( $optionRegistry ) );

		// Plugin update handlers
		$this->handlers->register( new CheckPluginUpdatesHandler() );
		$this->handlers->register( new ListPluginsHandler() );
		$rollbackManager = new RollbackManager();
		$rollbackManager->migrateFromLegacyKey();
		$triggerPluginUpdate = new TriggerPluginUpdateHandler( $rollbackManager, $this->logger );
		$this->handlers->register( $triggerPluginUpdate );
		$this->handlers->register( new TriggerAllPluginUpdatesHandler( $triggerPluginUpdate, $this->logger ) );
		$this->handlers->register( new ActivatePluginHandler() );

		// Theme update handlers
		$this->handlers->register( new CheckThemeUpdatesHandler() );
		$this->handlers->register( new ListThemesHandler() );
		$triggerThemeUpdate = new TriggerThemeUpdateHandler( $rollbackManager, $this->logger );
		$this->handlers->register( $triggerThemeUpdate );
		$this->handlers->register( new TriggerAllThemeUpdatesHandler( $triggerThemeUpdate, $this->logger ) );

		// Multisite network installer
		if ( \defined( 'WP_ALLOW_MULTISITE' ) && \WP_ALLOW_MULTISITE ) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound
			$this->handlers->register( new NetworkInstallHandler( $this->logger ) );
		}

		// Multisite configuration
		$this->handlers->register( new ConfigureMultisiteHandler( new WpConfigEditor(), $this->logger ) );

		// Debug
		$debugEditor = new WpConfigEditor();
		$this->handlers->register( new GetDebugStatusHandler( $debugEditor ) );
		$this->handlers->register( new SetDebugModeHandler( $debugEditor ) );
	}

	/**
	 * Register WordPress hooks for REST API, login, analytics, and admin.
	 *
	 * @return void
	 */
	private function registerHooks(): void {
		// REST API
		$authenticator  = new ApiKeyAuthenticator( $this->settings );
		$authMiddleware = new AuthMiddleware( $authenticator );
		$controller     = new ActionController( $this->handlers, $this->logger );
		$wooCommerce    = new WooCommerceController( $this->wooCommerceDataHandler, $this->logger );
		$mailApi        = new MailController( $this->mailTable, $this->logger );
		$subsiteApi     = new SubsiteController( $this->logger );
		$router         = new RestRouter( $authMiddleware, $controller, $wooCommerce, $mailApi, $subsiteApi );

		add_action( 'rest_api_init', [ $router, 'register' ] );

		// One-click login interceptor
		$loginInterceptor = new LoginInterceptor( $this->tokenManager, $this->logger );
		add_action( 'init', [ $loginInterceptor, 'handle' ] );

		// Login page guard — hides wp-login.php from unauthenticated visitors
		// when the manager has not exposed it. Registered after the interceptor
		// so the one-click login token is consumed first.
		$loginGuard = new LoginPageGuard( $this->settings );
		add_action( 'init', [ $loginGuard, 'handle' ] );

		// Google Analytics 4
		$ga4 = new GoogleAnalytics4( $this->settings );
		if ( ! $this->settings->isStaging() ) {
			$ga4->register();
		}

		// Ensure the mail table exists on every boot (dbDelta is idempotent).
		try {
			$this->mailTable->initialize();
		} catch ( \Throwable $e ) {
			$this->logger->error( 'Mail table initialization failed', $e );
		}

		// Mail interceptor + REST cleanup endpoint
		$mailSettings = new MailSettings( $this->settings );
		$mailManager  = MailManager::init( $this->mailTable, $this->settings, $this->logger );
		$mailCleaner  = new MailCleaner( $mailManager, $authMiddleware, $this->logger );

		if ( ! $mailSettings->isDisabled() ) {
			add_action( 'init', [ $mailManager, 'register' ] );

			// WP-Cron retry: drain any emails that failed to notify on the first attempt.
			// phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval -- 5-min retry is intentional for email notify resilience.
			add_filter( 'cron_schedules', [ $this, 'addCronSchedules' ] );
			add_action( 'wpcc_mail_notify', [ $mailManager, 'flush' ] );
			if ( ! wp_next_scheduled( 'wpcc_mail_notify' ) ) {
				wp_schedule_event( time(), 'wpcc_5min', 'wpcc_mail_notify' );
			}
		} else {
			// Clear any previously scheduled cron now that the interceptor is off.
			$timestamp = wp_next_scheduled( 'wpcc_mail_notify' );
			if ( $timestamp ) {
				wp_unschedule_event( $timestamp, 'wpcc_mail_notify' );
			}
		}

		add_action( 'rest_api_init', [ $mailCleaner, 'register' ] );

		// Business Profile rendering (isolated so a failure here does not break admin settings)
		$profileHelper = null;

		try {
			$profileRepo      = new ProfileRepository();
			$fieldRenderer    = new FieldRenderer();
			$profileShortcode = new ProfileShortcode( $profileRepo, $fieldRenderer );
			$profileShortcode->register();
			$legacyCompat = new LegacyShortcodeCompat( $profileRepo, $fieldRenderer );
			$legacyCompat->register();
			$profileBlock = new ProfileBlock( $profileRepo );
			$profileBlock->register();
			$profileNotice = new ProfileAdminNotice( $profileRepo );
			$profileNotice->register();
			$profileHelper = new ProfileHelperPage( $profileRepo, $fieldRenderer, $profileNotice );
		} catch ( \Throwable $e ) {
			$this->logger->error( 'Failed to register BusinessProfile module', $e );
		}

		// Admin settings
		$settingsPage = new SettingsPage( $this->settings, $ga4, $mailSettings, $profileHelper );

		add_action( 'admin_menu', [ $settingsPage, 'addMenuPage' ] );
		add_action( 'admin_init', [ $settingsPage, 'registerSettings' ] );

		if ( null !== $profileHelper ) {
			add_action( 'admin_enqueue_scripts', [ $profileHelper, 'enqueueAssets' ] );
		}

		// Multisite features
		if ( $this->settings->isMultisite() ) {
			( new DomainProtector() )->register();
			( new SubsiteLimit( $this->settings ) )->register();
			( new SubsiteSync( $this->settings, $this->logger ) )->register();
			( new SuperAdminManager() )->register();
		}

		// CF7, GF, WPForms, Ninja Forms, Formidable — enrich Reply-To with visitor name for CRM lead capture.
		if ( FormPluginReplyTo::isActive() ) {
			( new FormPluginReplyTo() )->register();
		}

		// WooCommerce outbound callers — POST order and product events to wp-cloud-manager.
		if ( WooCommerceHelper::isActive() ) {
			( new WooCommerceOrderCaller( $this->settings, $this->logger ) )->register();
			( new WooCommerceProductCaller( $this->settings, $this->logger ) )->register();
		}

		// WP-CLI commands (backfill after migration cutover).
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			$backfillCommand = new BackfillCommand( $this->settings, $this->logger );
			\WP_CLI::add_command( 'wpcc', $backfillCommand );
		}

		add_action( 'plugins_loaded', [ $this, 'maybeMigrate' ] );
	}

	/**
	 * Run first-install and upgrade migrations on every boot, gated by a stored version key.
	 *
	 * Runs on `plugins_loaded`. As a mu-plugin, activation/deactivation hooks never fire,
	 * so all one-time setup lives here and executes once per version change.
	 *
	 * @return void
	 */
	public function maybeMigrate(): void {
		if ( get_option( 'wpcc_db_version' ) === VPLUGINS_WPCC_VERSION ) {
			return;
		}

		if ( empty( $this->settings->all() ) ) {
			$this->settings->update(
				[
					'debug_mode'               => '',
					'ga4_tracking_id'          => '',
					'disable_mail_interceptor' => '',
				]
			);
		}

		$this->settings->purge( [ 'shared_secret', 'shared_secret_previous', 'environment', 'api_key', 'wp_cloud_manager_url' ] );

		// One-time GTM cleanup (WSP-3395): strip GTM-5VJ33FV6 from WPCode/IHAF options.
		( new GtmCleanup( $this->logger ) )->run();

		update_option( 'wpcc_db_version', VPLUGINS_WPCC_VERSION );
	}

	/**
	 * Register custom WP-Cron intervals used by this plugin.
	 *
	 * @internal WordPress `cron_schedules` filter callback.
	 *
	 * @param array<string, array{interval: int, display: string}> $schedules Existing schedules.
	 * @return array<string, array{interval: int, display: string}>
	 */
	public function addCronSchedules( array $schedules ): array {
		$schedules['wpcc_5min'] = [
			// phpcs:ignore WordPress.WP.CronInterval.CronSchedulesInterval -- 5-min retry is intentional for email notify resilience.
			'interval' => 300,
			'display'  => 'Every 5 Minutes (WP Cloud Client)',
		];
		return $schedules;
	}
}