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;
}
}