consume_flash(); $aeo_audit_score = null; if ( null !== $aeo_bundle && isset( $aeo_bundle['audit']['score'] ) ) { $aeo_audit_score = (int) $aeo_bundle['audit']['score']; } // Per-user, so one admin hiding the call for testers does not hide it for the others. $aeo_beta_dismissed = (bool) get_user_meta( get_current_user_id(), 'aeo_connect_beta_dismissed', true ); require AEO_CONNECT_PLUGIN_DIR . 'admin/settings-page.php'; } /** * Global admin notices: revoked key (error) and stale sync (warning). * * @return void */ public function admin_notices() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( get_option( 'aeo_connect_revoked' ) ) { echo '

' . esc_html__( 'Everlange Connect:', 'aeo-tool-connect' ) . ' ' . esc_html__( 'this site key has been revoked from the everlange.com dashboard. All modules have been disabled. Generate a new pairing code and reconnect to resume optimizations.', 'aeo-tool-connect' ) . '

'; return; // Revocation supersedes the stale-sync warning. } if ( '' !== AEO_Connect_API_Client::get_site_key() && AEO_Connect_Cache::is_sync_stale() ) { echo '

' . esc_html__( 'Everlange Connect:', 'aeo-tool-connect' ) . ' ' . esc_html__( 'the last successful sync with everlange.com was more than 48 hours ago. The cached optimizations are still being served.', 'aeo-tool-connect' ) . '

