Changeset 3448193
- Timestamp:
- 01/27/2026 09:19:27 PM (3 weeks ago)
- Location:
- ai-powered-seo-suggestions
- Files:
-
- 22 edited
- 1 copied
-
tags/1.1.0 (copied) (copied from ai-powered-seo-suggestions/trunk)
-
tags/1.1.0/ai-powered-seo-suggestions.php (modified) (2 diffs)
-
tags/1.1.0/assets/css/ait-aipseo-editor.css (modified) (1 diff)
-
tags/1.1.0/assets/js/ait-aipseo-editor.js (modified) (7 diffs)
-
tags/1.1.0/includes/class-ait-aipseo-ai-fixit.php (modified) (4 diffs)
-
tags/1.1.0/includes/class-ait-aipseo-ai-keywords.php (modified) (1 diff)
-
tags/1.1.0/includes/class-ait-aipseo-editor-classic.php (modified) (4 diffs)
-
tags/1.1.0/includes/class-ait-aipseo-editor-gutenberg.php (modified) (2 diffs)
-
tags/1.1.0/includes/class-ait-aipseo-meta-suggestions.php (modified) (2 diffs)
-
tags/1.1.0/includes/class-ait-aipseo-rest.php (modified) (3 diffs)
-
tags/1.1.0/includes/class-ait-aipseo-settings.php (modified) (4 diffs)
-
tags/1.1.0/readme.txt (modified) (2 diffs)
-
trunk/ai-powered-seo-suggestions.php (modified) (2 diffs)
-
trunk/assets/css/ait-aipseo-editor.css (modified) (1 diff)
-
trunk/assets/js/ait-aipseo-editor.js (modified) (7 diffs)
-
trunk/includes/class-ait-aipseo-ai-fixit.php (modified) (4 diffs)
-
trunk/includes/class-ait-aipseo-ai-keywords.php (modified) (1 diff)
-
trunk/includes/class-ait-aipseo-editor-classic.php (modified) (4 diffs)
-
trunk/includes/class-ait-aipseo-editor-gutenberg.php (modified) (2 diffs)
-
trunk/includes/class-ait-aipseo-meta-suggestions.php (modified) (2 diffs)
-
trunk/includes/class-ait-aipseo-rest.php (modified) (3 diffs)
-
trunk/includes/class-ait-aipseo-settings.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ai-powered-seo-suggestions/tags/1.1.0/ai-powered-seo-suggestions.php
r3399255 r3448193 4 4 * Plugin URI: https://www.wpaiplugins.dev/wordpress-ai-powered-seo-plugin/ 5 5 * Description: Score on-page SEO and get actionable suggestions. Pro adds AI-generated meta, semantic keywords, schema assistant, WooCommerce support, and reports. 6 * Version: 1. 0.16 * Version: 1.1.0 7 7 * Author: AI Tools 8 8 * Text Domain: ai-powered-seo-suggestions … … 14 14 15 15 // Define plugin constants 16 define( 'AIT_AIPSEO_VERSION', '1. 0.1' );16 define( 'AIT_AIPSEO_VERSION', '1.1.0' ); 17 17 define( 'AIT_AIPSEO_FILE', __FILE__ ); 18 18 define( 'AIT_AIPSEO_PATH', plugin_dir_path( __FILE__ ) ); -
ai-powered-seo-suggestions/tags/1.1.0/assets/css/ait-aipseo-editor.css
r3390026 r3448193 6 6 .ait-aipseo-muted { color: #6b7280; } 7 7 .ait-aipseo-actions { margin-top: 8px; } 8 9 .ait-aipseo-cpt-notice { 10 margin: 4px 0 10px; 11 padding: 6px 8px; 12 background: #fffbeb; 13 border-left: 3px solid #f59e0b; 14 font-size: 12px; 15 } 16 17 .ait-aipseo-links-list { 18 margin-top: 6px; 19 padding-left: 18px; 20 } 21 22 .ait-aipseo-links-list li { 23 margin-bottom: 4px; 24 } 25 26 .ait-aipseo-links-list a { 27 text-decoration: none; 28 } 29 30 .ait-aipseo-links-list a:hover { 31 text-decoration: underline; 32 } 33 34 .ait-aipseo-error { 35 color: #b91c1c; 36 } -
ai-powered-seo-suggestions/tags/1.1.0/assets/js/ait-aipseo-editor.js
r3390026 r3448193 49 49 const [schemaPreview, setSchemaPreview] = useState(''); 50 50 const [schemaOpen, setSchemaOpen] = useState(false); 51 51 52 const [links, setLinks] = useState([]); 53 const [loadingLinks, setLoadingLinks] = useState(false); 54 const [linksError, setLinksError] = useState(""); 55 52 56 const postId = select('core/editor')?.getCurrentPostId(); 57 const postType = select('core/editor')?.getCurrentPostType ? select('core/editor').getCurrentPostType() : null; 53 58 const nonce = AIT_AIPSEO_EDITOR?.nonce; 54 59 const pro = AIT_AIPSEO_EDITOR?.pro; … … 58 63 const kwURL = (ai.keywords || (base.replace(/\/+$/,'') + '/ai/keywords')); 59 64 const fxURL = (ai.fixit || (base.replace(/\/+$/,'') + '/ai/fixit')); 65 const linksURL = (ai.internal || (base.replace(/\/+$/,'') + '/internal-links')); 66 67 const isCustomPostType = postType && postType !== 'post' && postType !== 'page'; 60 68 61 69 function load_kws(){ … … 73 81 .then((f) => { setFx(f || []); }) 74 82 .finally(() => setLoadingFx(false)); 83 } 84 85 function load_links(){ 86 if (!postId) return; 87 setLoadingLinks(true); 88 fetchList(linksURL, postId, nonce) 89 .then((items) => { 90 setLinks(Array.isArray(items) ? items : []); 91 setLinksError(""); 92 }) 93 .catch(() => { 94 setLinks([]); 95 setLinksError("Sorry — couldn’t find internal link suggestions."); 96 }) 97 .finally(() => setLoadingLinks(false)); 75 98 } 76 99 … … 234 257 // ------- AI Keyword Hints ------- 235 258 h('div', {}, 236 h('h3', {}, 'AI Keyword Hints (Lite)'), 259 h('h3', {}, 'AI Keyword Hints ' + ( ! pro ? '(Lite)' : '' ) ), 260 isCustomPostType && h('p', { className: 'ait-aipseo-cpt-notice' }, 261 'This post is from a custom post type. You may want to consider whether it should be noindexed or nofollowed.' 262 ), 237 263 (kw && kw.length) 238 264 ? h('ul', { className: 'ait-aipseo-ai-list' }, kw.map((t,i)=> h('li', { key: i }, t ))) … … 242 268 ), 243 269 h('hr'), 244 h('h3', {}, 'AI Fix-It Tips (Lite)'),270 h('h3', {}, 'AI Fix-It Tips ' + ( ! pro ? '(Lite)' : '' ) ), 245 271 (fx && fx.length) 246 ? h('ol', { className: 'ait-aipseo-ai-list' }, fx.map((t,i)=> h('li', { key: i }, t ))) 272 ? h('ol', { className: 'ait-aipseo-ai-list' }, fx.map((t, i) => 273 h('li', { 274 key: i, 275 dangerouslySetInnerHTML: { __html: t } 276 }) 277 )) 247 278 : h('p', { className: 'ait-aipseo-muted' }, 'No AI tips yet.'), 248 279 h('div', { className: 'ait-aipseo-actions' }, … … 279 310 placeholder: 'e.g. ai seo, wordpress seo, meta description' 280 311 }), 312 313 // ------- Internal Link Suggestions ------- 314 h('hr'), 315 h('h3', {}, 'Internal Link Suggestions'), 316 linksError && h('p', { className: 'ait-aipseo-error' }, linksError), 317 (links && links.length) 318 ? h('ul', { className: 'ait-aipseo-ai-list ait-aipseo-links-list' }, 319 links.map((s, i) => 320 h('li', { key: i }, 321 h('strong', {}, s.anchor || s.keyword), 322 ' \u2192 ', 323 h('a', { 324 href: s.url, 325 target: '_blank', 326 rel: 'noopener noreferrer' 327 }, s.title || s.url) 328 ) 329 ) 330 ) 331 : h('p', { className: 'ait-aipseo-muted' }, 'No internal link suggestions yet.'), 332 h('div', { className: 'ait-aipseo-actions' }, 333 h('button', { 334 className: 'components-button is-secondary', 335 onClick: load_links, 336 disabled: loadingLinks, 337 }, loadingLinks ? 'Analyzing…' : 'Find Suggestions') 338 ), 281 339 282 340 // ------- Schema Assistant ------- … … 394 452 className: 'ait-aipseo-schema-modal', 395 453 }, 396 h('pre', { 397 style: { 398 maxHeight: '60vh', 399 overflow: 'auto', 400 background: '#f8f8f8', 401 padding: '10px', 402 borderRadius: '4px', 403 }, 404 }, schemaPreview || 'No schema to display.'), 454 h(wp.components.TextareaControl, { 455 label: 'Generated Schema (edit before inserting)', 456 value: schemaPreview || '', 457 onChange: (val) => setSchemaPreview(val), 458 rows: 14, 459 className: 'ait-aipseo-schema-textarea', 460 }), 405 461 h('div', { className: 'ait-aipseo-meta-footer' }, 406 462 h('button', { -
ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-ai-fixit.php
r3390026 r3448193 18 18 $signals = $this->ensure_signals( $post_id ); 19 19 $kws = AIT_AIPSEO_Keyword_Density::get_target_keywords( $post_id ); 20 $post_type = get_post_type( $post_id ); 20 21 21 22 $context = [ 22 'post_id' => $post_id, 23 'signals' => $signals, 24 'keywords'=> array_slice( $kws, 0, 5 ), 23 'post_id' => $post_id, 24 'signals' => $signals, 25 'keywords' => array_slice( $kws, 0, 5 ), 26 'post_type' => $post_type, 27 'time' => date('Y-m-d H:i'), 25 28 ]; 26 29 $hash = md5( wp_json_encode( $context ) ); … … 40 43 41 44 $data = $this->normalize_list( $result ); 45 46 $thumb_id = get_post_thumbnail_id( $post_id ); 47 $alt = $thumb_id ? get_post_meta( $thumb_id, '_wp_attachment_image_alt', true ) : ''; 48 49 if ( $thumb_id && empty( trim( $alt ) ) ) { 50 $suggestion = sprintf( 51 __( 'Your featured image is missing alt text. Consider installing the free AI Image Alt Text plugin to <a target=\'_blank\' href=\'%s\'>automatically generate alt text</a>.', 'ai-powered-seo-suggestions' ), 52 'https://wordpress.org/plugins/ai-image-alt-text/' 53 ); 54 array_unshift( $data, $suggestion ); 55 } 56 42 57 update_post_meta( $post_id, '_ait_aipseo_ai_fixit_tips', [ 43 58 'hash' => $hash, … … 46 61 ] ); 47 62 48 return array_slice( $data, 0, max( 1, $limit ) ); 63 return array_slice( $data, 0, max( 1, $limit ) ); 49 64 } 50 65 … … 74 89 75 90 $sys = (string) apply_filters( 'ait_aipseo/ai_fixit_system_prompt', 76 'You are an SEO assistant. Convert numeric on-page signals into specific, actionable suggestions for the editor. Use short, imperative sentences. Output ONLY a compact JSON array of strings (no numbering, no explanations).' 91 sprintf( 92 'You are an SEO assistant. Convert numeric on-page signals into specific, actionable suggestions for the editor. The suggestions should be geared towards a %s WordPress post. Use short, imperative sentences. Output ONLY a compact JSON array of strings (no numbering, no explanations).', 93 $context['post_type'], 94 ) 77 95 ); 78 96 -
ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-ai-keywords.php
r3390026 r3448193 63 63 'headings' => array_slice( array_values( array_filter( array_map( 'trim', $headings ) ) ), 0, 10 ), 64 64 'keywords' => array_slice( $kws, 0, 5 ), 65 'time' => date('Y-m-d H:i'), 65 66 ]; 66 67 } -
ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-editor-classic.php
r3390026 r3448193 38 38 $desc = get_post_meta( $post->ID, 'ait_aipseo_meta_description', true ); 39 39 40 $pro = (bool) apply_filters('ait_aipseo/can_pro', false); 41 40 42 wp_nonce_field( 'ait_aipseo_meta', 'ait_aipseo_meta_nonce' ); 41 43 ?> … … 52 54 <hr /> 53 55 54 <h4><?php esc_html_e( 'AI Keyword Hints (Lite)', 'ai-powered-seo-suggestions' ); ?></h4>56 <h4><?php esc_html_e( 'AI Keyword Hints', 'ai-powered-seo-suggestions' ); echo ( ! $pro ? '(' . esc_html( __( 'Lite', 'ai-powered-seo-suggestions' ) ) . ')' : '' ); ?></h4> 55 57 <?php 56 58 $kw_items = apply_filters( 'ait_aipseo_semantic_keywords', [], (int) $post->ID ); … … 65 67 <?php endif; ?> 66 68 67 <h4><?php esc_html_e( 'AI Fix-It Tips (Lite)', 'ai-powered-seo-suggestions' ); ?></h4>69 <h4><?php esc_html_e( 'AI Fix-It Tips', 'ai-powered-seo-suggestions' ); echo ( ! $pro ? '(' . esc_html( __( 'Lite', 'ai-powered-seo-suggestions' ) ) . ')' : '' ); ?></h4> 68 70 <?php 69 71 $fix_items = apply_filters( 'ait_aipseo_fixit_tips', [], (int) $post->ID ); … … 71 73 <ol class="ait-aipseo-ai-list ait-aipseo-ai-fixit"> 72 74 <?php foreach ( $fix_items as $tip ) : ?> 73 <li><?php echo esc_html( (string)$tip ); ?></li>75 <li><?php echo wp_kses_post( $tip ); ?></li> 74 76 <?php endforeach; ?> 75 77 </ol> -
ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-editor-gutenberg.php
r3390026 r3448193 12 12 public function enqueue() { 13 13 $hide_gutenberg = AIT_AIPSEO_Settings::get('hide_gutenberg'); 14 $post_types = (array) AIT_AIPSEO_Settings::get('post_types'); 14 15 15 16 if ( ! empty( $hide_gutenberg ) || ! $this->current_user_has_visible_role() ) { return; } 17 18 $screen = get_current_screen(); 19 20 if ( empty( $screen ) || $screen->base !== 'post' || ! in_array( $screen->post_type, $post_types, true ) ) { 21 return; 22 } 16 23 17 24 wp_enqueue_style( 'ait-aipseo-editor', AIT_AIPSEO_URL . 'assets/css/ait-aipseo-editor.css', [], AIT_AIPSEO_VERSION ); … … 27 34 'keywords' => $rest_base . '/ai/keywords', 28 35 'fixit' => $rest_base . '/ai/fixit', 36 'internal' => $rest_base . '/internal-links', 29 37 'seo_title' => $rest_base . '/ai/seo_title', 30 38 'meta_desc' => $rest_base . '/ai/meta_desc', -
ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-meta-suggestions.php
r3390026 r3448193 8 8 // Provide filters that editor UI/REST can use 9 9 add_filter( 'ait_aipseo_meta_suggestions', [ $this, 'suggest_for_post' ], 10, 2 ); 10 11 // Front-end output for manual meta fields. 12 add_filter( 'pre_get_document_title', [ $this, 'filter_document_title' ], 20 ); 13 add_filter( 'document_title_parts', [ $this, 'filter_document_title_parts' ], 20 ); 14 add_action( 'wp_head', [ $this, 'output_manual_meta_description' ], 1 ); 10 15 } 16 11 17 12 18 public function suggest_for_post( $suggestions, $post_id ) { … … 49 55 return rtrim( $desc, '. ' ) . '. ' . __( 'Learn more.', 'ai-powered-seo-suggestions' ); 50 56 } 57 58 /** 59 * Get manual meta values for a post, if set. 60 * 61 * @param int $post_id 62 * @return array{title:string, description:string} 63 */ 64 private function get_manual_meta( int $post_id ): array { 65 $title = trim( (string) get_post_meta( $post_id, 'ait_aipseo_meta_title', true ) ); 66 $desc = trim( (string) get_post_meta( $post_id, 'ait_aipseo_meta_description', true ) ); 67 68 return [ 69 'title' => $title, 70 'description' => $desc, 71 ]; 72 } 73 74 /** 75 * Replace the WP document title with the manual meta title if present. 76 */ 77 public function filter_document_title( $title ) { 78 if ( ! is_singular() ) { return $title; } 79 80 $post_id = get_queried_object_id(); 81 if ( ! $post_id ) { return $title; } 82 83 $manual = $this->get_manual_meta( (int) $post_id ); 84 if ( '' === $manual['title'] ) { return $title; } 85 86 return $manual['title']; 87 } 88 89 /** 90 * Some themes use document_title_parts. Ensure the manual meta title wins there too. 91 */ 92 public function filter_document_title_parts( $parts ) { 93 if ( ! is_array( $parts ) ) { return $parts; } 94 if ( ! is_singular() ) { return $parts; } 95 96 $post_id = get_queried_object_id(); 97 if ( ! $post_id ) { return $parts; } 98 99 $manual = $this->get_manual_meta( (int) $post_id ); 100 if ( '' === $manual['title'] ) { return $parts; } 101 102 $parts['title'] = $manual['title']; 103 return $parts; 104 } 105 106 /** 107 * Output meta description tag using manual meta description if present. 108 */ 109 public function output_manual_meta_description() { 110 if ( ! is_singular() ) { return; } 111 112 $post_id = get_queried_object_id(); 113 if ( ! $post_id ) { return; } 114 115 $manual = $this->get_manual_meta( (int) $post_id ); 116 if ( '' === $manual['description'] ) { return; } 117 118 // Allow other integrations to disable this output if desired. 119 if ( false === apply_filters( 'ait_aipseo_output_manual_meta_description', true, $post_id ) ) { 120 return; 121 } 122 123 echo "\n" . '<meta name="description" content="' . esc_attr( $manual['description'] ) . '" />' . "\n"; 124 } 51 125 } -
ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-rest.php
r3390026 r3448193 30 30 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, 31 31 ] ); 32 33 register_rest_route( 'aipseo/v1', '/internal-links/(?P<id>\d+)', [ 34 'methods' => 'GET', 35 'callback' => [ $this, 'get_internal_link_suggestions' ], 36 'permission_callback' => function() { 37 return current_user_can( 'edit_posts' ); 38 }, 39 ] ); 32 40 } 33 41 … … 59 67 return rest_ensure_response( [ 60 68 'post_id' => $post_id, 61 'items' => array_values( array_map( ' sanitize_text_field', $items ) ),69 'items' => array_values( array_map( 'wp_kses_post', $items ) ), 62 70 ] ); 63 71 } … … 77 85 return rest_ensure_response( [ 78 86 'post_id' => $post_id, 79 'items' => array_values( array_map( 'sanitize_text_field', $items ) ), 80 ] ); 81 } 87 'items' => array_values( array_map( 'wp_kses_post', $items ) ), 88 ] ); 89 } 90 91 /** 92 * Suggest internal link opportunities based on the post content and other site posts. 93 * 94 * Returns objects like: 95 * [ 'anchor' => 'iPad', 'title' => 'iPad Accessories', 'url' => 'https://example.com/ipad-accessories/' ] 96 */ 97 public function get_internal_link_suggestions( WP_REST_Request $request ) { 98 $post_id = (int) $request['id']; 99 if ( $post_id <= 0 ) { 100 return new WP_Error( 101 'aipseo_bad_id', 102 __( 'Invalid post ID.', 'ai-powered-seo-suggestions' ), 103 [ 'status' => 400 ] 104 ); 105 } 106 107 $post = get_post( $post_id ); 108 if ( ! $post || 'trash' === $post->post_status ) { 109 return rest_ensure_response( [ 110 'post_id' => $post_id, 111 'items' => [], 112 ] ); 113 } 114 115 // Strip tags / shortcodes for a simple text search. 116 $content = wp_strip_all_tags( $post->post_content ); 117 $content_lc = mb_strtolower( $content ); 118 119 // Fetch a reasonable pool of internal targets. 120 $query = new WP_Query( [ 121 'post_type' => 'any', 122 'post_status' => 'publish', 123 'posts_per_page' => 200, 124 'post__not_in' => [ $post_id ], 125 'fields' => 'ids', 126 ] ); 127 128 $suggestions = []; 129 if ( $query->have_posts() ) { 130 foreach ( $query->posts as $target_id ) { 131 $title = get_the_title( $target_id ); 132 $slug = get_post_field( 'post_name', $target_id ); 133 134 if ( ! $title && ! $slug ) { 135 continue; 136 } 137 138 $candidates = []; 139 140 // From slug: ipad-pro-11 => [ 'ipad', 'pro', '11' ] 141 if ( $slug ) { 142 foreach ( preg_split( '/[-_]+/', mb_strtolower( $slug ) ) as $part ) { 143 $part = trim( $part ); 144 if ( mb_strlen( $part ) >= 3 ) { 145 $candidates[] = $part; 146 } 147 } 148 } 149 150 // From title words 151 if ( $title ) { 152 foreach ( preg_split( '/\s+/', mb_strtolower( wp_strip_all_tags( $title ) ) ) as $word ) { 153 $word = preg_replace( '/[^\p{L}\p{N}]+/u', '', $word ); 154 if ( mb_strlen( $word ) >= 5 ) { 155 $candidates[] = $word; 156 } 157 } 158 } 159 160 $candidates = array_unique( $candidates ); 161 $matched_anchor = null; 162 163 foreach ( $candidates as $word ) { 164 if ( ! $word ) { 165 continue; 166 } 167 168 // Word-boundary match to avoid weird partials. 169 if ( preg_match( '/\b' . preg_quote( $word, '/' ) . '\b/ui', $content_lc ) ) { 170 $matched_anchor = $word; 171 break; 172 } 173 } 174 175 if ( ! $matched_anchor ) { 176 continue; 177 } 178 179 $suggestions[] = [ 180 'anchor' => ucfirst( $matched_anchor ), 181 'title' => $title, 182 'url' => get_permalink( $target_id ), 183 ]; 184 185 if ( count( $suggestions ) >= 20 ) { 186 break; 187 } 188 } 189 190 return rest_ensure_response( [ 191 'post_id' => $post_id, 192 'items' => array_values( $suggestions ), 193 ] ); 194 } 195 196 /** 197 * Filter internal link suggestions. 198 * 199 * @param array $suggestions Array of [ anchor, title, url ]. 200 * @param int $post_id Current post ID. 201 */ 202 $suggestions = apply_filters( 'ait_aipseo_internal_link_suggestions', $suggestions, $post_id ); 203 204 return rest_ensure_response( [ 205 'post_id' => $post_id, 206 'items' => $suggestions, 207 ] ); 208 } 209 82 210 83 211 private function current_user_has_visible_role(): bool { -
ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-settings.php
r3399255 r3448193 60 60 61 61 // Schema 62 'schema_mode' => ' preview_then_insert',62 'schema_mode' => 'auto_insert', 63 63 'schema_overwrite_existing' => false, 64 64 'schema_minify' => true, … … 301 301 'description' => __('Which OpenAI model should be used to handle the image processing requests?', 'ai-powered-seo-suggestions'), 302 302 'options' => [ 303 'gpt-5.1' => __( 'GPT-5.1', 'ai-powered-seo-suggestions' ), 303 304 'gpt-5' => __( 'GPT-5', 'ai-powered-seo-suggestions' ), 305 'gpt-5-mini' => __( 'GPT-5 Mini', 'ai-powered-seo-suggestions' ), 304 306 'gpt-4.1' => __( 'GPT-4.1', 'ai-powered-seo-suggestions' ), 305 307 'gpt-4o' => __( 'GPT-4o', 'ai-powered-seo-suggestions' ), … … 327 329 'radio', 328 330 [ 329 'id' => 'language', 330 'title' => __( 'Language', 'ai-powered-seo-suggestions' ), 331 'options' => array( 331 'id' => 'language', 332 'title' => __( 'Language', 'ai-powered-seo-suggestions' ), 333 'description' => __( 'Choose how AIPSEO decides which language to use when generating AI content (site language, detected from the content, or a custom language you set below).', 'ai-powered-seo-suggestions' ), 334 'options' => array( 332 335 'site' => __( 'Site Language', 'ai-powered-seo-suggestions' ), 333 336 'content-detected' => __( 'Detect from Content', 'ai-powered-seo-suggestions' ), 334 337 'custom' => __( 'Custom (set below)', 'ai-powered-seo-suggestions' ), 335 338 ), 336 'default' => 'site',339 'default' => 'site', 337 340 ] 338 341 ); … … 599 602 'checkbox', 600 603 [ 601 'id' => 'compat_integrations', 602 'title' => __( 'Integrations', 'ai-powered-seo-suggestions' ), 603 'options' => [ 604 'aio_seo' => __( 'All-In-One SEO', 'ai-powered-seo-suggestions' ), 605 'yoast' => __( 'Yoast SEO', 'ai-powered-seo-suggestions' ), 606 'rankmath' => __( 'Rank Math', 'ai-powered-seo-suggestions' ), 607 'seopress' => __( 'SEOPress', 'ai-powered-seo-suggestions' ), 604 'id' => 'compat_integrations', 605 'title' => __( 'Integrations', 'ai-powered-seo-suggestions' ), 606 'description' => __( 'Select which other SEO plugins AIPSEO should read from and write to, so titles, meta descriptions, and keywords stay in sync across plugins.', 'ai-powered-seo-suggestions' ), 607 'options' => [ 608 'aio_seo' => __( 'All-In-One SEO', 'ai-powered-seo-suggestions' ), 609 'yoast' => __( 'Yoast SEO', 'ai-powered-seo-suggestions' ), 610 'rankmath' => __( 'Rank Math', 'ai-powered-seo-suggestions' ), 611 'seopress' => __( 'SEOPress', 'ai-powered-seo-suggestions' ), 608 612 ], 609 'default' => self::$defaults['compat_integrations'],613 'default' => self::$defaults['compat_integrations'], 610 614 ] 611 615 ); -
ai-powered-seo-suggestions/tags/1.1.0/readme.txt
r3408444 r3448193 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1. 0.17 Stable tag: 1.1.0 8 8 License: GPLv3 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 91 91 == Changelog == 92 92 93 = 1.1.0 (2026-01-27) = 94 - Overhauled WooCommerce integration, so suggestions/fix-it tips for products are now much more WooCommerce-specific, and so you can now make use of manual features on the product edit screen, like specifying the meta title, meta description and target keywords. 95 - Added new internal link suggestions that find posts and pages related to the content of the post you're editing and suggest links to them. 96 - You can now set a default schema type for each activated post type on your site (including the default ones as well as custom ones). 97 - Updates to have more detailed suggestions for featured images and alt text. 98 - Added suggestions regarding nofollow and noindex for custom post type (posts). 99 - Fixed issue with suggestions still showing for post types that weren't selected on the general settings page. 100 93 101 = 1.0.1 (2025-11-19) = 94 102 * Update to make sure the minimum, maximum and step values, for number inputs on the settings pages that require them, are correctly implemented. -
ai-powered-seo-suggestions/trunk/ai-powered-seo-suggestions.php
r3399255 r3448193 4 4 * Plugin URI: https://www.wpaiplugins.dev/wordpress-ai-powered-seo-plugin/ 5 5 * Description: Score on-page SEO and get actionable suggestions. Pro adds AI-generated meta, semantic keywords, schema assistant, WooCommerce support, and reports. 6 * Version: 1. 0.16 * Version: 1.1.0 7 7 * Author: AI Tools 8 8 * Text Domain: ai-powered-seo-suggestions … … 14 14 15 15 // Define plugin constants 16 define( 'AIT_AIPSEO_VERSION', '1. 0.1' );16 define( 'AIT_AIPSEO_VERSION', '1.1.0' ); 17 17 define( 'AIT_AIPSEO_FILE', __FILE__ ); 18 18 define( 'AIT_AIPSEO_PATH', plugin_dir_path( __FILE__ ) ); -
ai-powered-seo-suggestions/trunk/assets/css/ait-aipseo-editor.css
r3390026 r3448193 6 6 .ait-aipseo-muted { color: #6b7280; } 7 7 .ait-aipseo-actions { margin-top: 8px; } 8 9 .ait-aipseo-cpt-notice { 10 margin: 4px 0 10px; 11 padding: 6px 8px; 12 background: #fffbeb; 13 border-left: 3px solid #f59e0b; 14 font-size: 12px; 15 } 16 17 .ait-aipseo-links-list { 18 margin-top: 6px; 19 padding-left: 18px; 20 } 21 22 .ait-aipseo-links-list li { 23 margin-bottom: 4px; 24 } 25 26 .ait-aipseo-links-list a { 27 text-decoration: none; 28 } 29 30 .ait-aipseo-links-list a:hover { 31 text-decoration: underline; 32 } 33 34 .ait-aipseo-error { 35 color: #b91c1c; 36 } -
ai-powered-seo-suggestions/trunk/assets/js/ait-aipseo-editor.js
r3390026 r3448193 49 49 const [schemaPreview, setSchemaPreview] = useState(''); 50 50 const [schemaOpen, setSchemaOpen] = useState(false); 51 51 52 const [links, setLinks] = useState([]); 53 const [loadingLinks, setLoadingLinks] = useState(false); 54 const [linksError, setLinksError] = useState(""); 55 52 56 const postId = select('core/editor')?.getCurrentPostId(); 57 const postType = select('core/editor')?.getCurrentPostType ? select('core/editor').getCurrentPostType() : null; 53 58 const nonce = AIT_AIPSEO_EDITOR?.nonce; 54 59 const pro = AIT_AIPSEO_EDITOR?.pro; … … 58 63 const kwURL = (ai.keywords || (base.replace(/\/+$/,'') + '/ai/keywords')); 59 64 const fxURL = (ai.fixit || (base.replace(/\/+$/,'') + '/ai/fixit')); 65 const linksURL = (ai.internal || (base.replace(/\/+$/,'') + '/internal-links')); 66 67 const isCustomPostType = postType && postType !== 'post' && postType !== 'page'; 60 68 61 69 function load_kws(){ … … 73 81 .then((f) => { setFx(f || []); }) 74 82 .finally(() => setLoadingFx(false)); 83 } 84 85 function load_links(){ 86 if (!postId) return; 87 setLoadingLinks(true); 88 fetchList(linksURL, postId, nonce) 89 .then((items) => { 90 setLinks(Array.isArray(items) ? items : []); 91 setLinksError(""); 92 }) 93 .catch(() => { 94 setLinks([]); 95 setLinksError("Sorry — couldn’t find internal link suggestions."); 96 }) 97 .finally(() => setLoadingLinks(false)); 75 98 } 76 99 … … 234 257 // ------- AI Keyword Hints ------- 235 258 h('div', {}, 236 h('h3', {}, 'AI Keyword Hints (Lite)'), 259 h('h3', {}, 'AI Keyword Hints ' + ( ! pro ? '(Lite)' : '' ) ), 260 isCustomPostType && h('p', { className: 'ait-aipseo-cpt-notice' }, 261 'This post is from a custom post type. You may want to consider whether it should be noindexed or nofollowed.' 262 ), 237 263 (kw && kw.length) 238 264 ? h('ul', { className: 'ait-aipseo-ai-list' }, kw.map((t,i)=> h('li', { key: i }, t ))) … … 242 268 ), 243 269 h('hr'), 244 h('h3', {}, 'AI Fix-It Tips (Lite)'),270 h('h3', {}, 'AI Fix-It Tips ' + ( ! pro ? '(Lite)' : '' ) ), 245 271 (fx && fx.length) 246 ? h('ol', { className: 'ait-aipseo-ai-list' }, fx.map((t,i)=> h('li', { key: i }, t ))) 272 ? h('ol', { className: 'ait-aipseo-ai-list' }, fx.map((t, i) => 273 h('li', { 274 key: i, 275 dangerouslySetInnerHTML: { __html: t } 276 }) 277 )) 247 278 : h('p', { className: 'ait-aipseo-muted' }, 'No AI tips yet.'), 248 279 h('div', { className: 'ait-aipseo-actions' }, … … 279 310 placeholder: 'e.g. ai seo, wordpress seo, meta description' 280 311 }), 312 313 // ------- Internal Link Suggestions ------- 314 h('hr'), 315 h('h3', {}, 'Internal Link Suggestions'), 316 linksError && h('p', { className: 'ait-aipseo-error' }, linksError), 317 (links && links.length) 318 ? h('ul', { className: 'ait-aipseo-ai-list ait-aipseo-links-list' }, 319 links.map((s, i) => 320 h('li', { key: i }, 321 h('strong', {}, s.anchor || s.keyword), 322 ' \u2192 ', 323 h('a', { 324 href: s.url, 325 target: '_blank', 326 rel: 'noopener noreferrer' 327 }, s.title || s.url) 328 ) 329 ) 330 ) 331 : h('p', { className: 'ait-aipseo-muted' }, 'No internal link suggestions yet.'), 332 h('div', { className: 'ait-aipseo-actions' }, 333 h('button', { 334 className: 'components-button is-secondary', 335 onClick: load_links, 336 disabled: loadingLinks, 337 }, loadingLinks ? 'Analyzing…' : 'Find Suggestions') 338 ), 281 339 282 340 // ------- Schema Assistant ------- … … 394 452 className: 'ait-aipseo-schema-modal', 395 453 }, 396 h('pre', { 397 style: { 398 maxHeight: '60vh', 399 overflow: 'auto', 400 background: '#f8f8f8', 401 padding: '10px', 402 borderRadius: '4px', 403 }, 404 }, schemaPreview || 'No schema to display.'), 454 h(wp.components.TextareaControl, { 455 label: 'Generated Schema (edit before inserting)', 456 value: schemaPreview || '', 457 onChange: (val) => setSchemaPreview(val), 458 rows: 14, 459 className: 'ait-aipseo-schema-textarea', 460 }), 405 461 h('div', { className: 'ait-aipseo-meta-footer' }, 406 462 h('button', { -
ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-ai-fixit.php
r3390026 r3448193 18 18 $signals = $this->ensure_signals( $post_id ); 19 19 $kws = AIT_AIPSEO_Keyword_Density::get_target_keywords( $post_id ); 20 $post_type = get_post_type( $post_id ); 20 21 21 22 $context = [ 22 'post_id' => $post_id, 23 'signals' => $signals, 24 'keywords'=> array_slice( $kws, 0, 5 ), 23 'post_id' => $post_id, 24 'signals' => $signals, 25 'keywords' => array_slice( $kws, 0, 5 ), 26 'post_type' => $post_type, 27 'time' => date('Y-m-d H:i'), 25 28 ]; 26 29 $hash = md5( wp_json_encode( $context ) ); … … 40 43 41 44 $data = $this->normalize_list( $result ); 45 46 $thumb_id = get_post_thumbnail_id( $post_id ); 47 $alt = $thumb_id ? get_post_meta( $thumb_id, '_wp_attachment_image_alt', true ) : ''; 48 49 if ( $thumb_id && empty( trim( $alt ) ) ) { 50 $suggestion = sprintf( 51 __( 'Your featured image is missing alt text. Consider installing the free AI Image Alt Text plugin to <a target=\'_blank\' href=\'%s\'>automatically generate alt text</a>.', 'ai-powered-seo-suggestions' ), 52 'https://wordpress.org/plugins/ai-image-alt-text/' 53 ); 54 array_unshift( $data, $suggestion ); 55 } 56 42 57 update_post_meta( $post_id, '_ait_aipseo_ai_fixit_tips', [ 43 58 'hash' => $hash, … … 46 61 ] ); 47 62 48 return array_slice( $data, 0, max( 1, $limit ) ); 63 return array_slice( $data, 0, max( 1, $limit ) ); 49 64 } 50 65 … … 74 89 75 90 $sys = (string) apply_filters( 'ait_aipseo/ai_fixit_system_prompt', 76 'You are an SEO assistant. Convert numeric on-page signals into specific, actionable suggestions for the editor. Use short, imperative sentences. Output ONLY a compact JSON array of strings (no numbering, no explanations).' 91 sprintf( 92 'You are an SEO assistant. Convert numeric on-page signals into specific, actionable suggestions for the editor. The suggestions should be geared towards a %s WordPress post. Use short, imperative sentences. Output ONLY a compact JSON array of strings (no numbering, no explanations).', 93 $context['post_type'], 94 ) 77 95 ); 78 96 -
ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-ai-keywords.php
r3390026 r3448193 63 63 'headings' => array_slice( array_values( array_filter( array_map( 'trim', $headings ) ) ), 0, 10 ), 64 64 'keywords' => array_slice( $kws, 0, 5 ), 65 'time' => date('Y-m-d H:i'), 65 66 ]; 66 67 } -
ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-editor-classic.php
r3390026 r3448193 38 38 $desc = get_post_meta( $post->ID, 'ait_aipseo_meta_description', true ); 39 39 40 $pro = (bool) apply_filters('ait_aipseo/can_pro', false); 41 40 42 wp_nonce_field( 'ait_aipseo_meta', 'ait_aipseo_meta_nonce' ); 41 43 ?> … … 52 54 <hr /> 53 55 54 <h4><?php esc_html_e( 'AI Keyword Hints (Lite)', 'ai-powered-seo-suggestions' ); ?></h4>56 <h4><?php esc_html_e( 'AI Keyword Hints', 'ai-powered-seo-suggestions' ); echo ( ! $pro ? '(' . esc_html( __( 'Lite', 'ai-powered-seo-suggestions' ) ) . ')' : '' ); ?></h4> 55 57 <?php 56 58 $kw_items = apply_filters( 'ait_aipseo_semantic_keywords', [], (int) $post->ID ); … … 65 67 <?php endif; ?> 66 68 67 <h4><?php esc_html_e( 'AI Fix-It Tips (Lite)', 'ai-powered-seo-suggestions' ); ?></h4>69 <h4><?php esc_html_e( 'AI Fix-It Tips', 'ai-powered-seo-suggestions' ); echo ( ! $pro ? '(' . esc_html( __( 'Lite', 'ai-powered-seo-suggestions' ) ) . ')' : '' ); ?></h4> 68 70 <?php 69 71 $fix_items = apply_filters( 'ait_aipseo_fixit_tips', [], (int) $post->ID ); … … 71 73 <ol class="ait-aipseo-ai-list ait-aipseo-ai-fixit"> 72 74 <?php foreach ( $fix_items as $tip ) : ?> 73 <li><?php echo esc_html( (string)$tip ); ?></li>75 <li><?php echo wp_kses_post( $tip ); ?></li> 74 76 <?php endforeach; ?> 75 77 </ol> -
ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-editor-gutenberg.php
r3390026 r3448193 12 12 public function enqueue() { 13 13 $hide_gutenberg = AIT_AIPSEO_Settings::get('hide_gutenberg'); 14 $post_types = (array) AIT_AIPSEO_Settings::get('post_types'); 14 15 15 16 if ( ! empty( $hide_gutenberg ) || ! $this->current_user_has_visible_role() ) { return; } 17 18 $screen = get_current_screen(); 19 20 if ( empty( $screen ) || $screen->base !== 'post' || ! in_array( $screen->post_type, $post_types, true ) ) { 21 return; 22 } 16 23 17 24 wp_enqueue_style( 'ait-aipseo-editor', AIT_AIPSEO_URL . 'assets/css/ait-aipseo-editor.css', [], AIT_AIPSEO_VERSION ); … … 27 34 'keywords' => $rest_base . '/ai/keywords', 28 35 'fixit' => $rest_base . '/ai/fixit', 36 'internal' => $rest_base . '/internal-links', 29 37 'seo_title' => $rest_base . '/ai/seo_title', 30 38 'meta_desc' => $rest_base . '/ai/meta_desc', -
ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-meta-suggestions.php
r3390026 r3448193 8 8 // Provide filters that editor UI/REST can use 9 9 add_filter( 'ait_aipseo_meta_suggestions', [ $this, 'suggest_for_post' ], 10, 2 ); 10 11 // Front-end output for manual meta fields. 12 add_filter( 'pre_get_document_title', [ $this, 'filter_document_title' ], 20 ); 13 add_filter( 'document_title_parts', [ $this, 'filter_document_title_parts' ], 20 ); 14 add_action( 'wp_head', [ $this, 'output_manual_meta_description' ], 1 ); 10 15 } 16 11 17 12 18 public function suggest_for_post( $suggestions, $post_id ) { … … 49 55 return rtrim( $desc, '. ' ) . '. ' . __( 'Learn more.', 'ai-powered-seo-suggestions' ); 50 56 } 57 58 /** 59 * Get manual meta values for a post, if set. 60 * 61 * @param int $post_id 62 * @return array{title:string, description:string} 63 */ 64 private function get_manual_meta( int $post_id ): array { 65 $title = trim( (string) get_post_meta( $post_id, 'ait_aipseo_meta_title', true ) ); 66 $desc = trim( (string) get_post_meta( $post_id, 'ait_aipseo_meta_description', true ) ); 67 68 return [ 69 'title' => $title, 70 'description' => $desc, 71 ]; 72 } 73 74 /** 75 * Replace the WP document title with the manual meta title if present. 76 */ 77 public function filter_document_title( $title ) { 78 if ( ! is_singular() ) { return $title; } 79 80 $post_id = get_queried_object_id(); 81 if ( ! $post_id ) { return $title; } 82 83 $manual = $this->get_manual_meta( (int) $post_id ); 84 if ( '' === $manual['title'] ) { return $title; } 85 86 return $manual['title']; 87 } 88 89 /** 90 * Some themes use document_title_parts. Ensure the manual meta title wins there too. 91 */ 92 public function filter_document_title_parts( $parts ) { 93 if ( ! is_array( $parts ) ) { return $parts; } 94 if ( ! is_singular() ) { return $parts; } 95 96 $post_id = get_queried_object_id(); 97 if ( ! $post_id ) { return $parts; } 98 99 $manual = $this->get_manual_meta( (int) $post_id ); 100 if ( '' === $manual['title'] ) { return $parts; } 101 102 $parts['title'] = $manual['title']; 103 return $parts; 104 } 105 106 /** 107 * Output meta description tag using manual meta description if present. 108 */ 109 public function output_manual_meta_description() { 110 if ( ! is_singular() ) { return; } 111 112 $post_id = get_queried_object_id(); 113 if ( ! $post_id ) { return; } 114 115 $manual = $this->get_manual_meta( (int) $post_id ); 116 if ( '' === $manual['description'] ) { return; } 117 118 // Allow other integrations to disable this output if desired. 119 if ( false === apply_filters( 'ait_aipseo_output_manual_meta_description', true, $post_id ) ) { 120 return; 121 } 122 123 echo "\n" . '<meta name="description" content="' . esc_attr( $manual['description'] ) . '" />' . "\n"; 124 } 51 125 } -
ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-rest.php
r3390026 r3448193 30 30 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, 31 31 ] ); 32 33 register_rest_route( 'aipseo/v1', '/internal-links/(?P<id>\d+)', [ 34 'methods' => 'GET', 35 'callback' => [ $this, 'get_internal_link_suggestions' ], 36 'permission_callback' => function() { 37 return current_user_can( 'edit_posts' ); 38 }, 39 ] ); 32 40 } 33 41 … … 59 67 return rest_ensure_response( [ 60 68 'post_id' => $post_id, 61 'items' => array_values( array_map( ' sanitize_text_field', $items ) ),69 'items' => array_values( array_map( 'wp_kses_post', $items ) ), 62 70 ] ); 63 71 } … … 77 85 return rest_ensure_response( [ 78 86 'post_id' => $post_id, 79 'items' => array_values( array_map( 'sanitize_text_field', $items ) ), 80 ] ); 81 } 87 'items' => array_values( array_map( 'wp_kses_post', $items ) ), 88 ] ); 89 } 90 91 /** 92 * Suggest internal link opportunities based on the post content and other site posts. 93 * 94 * Returns objects like: 95 * [ 'anchor' => 'iPad', 'title' => 'iPad Accessories', 'url' => 'https://example.com/ipad-accessories/' ] 96 */ 97 public function get_internal_link_suggestions( WP_REST_Request $request ) { 98 $post_id = (int) $request['id']; 99 if ( $post_id <= 0 ) { 100 return new WP_Error( 101 'aipseo_bad_id', 102 __( 'Invalid post ID.', 'ai-powered-seo-suggestions' ), 103 [ 'status' => 400 ] 104 ); 105 } 106 107 $post = get_post( $post_id ); 108 if ( ! $post || 'trash' === $post->post_status ) { 109 return rest_ensure_response( [ 110 'post_id' => $post_id, 111 'items' => [], 112 ] ); 113 } 114 115 // Strip tags / shortcodes for a simple text search. 116 $content = wp_strip_all_tags( $post->post_content ); 117 $content_lc = mb_strtolower( $content ); 118 119 // Fetch a reasonable pool of internal targets. 120 $query = new WP_Query( [ 121 'post_type' => 'any', 122 'post_status' => 'publish', 123 'posts_per_page' => 200, 124 'post__not_in' => [ $post_id ], 125 'fields' => 'ids', 126 ] ); 127 128 $suggestions = []; 129 if ( $query->have_posts() ) { 130 foreach ( $query->posts as $target_id ) { 131 $title = get_the_title( $target_id ); 132 $slug = get_post_field( 'post_name', $target_id ); 133 134 if ( ! $title && ! $slug ) { 135 continue; 136 } 137 138 $candidates = []; 139 140 // From slug: ipad-pro-11 => [ 'ipad', 'pro', '11' ] 141 if ( $slug ) { 142 foreach ( preg_split( '/[-_]+/', mb_strtolower( $slug ) ) as $part ) { 143 $part = trim( $part ); 144 if ( mb_strlen( $part ) >= 3 ) { 145 $candidates[] = $part; 146 } 147 } 148 } 149 150 // From title words 151 if ( $title ) { 152 foreach ( preg_split( '/\s+/', mb_strtolower( wp_strip_all_tags( $title ) ) ) as $word ) { 153 $word = preg_replace( '/[^\p{L}\p{N}]+/u', '', $word ); 154 if ( mb_strlen( $word ) >= 5 ) { 155 $candidates[] = $word; 156 } 157 } 158 } 159 160 $candidates = array_unique( $candidates ); 161 $matched_anchor = null; 162 163 foreach ( $candidates as $word ) { 164 if ( ! $word ) { 165 continue; 166 } 167 168 // Word-boundary match to avoid weird partials. 169 if ( preg_match( '/\b' . preg_quote( $word, '/' ) . '\b/ui', $content_lc ) ) { 170 $matched_anchor = $word; 171 break; 172 } 173 } 174 175 if ( ! $matched_anchor ) { 176 continue; 177 } 178 179 $suggestions[] = [ 180 'anchor' => ucfirst( $matched_anchor ), 181 'title' => $title, 182 'url' => get_permalink( $target_id ), 183 ]; 184 185 if ( count( $suggestions ) >= 20 ) { 186 break; 187 } 188 } 189 190 return rest_ensure_response( [ 191 'post_id' => $post_id, 192 'items' => array_values( $suggestions ), 193 ] ); 194 } 195 196 /** 197 * Filter internal link suggestions. 198 * 199 * @param array $suggestions Array of [ anchor, title, url ]. 200 * @param int $post_id Current post ID. 201 */ 202 $suggestions = apply_filters( 'ait_aipseo_internal_link_suggestions', $suggestions, $post_id ); 203 204 return rest_ensure_response( [ 205 'post_id' => $post_id, 206 'items' => $suggestions, 207 ] ); 208 } 209 82 210 83 211 private function current_user_has_visible_role(): bool { -
ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-settings.php
r3399255 r3448193 60 60 61 61 // Schema 62 'schema_mode' => ' preview_then_insert',62 'schema_mode' => 'auto_insert', 63 63 'schema_overwrite_existing' => false, 64 64 'schema_minify' => true, … … 301 301 'description' => __('Which OpenAI model should be used to handle the image processing requests?', 'ai-powered-seo-suggestions'), 302 302 'options' => [ 303 'gpt-5.1' => __( 'GPT-5.1', 'ai-powered-seo-suggestions' ), 303 304 'gpt-5' => __( 'GPT-5', 'ai-powered-seo-suggestions' ), 305 'gpt-5-mini' => __( 'GPT-5 Mini', 'ai-powered-seo-suggestions' ), 304 306 'gpt-4.1' => __( 'GPT-4.1', 'ai-powered-seo-suggestions' ), 305 307 'gpt-4o' => __( 'GPT-4o', 'ai-powered-seo-suggestions' ), … … 327 329 'radio', 328 330 [ 329 'id' => 'language', 330 'title' => __( 'Language', 'ai-powered-seo-suggestions' ), 331 'options' => array( 331 'id' => 'language', 332 'title' => __( 'Language', 'ai-powered-seo-suggestions' ), 333 'description' => __( 'Choose how AIPSEO decides which language to use when generating AI content (site language, detected from the content, or a custom language you set below).', 'ai-powered-seo-suggestions' ), 334 'options' => array( 332 335 'site' => __( 'Site Language', 'ai-powered-seo-suggestions' ), 333 336 'content-detected' => __( 'Detect from Content', 'ai-powered-seo-suggestions' ), 334 337 'custom' => __( 'Custom (set below)', 'ai-powered-seo-suggestions' ), 335 338 ), 336 'default' => 'site',339 'default' => 'site', 337 340 ] 338 341 ); … … 599 602 'checkbox', 600 603 [ 601 'id' => 'compat_integrations', 602 'title' => __( 'Integrations', 'ai-powered-seo-suggestions' ), 603 'options' => [ 604 'aio_seo' => __( 'All-In-One SEO', 'ai-powered-seo-suggestions' ), 605 'yoast' => __( 'Yoast SEO', 'ai-powered-seo-suggestions' ), 606 'rankmath' => __( 'Rank Math', 'ai-powered-seo-suggestions' ), 607 'seopress' => __( 'SEOPress', 'ai-powered-seo-suggestions' ), 604 'id' => 'compat_integrations', 605 'title' => __( 'Integrations', 'ai-powered-seo-suggestions' ), 606 'description' => __( 'Select which other SEO plugins AIPSEO should read from and write to, so titles, meta descriptions, and keywords stay in sync across plugins.', 'ai-powered-seo-suggestions' ), 607 'options' => [ 608 'aio_seo' => __( 'All-In-One SEO', 'ai-powered-seo-suggestions' ), 609 'yoast' => __( 'Yoast SEO', 'ai-powered-seo-suggestions' ), 610 'rankmath' => __( 'Rank Math', 'ai-powered-seo-suggestions' ), 611 'seopress' => __( 'SEOPress', 'ai-powered-seo-suggestions' ), 608 612 ], 609 'default' => self::$defaults['compat_integrations'],613 'default' => self::$defaults['compat_integrations'], 610 614 ] 611 615 ); -
ai-powered-seo-suggestions/trunk/readme.txt
r3408444 r3448193 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1. 0.17 Stable tag: 1.1.0 8 8 License: GPLv3 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 91 91 == Changelog == 92 92 93 = 1.1.0 (2026-01-27) = 94 - Overhauled WooCommerce integration, so suggestions/fix-it tips for products are now much more WooCommerce-specific, and so you can now make use of manual features on the product edit screen, like specifying the meta title, meta description and target keywords. 95 - Added new internal link suggestions that find posts and pages related to the content of the post you're editing and suggest links to them. 96 - You can now set a default schema type for each activated post type on your site (including the default ones as well as custom ones). 97 - Updates to have more detailed suggestions for featured images and alt text. 98 - Added suggestions regarding nofollow and noindex for custom post type (posts). 99 - Fixed issue with suggestions still showing for post types that weren't selected on the general settings page. 100 93 101 = 1.0.1 (2025-11-19) = 94 102 * Update to make sure the minimum, maximum and step values, for number inputs on the settings pages that require them, are correctly implemented.
Note: See TracChangeset
for help on using the changeset viewer.