Plugin Directory

Changeset 3448193


Ignore:
Timestamp:
01/27/2026 09:19:27 PM (3 weeks ago)
Author:
Rustaurius
Message:

v1.1.0 released and tagged

Location:
ai-powered-seo-suggestions
Files:
22 edited
1 copied

Legend:

Unmodified
Added
Removed
  • ai-powered-seo-suggestions/tags/1.1.0/ai-powered-seo-suggestions.php

    r3399255 r3448193  
    44 * Plugin URI:        https://www.wpaiplugins.dev/wordpress-ai-powered-seo-plugin/
    55 * 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.1
     6 * Version:           1.1.0
    77 * Author:            AI Tools
    88 * Text Domain:       ai-powered-seo-suggestions
     
    1414
    1515// Define plugin constants
    16 define( 'AIT_AIPSEO_VERSION', '1.0.1' );
     16define( 'AIT_AIPSEO_VERSION', '1.1.0' );
    1717define( 'AIT_AIPSEO_FILE', __FILE__ );
    1818define( 'AIT_AIPSEO_PATH', plugin_dir_path( __FILE__ ) );
  • ai-powered-seo-suggestions/tags/1.1.0/assets/css/ait-aipseo-editor.css

    r3390026 r3448193  
    66.ait-aipseo-muted { color: #6b7280; }
    77.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  
    4949    const [schemaPreview, setSchemaPreview] = useState('');
    5050    const [schemaOpen, setSchemaOpen] = useState(false);
    51 
     51   
     52    const [links, setLinks] = useState([]);
     53    const [loadingLinks, setLoadingLinks] = useState(false);
     54    const [linksError, setLinksError] = useState("");
     55   
    5256    const postId      = select('core/editor')?.getCurrentPostId();
     57    const postType    = select('core/editor')?.getCurrentPostType ? select('core/editor').getCurrentPostType() : null;
    5358    const nonce       = AIT_AIPSEO_EDITOR?.nonce;
    5459    const pro         = AIT_AIPSEO_EDITOR?.pro;
     
    5863    const kwURL       = (ai.keywords  || (base.replace(/\/+$/,'') + '/ai/keywords'));
    5964    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';
    6068
    6169    function load_kws(){
     
    7381        .then((f) => { setFx(f || []); })
    7482        .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));
    7598    }
    7699
     
    234257      // ------- AI Keyword Hints -------
    235258      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        ),
    237263        (kw && kw.length)
    238264          ? h('ul', { className: 'ait-aipseo-ai-list' }, kw.map((t,i)=> h('li', { key: i }, t )))
     
    242268        ),
    243269        h('hr'),
    244         h('h3', {}, 'AI Fix-It Tips (Lite)'),
     270        h('h3', {}, 'AI Fix-It Tips ' +  ( ! pro ? '(Lite)' : '' ) ),
    245271        (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            ))
    247278          : h('p', { className: 'ait-aipseo-muted' }, 'No AI tips yet.'),
    248279        h('div', { className: 'ait-aipseo-actions' },
     
    279310        placeholder: 'e.g. ai seo, wordpress seo, meta description'
    280311      }),
     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      ),
    281339
    282340      // ------- Schema Assistant -------
     
    394452          className: 'ait-aipseo-schema-modal',
    395453        },
    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          }),
    405461          h('div', { className: 'ait-aipseo-meta-footer' },
    406462            h('button', {
  • ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-ai-fixit.php

    r3390026 r3448193  
    1818        $signals = $this->ensure_signals( $post_id );
    1919        $kws     = AIT_AIPSEO_Keyword_Density::get_target_keywords( $post_id );
     20        $post_type = get_post_type( $post_id );
    2021
    2122        $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'),
    2528        ];
    2629        $hash = md5( wp_json_encode( $context ) );
     
    4043
    4144        $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
    4257        update_post_meta( $post_id, '_ait_aipseo_ai_fixit_tips', [
    4358            'hash' => $hash,
     
    4661        ] );
    4762
    48         return array_slice( $data, 0, max( 1, $limit ) );
     63        return array_slice( $data, 0, max( 1, $limit ) ); 
    4964    }
    5065
     
    7489
    7590        $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            )
    7795        );
    7896
  • ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-ai-keywords.php

    r3390026 r3448193  
    6363            'headings' => array_slice( array_values( array_filter( array_map( 'trim', $headings ) ) ), 0, 10 ),
    6464            'keywords' => array_slice( $kws, 0, 5 ),
     65            'time'     => date('Y-m-d H:i'),
    6566        ];
    6667    }
  • ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-editor-classic.php

    r3390026 r3448193  
    3838        $desc  = get_post_meta( $post->ID, 'ait_aipseo_meta_description', true );
    3939
     40        $pro = (bool) apply_filters('ait_aipseo/can_pro', false);
     41
    4042        wp_nonce_field( 'ait_aipseo_meta', 'ait_aipseo_meta_nonce' );
    4143        ?>
     
    5254        <hr />
    5355
    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>
    5557        <?php
    5658        $kw_items = apply_filters( 'ait_aipseo_semantic_keywords', [], (int) $post->ID );
     
    6567        <?php endif; ?>
    6668       
    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>
    6870        <?php
    6971        $fix_items = apply_filters( 'ait_aipseo_fixit_tips', [], (int) $post->ID );
     
    7173            <ol class="ait-aipseo-ai-list ait-aipseo-ai-fixit">
    7274                <?php foreach ( $fix_items as $tip ) : ?>
    73                     <li><?php echo esc_html( (string) $tip ); ?></li>
     75                    <li><?php echo wp_kses_post( $tip ); ?></li>
    7476                <?php endforeach; ?>
    7577            </ol>
  • ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-editor-gutenberg.php

    r3390026 r3448193  
    1212    public function enqueue() {
    1313        $hide_gutenberg = AIT_AIPSEO_Settings::get('hide_gutenberg');
     14        $post_types = (array) AIT_AIPSEO_Settings::get('post_types');
    1415       
    1516        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        }
    1623
    1724        wp_enqueue_style( 'ait-aipseo-editor', AIT_AIPSEO_URL . 'assets/css/ait-aipseo-editor.css', [], AIT_AIPSEO_VERSION );
     
    2734                'keywords'  => $rest_base . '/ai/keywords',
    2835                'fixit'     => $rest_base . '/ai/fixit',
     36                'internal'  => $rest_base . '/internal-links',
    2937                'seo_title' => $rest_base . '/ai/seo_title',
    3038                'meta_desc' => $rest_base . '/ai/meta_desc',
  • ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-meta-suggestions.php

    r3390026 r3448193  
    88        // Provide filters that editor UI/REST can use
    99        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 );
    1015    }
     16
    1117
    1218    public function suggest_for_post( $suggestions, $post_id ) {
     
    4955        return rtrim( $desc, '. ' ) . '. ' . __( 'Learn more.', 'ai-powered-seo-suggestions' );
    5056    }
     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    }
    51125}
  • ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-rest.php

    r3390026 r3448193  
    3030            'permission_callback' => function() { return current_user_can( 'edit_posts' ); },
    3131        ] );
     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        ] );
    3240    }
    3341
     
    5967        return rest_ensure_response( [
    6068            'post_id' => $post_id,
    61             'items'   => array_values( array_map( 'sanitize_text_field', $items ) ),
     69            'items'   => array_values( array_map( 'wp_kses_post', $items ) ),
    6270        ] );
    6371    }
     
    7785        return rest_ensure_response( [
    7886            '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
    82210
    83211    private function current_user_has_visible_role(): bool {
  • ai-powered-seo-suggestions/tags/1.1.0/includes/class-ait-aipseo-settings.php

    r3399255 r3448193  
    6060
    6161        // Schema
    62         'schema_mode'                 => 'preview_then_insert',
     62        'schema_mode'                 => 'auto_insert',
    6363        'schema_overwrite_existing'   => false,
    6464        'schema_minify'               => true,
     
    301301                'description'   => __('Which OpenAI model should be used to handle the image processing requests?', 'ai-powered-seo-suggestions'),
    302302                'options'       => [
     303                    'gpt-5.1'        => __( 'GPT-5.1', 'ai-powered-seo-suggestions' ),
    303304                    'gpt-5'          => __( 'GPT-5', 'ai-powered-seo-suggestions' ),
     305                    'gpt-5-mini'     => __( 'GPT-5 Mini', 'ai-powered-seo-suggestions' ),
    304306                    'gpt-4.1'        => __( 'GPT-4.1', 'ai-powered-seo-suggestions' ),
    305307                    'gpt-4o'         => __( 'GPT-4o', 'ai-powered-seo-suggestions' ),
     
    327329            'radio',
    328330            [
    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(
    332335                    'site'             => __( 'Site Language', 'ai-powered-seo-suggestions' ),
    333336                    'content-detected' => __( 'Detect from Content', 'ai-powered-seo-suggestions' ),
    334337                    'custom'           => __( 'Custom (set below)', 'ai-powered-seo-suggestions' ),
    335338                ),
    336                 'default' => 'site',
     339                'default'       => 'site',
    337340            ]
    338341        );
     
    599602            'checkbox',
    600603            [
    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' ),
    608612                ],
    609                 'default'  => self::$defaults['compat_integrations'],
     613                'default'       => self::$defaults['compat_integrations'],
    610614            ]
    611615        );
  • ai-powered-seo-suggestions/tags/1.1.0/readme.txt

    r3408444 r3448193  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.1
     7Stable tag: 1.1.0
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    9191== Changelog ==
    9292
     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
    93101= 1.0.1 (2025-11-19) =
    94102* 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  
    44 * Plugin URI:        https://www.wpaiplugins.dev/wordpress-ai-powered-seo-plugin/
    55 * 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.1
     6 * Version:           1.1.0
    77 * Author:            AI Tools
    88 * Text Domain:       ai-powered-seo-suggestions
     
    1414
    1515// Define plugin constants
    16 define( 'AIT_AIPSEO_VERSION', '1.0.1' );
     16define( 'AIT_AIPSEO_VERSION', '1.1.0' );
    1717define( 'AIT_AIPSEO_FILE', __FILE__ );
    1818define( 'AIT_AIPSEO_PATH', plugin_dir_path( __FILE__ ) );
  • ai-powered-seo-suggestions/trunk/assets/css/ait-aipseo-editor.css

    r3390026 r3448193  
    66.ait-aipseo-muted { color: #6b7280; }
    77.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  
    4949    const [schemaPreview, setSchemaPreview] = useState('');
    5050    const [schemaOpen, setSchemaOpen] = useState(false);
    51 
     51   
     52    const [links, setLinks] = useState([]);
     53    const [loadingLinks, setLoadingLinks] = useState(false);
     54    const [linksError, setLinksError] = useState("");
     55   
    5256    const postId      = select('core/editor')?.getCurrentPostId();
     57    const postType    = select('core/editor')?.getCurrentPostType ? select('core/editor').getCurrentPostType() : null;
    5358    const nonce       = AIT_AIPSEO_EDITOR?.nonce;
    5459    const pro         = AIT_AIPSEO_EDITOR?.pro;
     
    5863    const kwURL       = (ai.keywords  || (base.replace(/\/+$/,'') + '/ai/keywords'));
    5964    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';
    6068
    6169    function load_kws(){
     
    7381        .then((f) => { setFx(f || []); })
    7482        .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));
    7598    }
    7699
     
    234257      // ------- AI Keyword Hints -------
    235258      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        ),
    237263        (kw && kw.length)
    238264          ? h('ul', { className: 'ait-aipseo-ai-list' }, kw.map((t,i)=> h('li', { key: i }, t )))
     
    242268        ),
    243269        h('hr'),
    244         h('h3', {}, 'AI Fix-It Tips (Lite)'),
     270        h('h3', {}, 'AI Fix-It Tips ' +  ( ! pro ? '(Lite)' : '' ) ),
    245271        (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            ))
    247278          : h('p', { className: 'ait-aipseo-muted' }, 'No AI tips yet.'),
    248279        h('div', { className: 'ait-aipseo-actions' },
     
    279310        placeholder: 'e.g. ai seo, wordpress seo, meta description'
    280311      }),
     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      ),
    281339
    282340      // ------- Schema Assistant -------
     
    394452          className: 'ait-aipseo-schema-modal',
    395453        },
    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          }),
    405461          h('div', { className: 'ait-aipseo-meta-footer' },
    406462            h('button', {
  • ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-ai-fixit.php

    r3390026 r3448193  
    1818        $signals = $this->ensure_signals( $post_id );
    1919        $kws     = AIT_AIPSEO_Keyword_Density::get_target_keywords( $post_id );
     20        $post_type = get_post_type( $post_id );
    2021
    2122        $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'),
    2528        ];
    2629        $hash = md5( wp_json_encode( $context ) );
     
    4043
    4144        $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
    4257        update_post_meta( $post_id, '_ait_aipseo_ai_fixit_tips', [
    4358            'hash' => $hash,
     
    4661        ] );
    4762
    48         return array_slice( $data, 0, max( 1, $limit ) );
     63        return array_slice( $data, 0, max( 1, $limit ) ); 
    4964    }
    5065
     
    7489
    7590        $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            )
    7795        );
    7896
  • ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-ai-keywords.php

    r3390026 r3448193  
    6363            'headings' => array_slice( array_values( array_filter( array_map( 'trim', $headings ) ) ), 0, 10 ),
    6464            'keywords' => array_slice( $kws, 0, 5 ),
     65            'time'     => date('Y-m-d H:i'),
    6566        ];
    6667    }
  • ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-editor-classic.php

    r3390026 r3448193  
    3838        $desc  = get_post_meta( $post->ID, 'ait_aipseo_meta_description', true );
    3939
     40        $pro = (bool) apply_filters('ait_aipseo/can_pro', false);
     41
    4042        wp_nonce_field( 'ait_aipseo_meta', 'ait_aipseo_meta_nonce' );
    4143        ?>
     
    5254        <hr />
    5355
    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>
    5557        <?php
    5658        $kw_items = apply_filters( 'ait_aipseo_semantic_keywords', [], (int) $post->ID );
     
    6567        <?php endif; ?>
    6668       
    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>
    6870        <?php
    6971        $fix_items = apply_filters( 'ait_aipseo_fixit_tips', [], (int) $post->ID );
     
    7173            <ol class="ait-aipseo-ai-list ait-aipseo-ai-fixit">
    7274                <?php foreach ( $fix_items as $tip ) : ?>
    73                     <li><?php echo esc_html( (string) $tip ); ?></li>
     75                    <li><?php echo wp_kses_post( $tip ); ?></li>
    7476                <?php endforeach; ?>
    7577            </ol>
  • ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-editor-gutenberg.php

    r3390026 r3448193  
    1212    public function enqueue() {
    1313        $hide_gutenberg = AIT_AIPSEO_Settings::get('hide_gutenberg');
     14        $post_types = (array) AIT_AIPSEO_Settings::get('post_types');
    1415       
    1516        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        }
    1623
    1724        wp_enqueue_style( 'ait-aipseo-editor', AIT_AIPSEO_URL . 'assets/css/ait-aipseo-editor.css', [], AIT_AIPSEO_VERSION );
     
    2734                'keywords'  => $rest_base . '/ai/keywords',
    2835                'fixit'     => $rest_base . '/ai/fixit',
     36                'internal'  => $rest_base . '/internal-links',
    2937                'seo_title' => $rest_base . '/ai/seo_title',
    3038                'meta_desc' => $rest_base . '/ai/meta_desc',
  • ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-meta-suggestions.php

    r3390026 r3448193  
    88        // Provide filters that editor UI/REST can use
    99        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 );
    1015    }
     16
    1117
    1218    public function suggest_for_post( $suggestions, $post_id ) {
     
    4955        return rtrim( $desc, '. ' ) . '. ' . __( 'Learn more.', 'ai-powered-seo-suggestions' );
    5056    }
     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    }
    51125}
  • ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-rest.php

    r3390026 r3448193  
    3030            'permission_callback' => function() { return current_user_can( 'edit_posts' ); },
    3131        ] );
     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        ] );
    3240    }
    3341
     
    5967        return rest_ensure_response( [
    6068            'post_id' => $post_id,
    61             'items'   => array_values( array_map( 'sanitize_text_field', $items ) ),
     69            'items'   => array_values( array_map( 'wp_kses_post', $items ) ),
    6270        ] );
    6371    }
     
    7785        return rest_ensure_response( [
    7886            '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
    82210
    83211    private function current_user_has_visible_role(): bool {
  • ai-powered-seo-suggestions/trunk/includes/class-ait-aipseo-settings.php

    r3399255 r3448193  
    6060
    6161        // Schema
    62         'schema_mode'                 => 'preview_then_insert',
     62        'schema_mode'                 => 'auto_insert',
    6363        'schema_overwrite_existing'   => false,
    6464        'schema_minify'               => true,
     
    301301                'description'   => __('Which OpenAI model should be used to handle the image processing requests?', 'ai-powered-seo-suggestions'),
    302302                'options'       => [
     303                    'gpt-5.1'        => __( 'GPT-5.1', 'ai-powered-seo-suggestions' ),
    303304                    'gpt-5'          => __( 'GPT-5', 'ai-powered-seo-suggestions' ),
     305                    'gpt-5-mini'     => __( 'GPT-5 Mini', 'ai-powered-seo-suggestions' ),
    304306                    'gpt-4.1'        => __( 'GPT-4.1', 'ai-powered-seo-suggestions' ),
    305307                    'gpt-4o'         => __( 'GPT-4o', 'ai-powered-seo-suggestions' ),
     
    327329            'radio',
    328330            [
    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(
    332335                    'site'             => __( 'Site Language', 'ai-powered-seo-suggestions' ),
    333336                    'content-detected' => __( 'Detect from Content', 'ai-powered-seo-suggestions' ),
    334337                    'custom'           => __( 'Custom (set below)', 'ai-powered-seo-suggestions' ),
    335338                ),
    336                 'default' => 'site',
     339                'default'       => 'site',
    337340            ]
    338341        );
     
    599602            'checkbox',
    600603            [
    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' ),
    608612                ],
    609                 'default'  => self::$defaults['compat_integrations'],
     613                'default'       => self::$defaults['compat_integrations'],
    610614            ]
    611615        );
  • ai-powered-seo-suggestions/trunk/readme.txt

    r3408444 r3448193  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.0.1
     7Stable tag: 1.1.0
    88License: GPLv3 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    9191== Changelog ==
    9292
     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
    93101= 1.0.1 (2025-11-19) =
    94102* 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.