'; } } /* --------------------------------------------------------------------- * Action handlers (admin-post.php) * ------------------------------------------------------------------ */ /** * Pair the site using the submitted pairing code. * * @return void */ public function handle_pair() { $this->guard(); check_admin_referer( 'aeo_connect_pair' ); $code = isset( $_POST['aeo_connect_pairing_code'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_POST['aeo_connect_pairing_code'] ) ) ) : ''; if ( ! preg_match( '/^[A-Z0-9]{8}$/', $code ) ) { $this->flash( 'error', __( 'Please enter the 8-character pairing code shown in your everlange.com dashboard.', 'aeo-tool-connect' ) ); $this->redirect_back(); } $result = AEO_Connect_API_Client::pair( $code ); if ( is_wp_error( $result ) ) { $this->flash( 'error', sprintf( /* translators: %s: error message returned by the API. */ __( 'Pairing failed: %s', 'aeo-tool-connect' ), $result->get_error_message() ) ); $this->redirect_back(); } delete_option( 'aeo_connect_revoked' ); update_option( 'aeo_connect_enabled', '1', false ); // Fix 2026-07-24: a revoke/disconnect saves an explicit all-OFF module // choice (aeo_connect_modules) which takes precedence over the bundle. // Without clearing it, a re-paired site silently injects NOTHING while // the SaaS reports successful applies. Re-pairing = fresh start, the // bundle's enabled_modules apply again. delete_option( 'aeo_connect_modules' ); // Fetch and apply the first bundle right away. AEO_Connect_Cache::sync(); $this->flash( 'success', sprintf( /* translators: %s: paired domain. */ __( 'Connected to everlange.com for %s. The first optimization bundle has been synced.', 'aeo-tool-connect' ), $result ) ); $this->redirect_back(); } /** * Disconnect: delete the site key and disable all modules. * * @return void */ public function handle_disconnect() { $this->guard(); check_admin_referer( 'aeo_connect_disconnect' ); AEO_Connect_API_Client::delete_site_key(); delete_option( 'aeo_connect_domain' ); delete_option( 'aeo_connect_revoked' ); update_option( 'aeo_connect_enabled', '0', false ); update_option( 'aeo_connect_modules', array( 'jsonld' => false, 'robots' => false, 'llms' => false, 'meta' => false, ), false ); $this->flash( 'success', __( 'Disconnected from everlange.com. All modules have been disabled. Cached data is kept until you uninstall the plugin.', 'aeo-tool-connect' ) ); $this->redirect_back(); } /** * Save the module toggles and the global kill-switch. * * @return void */ public function handle_save_modules() { $this->guard(); check_admin_referer( 'aeo_connect_save_modules' ); $enabled = isset( $_POST['aeo_connect_enabled'] ) ? sanitize_text_field( wp_unslash( $_POST['aeo_connect_enabled'] ) ) : ''; $modules = array(); foreach ( AEO_Connect_Cache::MODULE_SLUGS as $slug ) { $field = 'aeo_connect_module_' . $slug; $value = isset( $_POST[ $field ] ) ? sanitize_text_field( wp_unslash( $_POST[ $field ] ) ) : ''; $modules[ $slug ] = ( '1' === $value ); } update_option( 'aeo_connect_enabled', ( '1' === $enabled ) ? '1' : '0', false ); update_option( 'aeo_connect_modules', $modules, false ); $this->flash( 'success', __( 'Module settings saved.', 'aeo-tool-connect' ) ); $this->redirect_back(); } /** * Manual "Sync now". * * @return void */ public function handle_sync_now() { $this->guard(); check_admin_referer( 'aeo_connect_sync_now' ); $result = AEO_Connect_Cache::sync(); if ( is_wp_error( $result ) ) { $this->flash( 'error', sprintf( /* translators: %s: error message. */ __( 'Sync failed: %s The previously cached optimizations are still being served.', 'aeo-tool-connect' ), $result->get_error_message() ) ); } else { $this->flash( 'success', sprintf( /* translators: %d: applied bundle version. */ __( 'Sync complete. Applied bundle version: %d.', 'aeo-tool-connect' ), AEO_Connect_Cache::get_version() ) ); } $this->redirect_back(); } /** * Restore a rollback snapshot. * * @return void */ public function handle_restore() { $this->guard(); check_admin_referer( 'aeo_connect_restore' ); $index = isset( $_POST['aeo_connect_history_index'] ) ? absint( wp_unslash( $_POST['aeo_connect_history_index'] ) ) : 0; $result = AEO_Connect_Rollback::restore( $index ); if ( is_wp_error( $result ) ) { $this->flash( 'error', $result->get_error_message() ); } else { $this->flash( 'success', sprintf( /* translators: %d: restored bundle version. */ __( 'Snapshot restored. Bundle version %d is now active. Note: the next sync may re-apply the latest version published on everlange.com.', 'aeo-tool-connect' ), AEO_Connect_Cache::get_version() ) ); } $this->redirect_back(); } /** * Hide the "beta testers wanted" box for the current user, for good. * * Stored in user meta rather than an option: on a multi-author site, * one administrator dismissing it must not decide for the others. * * @return void */ public function handle_dismiss_beta() { $this->guard(); check_admin_referer( 'aeo_connect_dismiss_beta' ); update_user_meta( get_current_user_id(), 'aeo_connect_beta_dismissed', '1' ); $this->redirect_back(); } /* --------------------------------------------------------------------- * Helpers * ------------------------------------------------------------------ */ /** * Capability guard shared by all handlers. The nonce check is done * inline in each handler (check_admin_referer) so static analysis can * see it in the same scope as the $_POST reads. * * @return void */ private function guard() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'aeo-tool-connect' ) ); } } /** * Redirect back to the settings page and stop. * * @return void */ private function redirect_back() { wp_safe_redirect( admin_url( 'options-general.php?page=' . self::PAGE_SLUG ) ); exit; } /** * Store a one-shot notice for the current user. * * @param string $type 'success' or 'error'. * @param string $message Message text (already translated). * @return void */ private function flash( $type, $message ) { set_transient( 'aeo_connect_flash_' . get_current_user_id(), array( 'type' => ( 'error' === $type ) ? 'error' : 'success', 'message' => $message, ), MINUTE_IN_SECONDS ); } /** * Read and delete the one-shot notice for the current user. * * @return array|null { type: string, message: string } or null. */ private function consume_flash() { $key = 'aeo_connect_flash_' . get_current_user_id(); $flash = get_transient( $key ); if ( false !== $flash ) { delete_transient( $key ); } return ( is_array( $flash ) && isset( $flash['type'], $flash['message'] ) ) ? $flash : null; } }