admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( self::NONCE ), 'tier' => (string) get_option( 'refrank_tier', 'free' ), 'paid' => 'free' !== get_option( 'refrank_tier', 'free' ), 'upgrade' => esc_url( REFRANK_UPGRADE_URL ), 'strings' => array( 'requesting' => __( 'Requesting rewrite...', 'refreshrank' ), /* translators: 1: position in the rewrite queue, 2: estimated wait in seconds */ 'queued' => __( 'Queued (position %1$d, ~%2$ds)...', 'refreshrank' ), 'failed' => __( 'Rewrite failed. Please try again.', 'refreshrank' ), 'applied' => __( 'Applied. A revision was saved.', 'refreshrank' ), 'apply' => __( 'Approve & apply', 'refreshrank' ), 'cancel' => __( 'Cancel', 'refreshrank' ), 'viewPlans' => __( 'View plans', 'refreshrank' ), ), ) ); } // --- guards ---------------------------------------------------------- /** * Capability check only - every AJAX handler calls check_ajax_referer() * inline first, so the nonce check is visible in the same scope as the * $_POST reads (and to static analysis). */ private static function guard() { if ( ! current_user_can( self::CAP ) ) { wp_send_json_error( array( 'message' => __( 'Not allowed.', 'refreshrank' ) ), 403 ); } } private static function fail( $wp_error ) { wp_send_json_error( array( 'code' => $wp_error->get_error_code(), 'message' => $wp_error->get_error_message(), ) ); } // --- AJAX ------------------------------------------------------------ public static function ajax_save_license() { check_ajax_referer( self::NONCE, 'nonce' ); self::guard(); $key = isset( $_POST['license_key'] ) ? sanitize_text_field( wp_unslash( $_POST['license_key'] ) ) : ''; $data = Refrank_API_Client::activate( $key, home_url() ); if ( is_wp_error( $data ) ) { self::fail( $data ); } update_option( 'refrank_license_key', $key ); update_option( 'refrank_site_id', sanitize_text_field( $data['site_id'] ) ); update_option( 'refrank_site_secret', sanitize_text_field( $data['site_secret'] ) ); update_option( 'refrank_tier', sanitize_text_field( $data['tier'] ) ); delete_transient( 'refrank_decay_queue' ); wp_send_json_success( array( 'tier' => $data['tier'] ) ); } public static function ajax_register_free() { check_ajax_referer( self::NONCE, 'nonce' ); self::guard(); $data = Refrank_API_Client::register_free( home_url() ); if ( is_wp_error( $data ) ) { self::fail( $data ); } update_option( 'refrank_license_key', sanitize_text_field( $data['license_key'] ) ); update_option( 'refrank_site_id', sanitize_text_field( $data['site_id'] ) ); update_option( 'refrank_site_secret', sanitize_text_field( $data['site_secret'] ) ); update_option( 'refrank_tier', sanitize_text_field( $data['tier'] ) ); delete_transient( 'refrank_decay_queue' ); wp_send_json_success( array( 'tier' => $data['tier'] ) ); } public static function ajax_redeem_code() { check_ajax_referer( self::NONCE, 'nonce' ); self::guard(); $code = isset( $_POST['code'] ) ? sanitize_text_field( wp_unslash( $_POST['code'] ) ) : ''; if ( '' === $code ) { wp_send_json_error( array( 'message' => __( 'Enter your code first.', 'refreshrank' ) ) ); } $had_key = '' !== get_option( 'refrank_license_key', '' ); $data = Refrank_API_Client::redeem_code( $code ); if ( is_wp_error( $data ) ) { self::fail( $data ); } update_option( 'refrank_license_key', sanitize_text_field( $data['license_key'] ) ); update_option( 'refrank_tier', sanitize_text_field( $data['tier'] ) ); // Fresh account from the code (no prior key): bind this site to it. if ( ! $had_key ) { $act = Refrank_API_Client::activate( $data['license_key'], home_url() ); if ( is_wp_error( $act ) ) { self::fail( $act ); } update_option( 'refrank_site_id', sanitize_text_field( $act['site_id'] ) ); update_option( 'refrank_site_secret', sanitize_text_field( $act['site_secret'] ) ); } delete_transient( 'refrank_decay_queue' ); wp_send_json_success( array( 'tier' => $data['tier'] ) ); } public static function ajax_gsc_connect() { check_ajax_referer( self::NONCE, 'nonce' ); self::guard(); $url = Refrank_API_Client::gsc_connect_url(); if ( is_wp_error( $url ) ) { self::fail( $url ); } wp_send_json_success( array( 'auth_url' => esc_url_raw( $url ) ) ); } public static function ajax_sync() { check_ajax_referer( self::NONCE, 'nonce' ); self::guard(); $res = Refrank_API_Client::sync(); if ( is_wp_error( $res ) ) { self::fail( $res ); } delete_transient( 'refrank_decay_queue' ); wp_send_json_success( $res ); } public static function ajax_client_report() { check_ajax_referer( self::NONCE, 'nonce' ); self::guard(); $brand = array( 'brand_name' => isset( $_POST['brand_name'] ) ? sanitize_text_field( wp_unslash( $_POST['brand_name'] ) ) : '', 'brand_logo_url' => isset( $_POST['brand_logo_url'] ) ? esc_url_raw( wp_unslash( $_POST['brand_logo_url'] ) ) : '', 'brand_accent' => isset( $_POST['brand_accent'] ) ? sanitize_hex_color( wp_unslash( $_POST['brand_accent'] ) ) : '', ); update_option( 'refrank_report_brand_name', $brand['brand_name'] ); update_option( 'refrank_report_brand_logo', $brand['brand_logo_url'] ); update_option( 'refrank_report_brand_accent', (string) $brand['brand_accent'] ); $data = Refrank_API_Client::create_report( $brand ); if ( is_wp_error( $data ) ) { self::fail( $data ); } wp_send_json_success( array( 'report_url' => esc_url_raw( $data['report_url'] ) ) ); } // --- pages ----------------------------------------------------------- public static function render_settings() { if ( ! current_user_can( self::CAP ) ) { return; } $key = get_option( 'refrank_license_key', '' ); $tier = get_option( 'refrank_tier', '' ); $status = $key ? Refrank_API_Client::license_status() : null; $gsc = $key ? Refrank_API_Client::gsc_status() : null; $connected = ( is_array( $gsc ) && ! empty( $gsc['connected'] ) ); $synced = ( is_array( $gsc ) && ! empty( $gsc['last_sync'] ) ); ?>
✔ ' . esc_html( self::tier_label( $tier ) ) . '' ); if ( is_array( $status ) && isset( $status['limits'] ) ) { $lim = $status['limits']; if ( ! empty( $lim['rewrites_enabled'] ) ) { echo ' · ' . esc_html( sprintf( /* translators: 1: rewrites used, 2: monthly quota */ __( 'Rewrites this month: %1$d / %2$d', 'refreshrank' ), (int) $status['rewrites_used_month'], (int) $lim['rewrites_per_month'] ) ); } } ?>
✔
' . esc_html( $error->get_error_message() ) . ''; if ( isset( $hints[ $code ] ) ) { echo ' ' . esc_html( $hints[ $code ] ); } echo '
' . esc_html__( 'RefreshRank finds the posts that are losing search traffic and helps you refresh them - you review every change before it goes live.', 'refreshrank' ) . '
'; echo '' . esc_html__( 'Get started - it\'s free', 'refreshrank' ) . '
'; echo '' . esc_html__( 'You authorized Google, but this site was not found in your Search Console. Add and verify it there, then reconnect from Settings.', 'refreshrank' ) . '
'; echo '' . esc_html__( 'Go to Settings', 'refreshrank' ) . '
'; } elseif ( ! $connected ) { echo '' . esc_html__( 'Almost there - connect Google Search Console so RefreshRank can see which posts are losing traffic.', 'refreshrank' ) . '
'; // Launches OAuth directly (no extra hop through Settings). echo ''; } elseif ( is_array( $gsc ) && empty( $gsc['last_sync'] ) ) { echo '' . esc_html__( 'Connected! Run your first sync to pull in 16 months of Search Console history.', 'refreshrank' ) . '
'; echo '' . esc_html__( 'Sync now', 'refreshrank' ) . '
'; } else { echo '' . esc_html__( 'No decaying content found - your posts are holding their rankings. RefreshRank re-checks after every daily sync.', 'refreshrank' ) . '
'; } echo '| ' . esc_html( $h ) . ' | '; } echo '||||
|---|---|---|---|---|
| ' . esc_html( $p['post_hint'] ) . ' | '; echo '' . esc_html( number_format_i18n( (float) $p['decay_score'], 1 ) ) . ' | '; echo '' . esc_html( number_format_i18n( (float) $p['potential'], 1 ) ) . ' | '; echo '' . esc_html( implode( ', ', array_map( 'sanitize_text_field', $actions ) ) ) . ' | '; echo ''; if ( ! $paid ) { echo '' . esc_html__( 'Upgrade', 'refreshrank' ) . ''; } elseif ( ! $post_id ) { echo '' . esc_html__( 'No local post', 'refreshrank' ) . ''; } else { echo ''; } echo ' |