0 ) return $sum_bal; // Legacy field names $free = (int)($st['free'] ?? 0); $monthly = (int)($st['monthly_remaining'] ?? 0); $pack = (int)($st['token_pack'] ?? 0); $sum_legacy = $free + $monthly + $pack; if ( $sum_legacy > 0 ) return $sum_legacy; // Generic "remaining" if ( isset($st['remaining']) ) { return max(0, (int)$st['remaining']); } // Fallback: limit - usage $limit = (int)($st['plan_limit_total'] ?? $st['plan_limit'] ?? $st['token_limit'] ?? 0); $usage = null; foreach ( ['usage_total','all_usage','total_usage','token_usage','usage'] as $k ) { if ( isset($st[$k]) ) { $usage = (int)$st[$k]; break; } } if ( $usage === null ) $usage = (int)($st['site_usage'] ?? 0); return max(0, $limit - (int)$usage); } } /* ========================================================= * 1) Snippet Score Calculation * Simple 3-rule check: keyword found in normalized Title, Description, and Slug. * ======================================================= */ if ( ! function_exists( 'aish_calc_score' ) ) { function aish_calc_score( $fw, $ti, $de, $slug ) { $norm_text = function( $s ){ $s = strtolower( (string) $s ); if ( function_exists('remove_accents') ) $s = remove_accents($s); return trim( preg_replace('/[^a-z0-9]+/',' ', $s ) ); }; $norm_slug = function( $s ){ $s = strtolower( (string) $s ); if ( function_exists('remove_accents') ) $s = remove_accents($s); return trim( preg_replace('/[^a-z0-9]+/','-', $s ), '-' ); }; $arr = array_map( 'trim', explode( ',', (string) $fw ) ); $kw = strtolower( $arr[0] ?? '' ); if ( $kw === '' ) { return [ 'score' => 0, 'color' => '#e74c3c' ]; } $has_title = strpos( ' ' . $norm_text($ti) . ' ', ' ' . $norm_text($kw) . ' ' ) !== false; $has_desc = strpos( ' ' . $norm_text($de) . ' ', ' ' . $norm_text($kw) . ' ' ) !== false; $has_slug = strpos( $norm_slug($slug), $norm_slug($kw) ) !== false; $hit = (int)$has_title + (int)$has_desc + (int)$has_slug; $score = ($hit === 0) ? 0 : (($hit === 1) ? 33 : (($hit === 2) ? 66 : 100)); $color = ($score >= 100) ? '#2980b9' : (($score >= 66) ? '#f39c12' : '#e74c3c'); return [ 'score' => $score, 'color' => $color ]; } } /** Minimal post payload for bulk operations */ function aish_prepare_snippet_item_for_post( int $post_id, array $opts = [] ) : array { $title = get_the_title( $post_id ); $excerpt = get_the_excerpt( $post_id ); $ptype = get_post_type( $post_id ); $fw = get_post_meta( $post_id, 'rank_math_focus_keyword', true ); return [ 'id' => (int) $post_id, 'context_type' => 'post', 'post_title' => $title, 'post_excerpt' => $excerpt, 'post_type' => $ptype, 'focus_keyword' => $fw, 'slug' => get_post_field( 'post_name', $post_id ), ]; } /* ========================================================= * 2) RankPilot AI — /snippet * ======================================================= */ if ( ! function_exists( 'aish_call_rankpilot_api' ) ) { function aish_call_rankpilot_api( string $site_token, array $payload ) : array { if ( empty( $site_token ) ) { throw new Exception( 'Site token missing.' ); } $payload = array_merge( $payload, [ 'site_token' => $site_token, 'plugin' => 'ai-snippet-seo-helper', 'site_url' => home_url(), ] ); // Allow plugins/themes to adjust payload $payload = apply_filters( 'aish/snippet_payload', $payload, $site_token ); $r = aish_http_json( AISH_RP_API . '/snippet', $payload, 'POST', ['timeout'=>30] ); if ( is_wp_error( $r ) ) { aish_log('API request error', ['err'=>$r->get_error_message()]); throw new Exception( 'API request error: ' . $r->get_error_message() ); } $code = wp_remote_retrieve_response_code( $r ); $body = wp_remote_retrieve_body( $r ); if ( (int)$code !== 200 ) { aish_log('RankPilotAI API HTTP error', ['code'=>$code,'body'=>substr((string)$body,0,300)]); throw new Exception( 'RankPilotAI API Error (HTTP ' . $code . '): ' . $body ); } $j = json_decode( (string)$body, true ); if ( ! is_array( $j ) ) { aish_log('Invalid JSON response', ['body'=>substr((string)$body,0,300)]); throw new Exception( 'Invalid JSON response.' ); } $j = aish_sanitize_snippet_response( $j ); // Usage changed → bust token-info cache immediately aish_flush_token_cache( $site_token ); // Allow plugins/themes to inspect/adjust response $j = apply_filters( 'aish/snippet_response', $j, $site_token ); return $j; } } /** * Try /snippet-bulk; if it fails, fall back to single /snippet calls. */ function aish_call_rankpilot_api_bulk( string $site_token, array $items, string $model_sel, array $opts = [] ) : array { if ( empty( $site_token ) ) { throw new Exception('Site token missing.'); } if ( empty( $items ) ) { return [ 'items' => [] ]; } $endpoint = AISH_RP_API . '/snippet-bulk'; $payload = [ 'site_token' => $site_token, 'plugin' => 'ai-snippet-seo-helper', 'site_url' => home_url(), 'domain' => parse_url( home_url(), PHP_URL_HOST ), 'model' => strtolower( $model_sel ?: 'gpt-4-turbo' ), 'custom_prompt' => $opts['custom_prompt'] ?? '', 'auto_slug' => ( $opts['auto_slug'] ?? 'no' ) === 'yes', 'items' => array_values( $items ), ]; $res = aish_http_json( $endpoint, $payload, 'POST', [ 'timeout' => 120 ] ); if ( ! is_wp_error( $res ) ) { $code = (int) wp_remote_retrieve_response_code( $res ); $body = wp_remote_retrieve_body( $res ); if ( $code === 200 ) { $json = json_decode( (string)$body, true ); if ( is_array($json) ) { if ( empty($json['items']) ) $json['items'] = $json; return $json; } } } // Fallback: single /snippet loop $out = []; foreach ( $items as $it ) { $single = array_merge( $it, [ 'model' => strtolower( $model_sel ?: 'gpt-4-turbo' ), 'auto_slug' => ( $opts['auto_slug'] ?? 'no' ) === 'yes', 'custom_prompt' => $opts['custom_prompt'] ?? '', ] ); try { $resp = aish_call_rankpilot_api( $site_token, $single ); $resp['id'] = (int) ($it['id'] ?? 0); $out[ (int)$it['id'] ] = $resp; } catch ( \Exception $ex ) { $out[ (int)$it['id'] ] = [ 'id'=>(int)$it['id'], 'error'=>$ex->getMessage() ]; } usleep( 50000 ); // 50ms backoff } return [ 'items' => $out, 'fallback' => 'single' ]; } /** Apply a generated snippet to Rank Math fields for a given post */ function aish_apply_snippet_result_for_post( int $post_id, array $data, array $opts = [] ) : bool { if ( ! empty($data['error']) ) return false; $kw = $data['keyword'] ?? ''; $ti = $data['title'] ?? ''; $desc = $data['description'] ?? ''; $slug = $data['slug'] ?? ''; // Rank Math fields if ( $kw ) update_post_meta( $post_id, 'rank_math_focus_keyword', $kw ); if ( $ti ) update_post_meta( $post_id, 'rank_math_title', $ti ); if ( $desc ) update_post_meta( $post_id, 'rank_math_description', $desc ); // Slug (optional) if ( ( $opts['auto_slug'] ?? 'no' ) === 'yes' && $slug ) { $san = sanitize_title( $slug ); $cur = get_post_field( 'post_name', $post_id ); if ( $san && $san !== $cur ) { add_post_meta( $post_id, '_wp_old_slug', $cur ); wp_update_post( [ 'ID' => $post_id, 'post_name' => $san ] ); } } $final_slug = get_post_field( 'post_name', $post_id ); $sc = aish_calc_score( $kw, $ti, $desc, $final_slug ); update_post_meta( $post_id, '_aish_snippet_score', $sc['score'] ); update_post_meta( $post_id, '_aish_score', $sc['score'] ); // legacy return true; } /* ========================================================= * 3) /token-info * ======================================================= */ if ( ! function_exists( 'aish_check_token_status' ) ) { function aish_check_token_status( string $site_token, bool $force_refresh = false ) : array { $cache_key = aish_token_cache_key( $site_token ); // Soft cache with optional bypass if ( $force_refresh ) { delete_transient( $cache_key ); } else { $cached = get_transient( $cache_key ); if ( is_array( $cached ) ) { return $cached; } } if ( empty($site_token) ) { return [ 'error' => 'No token provided' ]; } $body = [ 'site_token' => $site_token, 'plugin' => 'ai-snippet-seo-helper', 'site_url' => home_url(), 'cache_bust' => time(), 'include' => 'balances,usage,limits', ]; $r = aish_http_json( AISH_RP_API . '/token-info', $body, 'POST', [ 'timeout' => 20, 'headers' => [ 'Cache-Control' => 'no-cache' ] ] ); if ( is_wp_error( $r ) ) { aish_log('Token-info connection error', ['err'=>$r->get_error_message()]); return [ 'error' => 'Connection Error' ]; } $resp = wp_remote_retrieve_body( $r ); $data = json_decode( (string)$resp, true ); if ( ! is_array($data) ) { aish_log('Invalid token-info response', ['body'=>substr((string)$resp,0,300)]); return [ 'error' => 'Invalid token-info response' ]; } // Ensure at least one allowed model is present if ( empty($data['allowed_models']) ) { $data['allowed_models'] = [ 'gpt-4-turbo' ]; } set_transient( $cache_key, $data, 30 ); return $data; } } /* ========================================================= * 4) /token-reduce * ======================================================= */ if ( ! function_exists( 'aish_reduce_rankpilot_token' ) ) { function aish_reduce_rankpilot_token( string $site_token, string $model = 'gpt-4-turbo' ) : array { $body = [ 'site_token' => $site_token, 'plugin' => 'ai-snippet-seo-helper', 'model' => strtolower( $model ), 'site_url' => home_url(), ]; $r = aish_http_json( AISH_RP_API . '/token-reduce', $body, 'POST', ['timeout'=>20] ); if ( is_wp_error( $r ) ) { aish_log('Token-reduce connection error', ['err'=>$r->get_error_message()]); return [ 'error' => 'Connection Error' ]; } $data = json_decode( (string) wp_remote_retrieve_body( $r ), true ); if ( is_array( $data ) && empty( $data['error'] ) ) { aish_flush_token_cache( $site_token ); } return is_array( $data ) ? $data : []; } }