settingsView = $settingsView ?: new SettingsView(); $this->ajaxHandler = $ajaxHandler ?: new AjaxHandler(); } /** * Registers admin hooks. */ public function register(): void { add_action('admin_menu', [$this, 'registerMenu']); add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']); add_action('admin_init', [$this, 'handleFormPost']); add_action('admin_init', [$this, 'handleWizardQueryActions']); $this->ajaxHandler->register(); } /** * Handles query-parameter driven wizard completion or relaunch requests. */ public function handleWizardQueryActions(): void { if (function_exists('current_user_can') && !current_user_can('manage_options')) { return; } // When completing wizard if (isset($_GET['wizard_completed']) && sanitize_text_field(wp_unslash($_GET['wizard_completed'])) === '1') { check_admin_referer('wp_zaraz_wizard_action'); \WPZaraz\Support\Options::updateMany([ 'wizard_completed' => true, 'wizard_banner_dismissed' => true, ]); } // When manually dismissing the onboarding banner if (isset($_GET['dismiss_wizard_banner']) && sanitize_text_field(wp_unslash($_GET['dismiss_wizard_banner'])) === '1') { check_admin_referer('wp_zaraz_wizard_action'); \WPZaraz\Support\Options::set('wizard_banner_dismissed', true); } // When resetting wizard status from diagnostics if (isset($_GET['reset_wizard']) && sanitize_text_field(wp_unslash($_GET['reset_wizard'])) === '1') { check_admin_referer('wp_zaraz_wizard_action'); \WPZaraz\Support\Options::updateMany([ 'wizard_completed' => false, 'wizard_banner_dismissed' => false, ]); } } /** * Handles traditional HTTP POST form submission as a fallback. */ public function handleFormPost(): void { if (!isset($_POST['wp_zaraz_admin_save']) || (function_exists('current_user_can') && !current_user_can('manage_options'))) { return; } if (function_exists('check_admin_referer')) { check_admin_referer('wp_zaraz_save_action', 'wp_zaraz_nonce'); } $data = wp_unslash($_POST); unset($data['wp_zaraz_admin_save'], $data['wp_zaraz_nonce'], $data['_wp_http_referer']); $sanitized = \WPZaraz\Support\Options::sanitizeSubmitted((array) $data); $isProActive = class_exists('\WPZaraz\Licensing\LicenseManager') && \WPZaraz\Licensing\LicenseManager::isPro(); if (!$isProActive) { $sanitized['connection_mode'] = 'manual'; } \WPZaraz\Support\Options::updateMany($sanitized); if (function_exists('add_settings_error')) { add_settings_error('wp_zaraz', 'settings_saved', 'Settings saved successfully!', 'success'); } } /** * Registers top-level menu page. */ public function registerMenu(): void { add_menu_page( 'Zbridge', 'Zbridge', 'manage_options', 'wp-zbridge', [$this->settingsView, 'render'], 'dashicons-cloud', 80 ); add_submenu_page( 'wp-zbridge', __('Settings & Dashboard', 'zbridge'), __('Dashboard', 'zbridge'), 'manage_options', 'wp-zbridge', [$this->settingsView, 'render'] ); // Hidden routes (no sidebar item): the Setup Wizard and backward compatibility aliases $wizardView = new WizardView(); $hiddenPages = [ ['wp-zbridge-wizard', __('Setup Wizard', 'zbridge'), [$wizardView, 'render']], ['wp-zaraz', 'Zbridge', [$this->settingsView, 'render']], ['wp-zaraz-wizard', __('Setup Wizard', 'zbridge'), [$wizardView, 'render']], ]; foreach ($hiddenPages as [$slug, $pageTitle, $callback]) { $hook = add_submenu_page(null, $pageTitle, $pageTitle, 'manage_options', $slug, $callback); if ($hook) { // Pages without a parent have no admin title, which PHP 8.1+ reports as deprecated add_action('load-' . $hook, static function () use ($pageTitle) { $GLOBALS['title'] = $pageTitle; }); } } } /** * Enqueues admin stylesheet and JavaScript. * * @param string $hook */ public function enqueueAssets(string $hook = ''): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $currentPage = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; $isPluginPage = (strpos($hook, 'zbridge') !== false || strpos($hook, 'wp-zaraz') !== false) || in_array($currentPage, ['zbridge', 'wp-zbridge', 'wp-zaraz', 'wp-zbridge-wizard', 'wp-zaraz-wizard', 'zbridge-wizard'], true); if (!$isPluginPage) { return; } $baseUrl = defined('ZBRIDGE_URL') ? ZBRIDGE_URL : (defined('WP_ZBRIDGE_URL') ? WP_ZBRIDGE_URL : (defined('WP_ZARAZ_URL') ? WP_ZARAZ_URL : '')); $basePath = defined('ZBRIDGE_PATH') ? ZBRIDGE_PATH : (defined('WP_ZBRIDGE_PATH') ? WP_ZBRIDGE_PATH : (defined('WP_ZARAZ_PATH') ? WP_ZARAZ_PATH : dirname(__DIR__, 2) . '/')); $defaultVer = defined('ZBRIDGE_VERSION') ? ZBRIDGE_VERSION : (defined('WP_ZBRIDGE_VERSION') ? WP_ZBRIDGE_VERSION : (defined('WP_ZARAZ_VERSION') ? WP_ZARAZ_VERSION : '1.2.7')); $cssFile = $basePath . 'assets/css/admin.css'; $cssVer = file_exists($cssFile) ? (string) filemtime($cssFile) : $defaultVer; $jsFile = $basePath . 'assets/js/admin.js'; $jsVer = file_exists($jsFile) ? (string) filemtime($jsFile) : $defaultVer; wp_enqueue_style( 'wp-zbridge-admin', $baseUrl . 'assets/css/admin.css', [], $cssVer ); wp_enqueue_script( 'wp-zbridge-admin', $baseUrl . 'assets/js/admin.js', ['jquery'], $jsVer, true ); $nonce = function_exists('wp_create_nonce') ? wp_create_nonce('wp_zaraz_admin_nonce') : 'mock_nonce'; $ajaxUrl = function_exists('admin_url') ? admin_url('admin-ajax.php') : ''; $l10n = [ 'ajaxUrl' => $ajaxUrl, 'nonce' => $nonce, 'homeUrl' => function_exists('home_url') ? home_url('/') : '/', 'i18n' => [ 'rollback' => __('Roll back last sync', 'zbridge'), 'rollbackConfirm' => __('Put back the Zaraz configuration from before your last sync? Changes made in the Cloudflare dashboard since then are undone too.', 'zbridge'), 'rollbackFailed' => __('Rollback failed.', 'zbridge'), 'rolledBack' => __('Rolled back.', 'zbridge'), 'rollingBack' => __('Rolling back…', 'zbridge'), 'activate' => __('Activate', 'zbridge'), 'activated' => __('Activated!', 'zbridge'), 'activating' => __('Activating...', 'zbridge'), 'catAnalytics' => __('Analytics', 'zbridge'), 'catFunctional' => __('Functional', 'zbridge'), 'catMarketing' => __('Marketing', 'zbridge'), 'catNecessary' => __('Necessary', 'zbridge'), 'catUnrecognised' => __('Not recognised', 'zbridge'), 'checkStatus' => __('Check Status', 'zbridge'), 'checking' => __('Checking…', 'zbridge'), 'checkingLicense' => __('Checking...', 'zbridge'), 'colAbout' => __('What it is', 'zbridge'), 'colCategory' => __('Category', 'zbridge'), 'colName' => __('Name', 'zbridge'), 'colProvider' => __('Provider', 'zbridge'), 'colType' => __('Type', 'zbridge'), 'connectionFailed' => __('Connection failed', 'zbridge'), 'connectionNetworkError' => __('Network error testing connection', 'zbridge'), 'connectionOk' => __('Connection verified!', 'zbridge'), 'copied' => __('Copied to the clipboard.', 'zbridge'), 'copyManually' => __('Select the text below and copy it.', 'zbridge'), 'deactivateConfirm' => __('Are you sure you want to deactivate your license on this site? This will release your site slot on the edge registry so you can use it on another domain.', 'zbridge'), 'deactivateFailed' => __('Deactivation failed.', 'zbridge'), 'deactivateLicense' => __('Deactivate License', 'zbridge'), 'deactivated' => __('Deactivated!', 'zbridge'), 'deactivating' => __('Deactivating...', 'zbridge'), 'dispatchFailed' => __('Sending failed.', 'zbridge'), 'dispatched' => __('Dispatched!', 'zbridge'), 'dispatching' => __('Dispatching…', 'zbridge'), 'downloadFailed' => __('Could not build the container.', 'zbridge'), 'downloadJson' => __('Download Container JSON', 'zbridge'), 'edgeFailed' => __('Could not reach the Cloudflare edge.', 'zbridge'), 'edgeFailedProxy' => __('Connection failed. Check that the site is proxied through Cloudflare.', 'zbridge'), 'error' => __('An error occurred', 'zbridge'), 'failed' => __('Failed', 'zbridge'), 'generating' => __('Generating…', 'zbridge'), 'handshakeConnecting' => __('Connecting to Cloudflare Anycast edge...', 'zbridge'), 'handshakeReceived' => __('Handshake response received.', 'zbridge'), 'handshakeTesting' => __('Testing Edge Handshake...', 'zbridge'), 'invalidJson' => __('Server returned invalid JSON response. Please check PHP error logs.', 'zbridge'), 'licenseActive' => __('License is active.', 'zbridge'), 'licenseCheckFailed' => __('License check failed.', 'zbridge'), 'licenseNetworkError' => __('Network error while contacting licensing gateway.', 'zbridge'), 'networkError' => __('Network error.', 'zbridge'), 'networkErrorShort' => __('Network error', 'zbridge'), 'recheck' => __('Recheck', 'zbridge'), 'refundDispatched' => __('Refund Dispatched!', 'zbridge'), 'reportFailed' => __('Could not build the report.', 'zbridge'), 'saveChanges' => __('Save Changes', 'zbridge'), 'saveFailed' => __('Failed to save settings', 'zbridge'), 'saveNetworkError' => __('Network error saving settings', 'zbridge'), 'saving' => __('Saving...', 'zbridge'), 'scanAccepted' => __('You have accepted all cookies in this browser, so this shows what visitors who accept get.', 'zbridge'), 'scanClassifying' => __('Classifying what was found...', 'zbridge'), 'scanFailed' => __('Scan failed', 'zbridge'), /* translators: %d: number of items found */ 'scanFound' => __('Found %d cookies, storage keys and third-party sites.', 'zbridge'), 'scanHttpOnly' => __('Cookies only the server can read (HttpOnly) are listed when the homepage sets them. Cookies from earlier visits in this browser are included.', 'zbridge'), 'scanLoading' => __('Loading your homepage in this browser...', 'zbridge'), 'scanNetworkError' => __('Network error during the scan.', 'zbridge'), 'scanNoBrowser' => __('Your homepage could not be loaded in this browser (it may be on a different domain than wp-admin), so only cookies the server sets are listed.', 'zbridge'), 'scanNoChoice' => __('You have not made a cookie banner choice in this browser, so tools that wait for consent may be missing.', 'zbridge'), 'scanNothing' => __('Nothing found.', 'zbridge'), 'scanPartial' => __('You have not accepted all cookies in this browser, so tools that wait for consent may be missing. Accept all in the cookie banner and scan again to see everything.', 'zbridge'), /* translators: %s: error message */ 'scanServerNote' => __('The server could not fetch the homepage: %s', 'zbridge'), 'scanning' => __('Scanning...', 'zbridge'), 'sendPurchase' => __('Dispatch Simulated Test Purchase', 'zbridge'), 'sendRefund' => __('Dispatch Simulated Test Refund', 'zbridge'), 'sendingPurchase' => __('Sending test purchase...', 'zbridge'), 'sendingRefund' => __('Sending test refund...', 'zbridge'), 'settingsSaved' => __('Settings saved!', 'zbridge'), 'settingsSavedFull' => __('Settings saved successfully!', 'zbridge'), 'sync' => __('Sync to Zaraz', 'zbridge'), 'syncConfirm' => __('Sync your current settings to Cloudflare Zaraz?', 'zbridge'), 'syncFailed' => __('Sync failed.', 'zbridge'), 'syncNeedsCredentials' => __('Enter your Cloudflare API token and zone ID on the Cloudflare tab first.', 'zbridge'), 'synced' => __('Synced to Zaraz.', 'zbridge'), 'syncing' => __('Syncing…', 'zbridge'), 'testConnection' => __('Test Connection', 'zbridge'), 'testEventSent' => __('Test event sent to Zaraz.', 'zbridge'), 'testing' => __('Testing...', 'zbridge'), ], ]; wp_localize_script('wp-zbridge-admin', 'wpZarazAdmin', $l10n); wp_localize_script('wp-zbridge-admin', 'wpZbridgeAdmin', $l10n); } }