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'] ) ); } } ?>

✔

✔

__( 'Free', 'refreshrank' ), 't1' => __( 'Starter', 'refreshrank' ), 't2' => __( 'Growth', 'refreshrank' ), 't3' => __( 'Agency', 'refreshrank' ), ); return isset( $labels[ $tier ] ) ? $labels[ $tier ] : ( $tier ? $tier : $labels['free'] ); } /** Render an API ISO-8601 timestamp in the site's locale and timezone. */ private static function format_time( $iso ) { $ts = strtotime( (string) $iso ); if ( ! $ts ) { return (string) $iso; } return wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $ts ); } /** Render a WP_Error from the API client as an actionable admin notice. */ private static function notice( $error ) { $hints = array( 'bad_license' => __( 'Your license key was not accepted. Re-enter it in Step 1, or start a new free account.', 'refreshrank' ), 'network' => __( 'Could not reach the RefreshRank service. Check your connection or try again in a minute.', 'refreshrank' ), 'rate_limit' => __( 'Too many requests right now - wait a minute and try again.', 'refreshrank' ), ); $code = $error->get_error_code(); echo '

' . esc_html( $error->get_error_message() ) . ''; if ( isset( $hints[ $code ] ) ) { echo ' ' . esc_html( $hints[ $code ] ); } echo '

'; } public static function render_queue() { if ( ! current_user_can( self::CAP ) ) { return; } $key = get_option( 'refrank_license_key', '' ); echo '

' . esc_html__( 'Content Decay Queue', 'refreshrank' ) . '

'; $settings_url = admin_url( 'admin.php?page=refrank-settings' ); if ( ! $key ) { echo '
'; 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 '
'; return; } $queue = get_transient( 'refrank_decay_queue' ); if ( false === $queue ) { $queue = Refrank_API_Client::decay_queue(); if ( ! is_wp_error( $queue ) ) { set_transient( 'refrank_decay_queue', $queue, self::QUEUE_TTL ); } } if ( is_wp_error( $queue ) ) { self::notice( $queue ); echo ''; return; } $pages = isset( $queue['pages'] ) ? $queue['pages'] : array(); $paid = 'free' !== get_option( 'refrank_tier', 'free' ); if ( empty( $pages ) ) { // Distinguish "not connected yet" from "connected, no data yet". $gsc = Refrank_API_Client::gsc_status(); $connected = ( is_array( $gsc ) && ! empty( $gsc['connected'] ) ); echo '
'; if ( is_array( $gsc ) && ! empty( $gsc['needs_property'] ) ) { 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 '
'; return; } echo ''; foreach ( array( 'URL', 'Decay', 'Potential', 'Suggested', '' ) as $h ) { echo ''; } echo ''; foreach ( $pages as $p ) { $url = isset( $p['url'] ) ? $p['url'] : ''; $post_id = url_to_postid( $url ); $actions = isset( $p['suggested_actions'] ) ? (array) $p['suggested_actions'] : array(); $queries = isset( $p['top_queries'] ) ? (array) $p['top_queries'] : array(); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } echo '
' . esc_html( $h ) . '
' . esc_html( $p['post_hint'] ) . '' . esc_html( number_format_i18n( (float) $p['decay_score'], 1 ) ) . '' . esc_html( number_format_i18n( (float) $p['potential'], 1 ) ) . '' . esc_html( implode( ', ', array_map( 'sanitize_text_field', $actions ) ) ) . ''; if ( ! $paid ) { echo '' . esc_html__( 'Upgrade', 'refreshrank' ) . ''; } elseif ( ! $post_id ) { echo '' . esc_html__( 'No local post', 'refreshrank' ) . ''; } else { echo ''; } echo '
'; echo '
'; } }