true ], 'names' ); foreach ( $pts as $pt ) { add_meta_box( 'aissp_meta_box', 'AI Snippet SEO Pro', [ __CLASS__, 'render_meta_box' ], $pt, 'normal', 'high' ); add_meta_box( 'aissp_meta_box_side', 'AI Snippet SEO Pro', [ __CLASS__, 'render_side_meta_box' ], $pt, 'side', 'high' ); } } /* ========================================================= * 2) SIDE PANEL (compact overview) * ======================================================= */ public static function render_side_meta_box( $post ) { $scVal = get_post_meta( $post->ID, self::$mk_score, true ); if ( $scVal === '' ) { $scVal = 0; } $ti = get_post_meta( $post->ID, self::$mk_title, true ); $de = get_post_meta( $post->ID, self::$mk_desc, true ); $col = '#dc3232'; if ( $scVal >= 100 ) { $scVal = 100; $col = '#0091e0'; } elseif ( $scVal >= 67 ) { $col = '#ffab00'; } echo '
SEO Snippet Score: ' . esc_html( $scVal ) . ' / 100
'; echo '
Title: ' . esc_html( $ti ? $ti : '(No Title)' ) . '
'; echo '
Description: ' . esc_html( $de ? $de : '(No Meta Description)' ) . '
'; } /* ========================================================= * 3) MAIN PANEL (full editor) * ======================================================= */ public static function render_meta_box( $post ) { $fw = get_post_meta( $post->ID, self::$mk_focus_kw, true ); $ti = get_post_meta( $post->ID, self::$mk_title, true ); $de = get_post_meta( $post->ID, self::$mk_desc, true ); wp_nonce_field( 'aissp_save_meta_box', 'aissp_meta_nonce' ); $sc = aissp_calc_score( $fw, $ti, $de, get_post_field( 'post_name', $post->ID ) ); $score_txt = aissp_score_text( $sc['score'] ); $col = $sc['color']; $slugVal = get_post_field( 'post_name', $post->ID ); /* ---------- Preview ---------- */ echo '

Preview

' . ( $ti ? esc_html( $ti ) : '(No Title)' ) . '

' . esc_html( home_url( '/' ) ) . '' . esc_html( $slugVal ) . '

' . ( $de ? esc_html( $de ) : '(No Meta Description)' ) . '

'; echo '

'; /* ---------- Focus Keyword ---------- */ echo '
Focus Keyword (comma separated):

Enter one or more keywords separated by commas.

'; /* ---------- Title ---------- */ echo '
SEO Title:

Recommended 30–60 chars. EXACT KW must appear as is (for best score).

'; /* ---------- Description ---------- */ echo '
Meta Description:

Recommended 120–160 chars. EXACT KW must appear.

'; /* ---------- Score ---------- */ echo '
SEO Snippet Score: ' . esc_html( $score_txt ) . '
'; /* ---------- Checklist ---------- */ echo '
Snippet Checklist'; $arr = array_map( 'trim', explode( ',', $fw ) ); $fk = strtolower( $arr[0] ?? '' ); $tLen = strlen( $ti ); $descLen = mb_strlen( $de, 'UTF-8' ); $slugCheck = str_replace( ' ', '-', strtolower( $slugVal ) ); $fkCheck = str_replace( ' ', '-', $fk ); $c1 = ( ! empty( $fk ) && strpos( strtolower( $ti ), $fk ) !== false ); $c2 = ( ! empty( $fk ) && strpos( strtolower( $de ), $fk ) !== false ); $c3 = ( ! empty( $fk ) && strpos( $slugCheck, $fkCheck ) !== false ); $c4 = ( $tLen >= 30 && $tLen <= 60 ); $c5 = ( strlen( $slugVal ) > 0 && strlen( $slugVal ) <= 75 ); $c6 = ( $descLen >= 120 && $descLen <= 160 ); echo self::check_item( $c1, 'Focus Keyword in Title' ); echo self::check_item( $c2, 'Focus Keyword in Description' ); echo self::check_item( $c3, 'Focus Keyword in URL Slug' ); echo self::check_item( $c4, 'Title 30–60 Characters' ); echo self::check_item( $c5, 'Slug ≤ 75 Characters' ); echo self::check_item( $c6, 'Description 120–160 Characters' ); echo '
'; } /* ─────────────────────────────────────────────── * Single checklist row renderer * ───────────────────────────────────────────── */ private static function check_item( $ok, $txt ) { $okI = '✔'; $noI = '✘'; return '

' . ( $ok ? $okI : $noI ) . ' ' . esc_html( $txt ) . '

'; } /* ========================================================= * 4) SAVE (Post) * ======================================================= */ public static function save_meta_box_data( $post_id ) { if ( ! isset( $_POST['aissp_meta_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['aissp_meta_nonce'] ) ), 'aissp_save_meta_box' ) ) { return; } if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { return; } if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) { return; } if ( ! current_user_can( 'edit_post', $post_id ) ) { return; } $fwRaw = isset( $_POST['aissp_focus_keyword'] ) ? sanitize_text_field( wp_unslash( $_POST['aissp_focus_keyword'] ) ) : ''; $arr = array_map( 'trim', explode( ',', $fwRaw ) ); $fw = implode( ', ', $arr ); update_post_meta( $post_id, self::$mk_focus_kw, $fw ); $ti = isset( $_POST['aissp_seo_title'] ) ? sanitize_text_field( wp_unslash( $_POST['aissp_seo_title'] ) ) : ''; update_post_meta( $post_id, self::$mk_title, $ti ); $de = isset( $_POST['aissp_seo_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['aissp_seo_description'] ) ) : ''; update_post_meta( $post_id, self::$mk_desc, $de ); /* Slug: first focus keyword (if auto-slug is ON) */ $opts = get_option( 'aissp_settings', [] ); if ( ( $opts['auto_slug'] ?? 'no' ) !== 'yes' ) { // Auto-Slug disabled → skip slug change block } else { if ( ! empty( $arr[0] ) ) { $mk = sanitize_title( $arr[0] ); $curSlug = get_post_field( 'post_name', $post_id ); if ( $mk && $mk !== $curSlug ) { wp_update_post( [ 'ID' => $post_id, 'post_name' => $mk ] ); } } } // ← closes the "else" block $slug = get_post_field( 'post_name', $post_id ); $sc = aissp_calc_score( $fw, $ti, $de, $slug ); update_post_meta( $post_id, self::$mk_score, $sc['score'] ); } /* ========================================================= * 5) TERM UI — Edit screen * ======================================================= */ public static function render_term_edit_ui( $term, $taxonomy ) { echo '

AI Snippet SEO Pro

'; wp_nonce_field( 'aissp_save_term_meta', 'aissp_term_nonce' ); $fw = get_term_meta( $term->term_id, self::$mk_focus_kw, true ); $ti = get_term_meta( $term->term_id, self::$mk_title, true ); $de = get_term_meta( $term->term_id, self::$mk_desc, true ); $sc = aissp_calc_score( $fw, $ti, $de, $term->slug ); $score_txt = aissp_score_text( $sc['score'] ); $col = $sc['color']; /* Preview */ echo '

' . ( $ti ? esc_html( $ti ) : '(No Title)' ) . '

' . esc_html( home_url( '/' ) ) . '' . esc_html( $term->slug ) . '

' . ( $de ? esc_html( $de ) : '(No Meta Description)' ) . '

'; /* Focus KW */ echo ' '; /* Title */ echo ' '; /* Description */ echo ' '; /* Score */ echo ' ' . esc_html( $score_txt ) . ' '; /* Checklist */ $arr = array_map( 'trim', explode( ',', $fw ) ); $fk = strtolower( $arr[0] ?? '' ); $tLen = strlen( $ti ); $descLen = mb_strlen( $de, 'UTF-8' ); $slugCheck = str_replace( ' ', '-', strtolower( $term->slug ) ); $fkCheck = str_replace( ' ', '-', $fk ); $c1 = ( ! empty( $fk ) && strpos( strtolower( $ti ), $fk ) !== false ); $c2 = ( ! empty( $fk ) && strpos( strtolower( $de ), $fk ) !== false ); $c3 = ( ! empty( $fk ) && strpos( $slugCheck, $fkCheck ) !== false ); $c4 = ( $tLen >= 30 && $tLen <= 60 ); $c5 = ( strlen( $term->slug ) > 0 && strlen( $term->slug ) <= 75 ); $c6 = ( $descLen >= 120 && $descLen <= 160 ); echo ' Snippet Checklist'; echo self::check_item( $c1, 'Focus Keyword in Title' ); echo self::check_item( $c2, 'Focus Keyword in Description' ); echo self::check_item( $c3, 'Focus Keyword in URL Slug' ); echo self::check_item( $c4, 'Title 30–60 Characters' ); echo self::check_item( $c5, 'Slug ≤ 75 Characters' ); echo self::check_item( $c6, 'Description 120–160 Characters' ); echo ''; } /* --------------------------------------------------------- * TERM SAVE * ------------------------------------------------------- */ public static function save_term_edit_ui( $term_id, $tax ) { // 1) Verify nonce for security if ( ! isset( $_POST['aissp_term_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['aissp_term_nonce'] ) ), 'aissp_save_term_meta' ) ) { return; } // 2) Check user capability if ( ! current_user_can( 'edit_term', $term_id ) ) { return; } // 3) Safely read POST values $raw = isset( $_POST['aissp_focus_keyword'] ) ? sanitize_text_field( wp_unslash( $_POST['aissp_focus_keyword'] ) ) : ''; if ( $raw === '' ) { delete_term_meta( $term_id, self::$mk_focus_kw ); delete_term_meta( $term_id, self::$mk_title ); delete_term_meta( $term_id, self::$mk_desc ); delete_term_meta( $term_id, self::$mk_score ); return; } $arr = array_map( 'trim', explode( ',', $raw ) ); $fw = implode( ', ', $arr ); update_term_meta( $term_id, self::$mk_focus_kw, $fw ); $ti = isset( $_POST['aissp_seo_title'] ) ? sanitize_text_field( wp_unslash( $_POST['aissp_seo_title'] ) ) : ''; update_term_meta( $term_id, self::$mk_title, $ti ); $de = isset( $_POST['aissp_seo_description'] ) ? sanitize_textarea_field( wp_unslash( $_POST['aissp_seo_description'] ) ) : ''; update_term_meta( $term_id, self::$mk_desc, $de ); // Update slug only when auto_slug is enabled $opts = get_option( 'aissp_settings', [] ); if ( ( $opts['auto_slug'] ?? 'no' ) === 'yes' && ! empty( $arr[0] ) ) { $mk = sanitize_title( $arr[0] ); $term_obj = get_term( $term_id, $tax ); if ( $term_obj && $term_obj->slug !== $mk ) { wp_update_term( $term_id, $tax, [ 'slug' => $mk ] ); } } $term_obj = get_term( $term_id, $tax ); if ( $term_obj && ! is_wp_error( $term_obj ) ) { $sc = aissp_calc_score( $fw, $ti, $de, $term_obj->slug ); update_term_meta( $term_id, self::$mk_score, $sc['score'] ); } } /* ========================================================= * 6) TERM LIST COLUMN & SORTING * ======================================================= */ public static function term_snippet_cols( $cols ) { $new = []; foreach ( $cols as $k => $v ) { $new[ $k ] = $v; if ( $k === 'slug' ) { $new['seo_snippet_col'] = 'SEO Snippet'; } } return $new; } public static function term_snippet_cols_render( $out, $col, $term_id ) { if ( $col === 'seo_snippet_col' ) { $val = get_term_meta( $term_id, self::$mk_score, true ); if ( $val === '' ) { $val = 0; } $color = '#dc3232'; if ( $val >= 100 ) { $val = 100; $color = '#0091e0'; } elseif ( $val >= 67 ) { $color = '#ffab00'; } return '' . esc_html( $val ) . ' / 100'; } return $out; } public static function term_snippet_cols_sortable( $cols ) { $cols['seo_snippet_col'] = 'seo_snippet_col'; return $cols; } public static function term_snippet_cols_sort_clauses( $clauses, $tax, $args ) { if ( isset( $_GET['orderby'] ) && $_GET['orderby'] === 'seo_snippet_col' ) { global $wpdb; $ord = ( isset( $_GET['order'] ) && strtoupper( $_GET['order'] ) === 'DESC' ) ? 'DESC' : 'ASC'; $clauses['fields'] .= ", (CASE WHEN tm_score.meta_value<>'' THEN CAST(tm_score.meta_value AS UNSIGNED) ELSE 0 END) as snippet_score_val"; $clauses['join'] .= " LEFT JOIN {$wpdb->termmeta} tm_score ON (t.term_id=tm_score.term_id AND tm_score.meta_key='" . self::$mk_score . "')"; $clauses['orderby'] = " snippet_score_val $ord"; $clauses['groupby'] = " t.term_id"; } return $clauses; } /* ========================================================= * 7) GENERATE AI SNIPPET — POST * ======================================================= */ public static function generate_ai_snippet_for_post( $post_id, $return_data = false ) { $opts = get_option( 'aissp_settings' ); $site_token = isset( $opts['site_token'] ) ? $opts['site_token'] : ''; $model_sel = isset( $opts['model_choice'] ) ? strtolower( $opts['model_choice'] ) : 'gpt-4-turbo'; if ( ! $site_token ) { throw new \Exception( 'No site token' ); } /* ---- usage/balance check ---- */ $st = aissp_check_token_status( $site_token ); if ( isset( $st['error'] ) ) { throw new \Exception( 'Token check error: ' . $st['error'] ); } $__remaining_global = function_exists('aissp_status_remaining') ? aissp_status_remaining( $st ) : (int) ( $st['remaining'] ?? 0 ); if ( $__remaining_global <= 0 ) { throw new \Exception( 'No tokens left. Please upgrade.' ); } /* === TOKEN GUARD (is there enough balance for the model cost?) === */ $__need = function_exists('aissp_model_cost') ? aissp_model_cost( $model_sel ) : ( $model_sel === 'gpt-4o' ? 5 : ( $model_sel === 'gpt-4.1' ? 3 : ( $model_sel === 'o3' ? 12 : 1 ) ) ); $__remaining = function_exists('aissp_status_remaining') ? aissp_status_remaining( $st ) : ( isset($st['remaining']) ? (int)$st['remaining'] : 0 ); if ( $__remaining < $__need ) { throw new \Exception( 'Not enough tokens for the selected model (' . $model_sel . '). ' . 'Needs ' . $__need . ', you have ' . $__remaining . '. ' . 'Choose a cheaper model (e.g. GPT-4 Turbo) or buy more tokens.' ); } /* ---- Payload ---- */ $title = get_the_title( $post_id ); $excerpt = get_the_excerpt( $post_id ); $ptype = get_post_type( $post_id ); $fw = get_post_meta( $post_id, self::$mk_focus_kw, true ); $payload = [ 'context_type' => 'post', 'post_title' => $title, 'post_excerpt' => $excerpt, 'post_type' => $ptype, 'focus_keyword' => $fw, 'model' => $model_sel, ]; $payload['slug'] = get_post_field( 'post_name', $post_id ); $payload['auto_slug'] = ( $opts['auto_slug'] ?? 'no' ) === 'yes'; /* ---- API ---- */ $res = aissp_call_rankpilot_api( $site_token, $payload ); /* ---- Persist results ---- */ $newKW = $res['keyword'] ?? ''; $newTit = $res['title'] ?? ''; $newDes = $res['description'] ?? ''; $newSlug = $res['slug'] ?? ''; update_post_meta( $post_id, self::$mk_focus_kw, $newKW ); update_post_meta( $post_id, self::$mk_title, $newTit ); update_post_meta( $post_id, self::$mk_desc, $newDes ); /* ---------------- Slug update ---------------- */ $auto = $opts['auto_slug'] ?? 'no'; if ( $auto === 'yes' && $newSlug ) { $san = sanitize_title( $newSlug ); $current_slug = get_post_field( 'post_name', $post_id ); if ( $san && $san !== $current_slug ) { // WordPress' built-in old-slug → 301 redirect mechanism add_post_meta( $post_id, '_wp_old_slug', $current_slug ); wp_update_post( [ 'ID' => $post_id, 'post_name' => $san ] ); } } $final_slug = get_post_field( 'post_name', $post_id ); $sc = aissp_calc_score( $newKW, $newTit, $newDes, $final_slug ); update_post_meta( $post_id, self::$mk_score, $sc['score'] ); if ( $return_data ) { return [ 'kw' => $newKW, 'ti' => $newTit, 'de' => $newDes, 'slug' => $final_slug, ]; } } /* ========================================================= * 8) GENERATE AI SNIPPET — TERM * ======================================================= */ public static function generate_ai_snippet_for_term( $term_id ) { $opts = get_option( 'aissp_settings' ); $site_token = isset( $opts['site_token'] ) ? $opts['site_token'] : ''; $model_sel = isset( $opts['model_choice'] ) ? strtolower( $opts['model_choice'] ) : 'gpt-4-turbo'; if ( ! $site_token ) { throw new \Exception( 'No site token' ); } $st = aissp_check_token_status( $site_token ); if ( isset( $st['error'] ) ) { throw new \Exception( 'Token check error: ' . $st['error'] ); } $__remaining_global = function_exists('aissp_status_remaining') ? aissp_status_remaining( $st ) : (int) ( $st['remaining'] ?? 0 ); if ( $__remaining_global <= 0 ) { throw new \Exception( 'No tokens left. Please upgrade.' ); } /* === TOKEN GUARD (enough balance for the model?) === */ $__need = function_exists('aissp_model_cost') ? aissp_model_cost( $model_sel ) : ( $model_sel === 'gpt-4o' ? 5 : ( $model_sel === 'gpt-4.1' ? 3 : ( $model_sel === 'o3' ? 12 : 1 ) ) ); $__remaining = function_exists('aissp_status_remaining') ? aissp_status_remaining( $st ) : ( isset($st['remaining']) ? (int)$st['remaining'] : 0 ); if ( $__remaining < $__need ) { throw new \Exception( 'Not enough tokens for the selected model (' . $model_sel . '). ' . 'Needs ' . $__need . ', you have ' . $__remaining . '. ' . 'Choose a cheaper model (e.g. GPT-4 Turbo) or buy more tokens.' ); } $term_obj = get_term( $term_id ); if ( ! $term_obj || is_wp_error( $term_obj ) ) { throw new \Exception( 'Invalid term' ); } $fw = get_term_meta( $term_id, self::$mk_focus_kw, true ); $payload = [ 'context_type' => 'term', 'term_name' => $term_obj->name, 'term_slug' => $term_obj->slug, 'term_desc' => $term_obj->description, 'focus_keyword' => $fw, 'model' => $model_sel, ]; $payload['slug'] = $term_obj->slug; $payload['auto_slug'] = ( $opts['auto_slug'] ?? 'no' ) === 'yes'; $res = aissp_call_rankpilot_api( $site_token, $payload ); $newKW = $res['keyword'] ?? ''; $newTit= $res['title'] ?? ''; $newDes= $res['description'] ?? ''; $newSlu= $res['slug'] ?? ''; update_term_meta( $term_id, self::$mk_focus_kw, $newKW ); update_term_meta( $term_id, self::$mk_title, $newTit ); update_term_meta( $term_id, self::$mk_desc, $newDes ); $auto = $opts['auto_slug'] ?? 'no'; if ( $auto === 'yes' && $newSlu ) { $san = sanitize_title( $newSlu ); $term_obj_after = get_term( $term_id ); if ( $term_obj_after && $term_obj_after->slug !== $san ) { wp_update_term( $term_id, $term_obj->taxonomy, [ 'slug' => $san ] ); } } $updated = get_term( $term_id ); if ( $updated && ! is_wp_error( $updated ) ) { $sc = aissp_calc_score( $newKW, $newTit, $newDes, $updated->slug ); update_term_meta( $term_id, self::$mk_score, $sc['score'] ); } } /* ========================================================= * 9) FRONTEND: HOOK REGISTRATION * ======================================================= */ public static function init_hooks() { /* Post meta-box */ add_action( 'add_meta_boxes', [ __CLASS__, 'register_meta_boxes' ] ); add_action( 'save_post', [ __CLASS__, 'save_meta_box_data' ] ); /* Term edit UI */ add_action( 'product_cat_edit_form_fields', [ __CLASS__, 'render_term_edit_ui' ], 10, 2 ); add_action( 'product_tag_edit_form_fields', [ __CLASS__, 'render_term_edit_ui' ], 10, 2 ); add_action( 'category_edit_form_fields', [ __CLASS__, 'render_term_edit_ui' ], 10, 2 ); add_action( 'post_tag_edit_form_fields', [ __CLASS__, 'render_term_edit_ui' ], 10, 2 ); add_action( 'edited_term', [ __CLASS__, 'save_term_edit_ui' ], 10, 2 ); /* Admin list column */ add_filter( 'manage_edit-category_columns', [ __CLASS__, 'term_snippet_cols' ] ); add_filter( 'manage_edit-post_tag_columns', [ __CLASS__, 'term_snippet_cols' ] ); add_filter( 'manage_edit-product_cat_columns', [ __CLASS__, 'term_snippet_cols' ] ); add_filter( 'manage_edit-product_tag_columns', [ __CLASS__, 'term_snippet_cols' ] ); add_filter( 'manage_category_custom_column', [ __CLASS__, 'term_snippet_cols_render' ], 10, 3 ); add_filter( 'manage_post_tag_custom_column', [ __CLASS__, 'term_snippet_cols_render' ], 10, 3 ); add_filter( 'manage_product_cat_custom_column', [ __CLASS__, 'term_snippet_cols_render' ], 10, 3 ); add_filter( 'manage_product_tag_custom_column', [ __CLASS__, 'term_snippet_cols_render' ], 10, 3 ); add_filter( 'manage_edit-category_sortable_columns', [ __CLASS__, 'term_snippet_cols_sortable' ] ); add_filter( 'manage_edit-post_tag_sortable_columns', [ __CLASS__, 'term_snippet_cols_sortable' ] ); add_filter( 'manage_edit-product_cat_sortable_columns', [ __CLASS__, 'term_snippet_cols_sortable' ] ); add_filter( 'manage_edit-product_tag_sortable_columns', [ __CLASS__, 'term_snippet_cols_sortable' ] ); add_filter( 'terms_clauses', [ __CLASS__, 'term_snippet_cols_sort_clauses' ], 10, 3 ); } } /* ========================================================= * 11) BOOTSTRAP * ======================================================= */ add_action( 'plugins_loaded', [ 'AISSP_Meta_Box', 'init_hooks' ] );