Plugin Directory

Changeset 3467961


Ignore:
Timestamp:
02/23/2026 07:14:25 PM (5 weeks ago)
Author:
instarank
Message:

Release 2.0.5 - Hierarchical post type URLs, search API, word count, noindex/nofollow detection, SaaS settings migration

Location:
instarank
Files:
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • instarank/tags/2.0.5/api/endpoints.php

    r3427377 r3467961  
    989989        $limit = intval($request->get_param('limit') ?? 100);
    990990        $offset = intval($request->get_param('offset') ?? 0);
     991        $search = $request->get_param('search');
    991992
    992993        $args = [
     
    9991000        ];
    10001001
     1002        if (!empty($search)) {
     1003            $args['s'] = sanitize_text_field($search);
     1004        }
     1005
    10011006        $query = new WP_Query($args);
    10021007        $posts = [];
     
    10041009        foreach ($query->posts as $post) {
    10051010            $meta = $detector->get_post_meta($post->ID);
     1011            $word_count = str_word_count(wp_strip_all_tags($post->post_content));
     1012
     1013            // Detect noindex/nofollow from SEO plugin meta
     1014            $noindex = false;
     1015            $nofollow = false;
     1016            if (!empty($meta['robots'])) {
     1017                $robots = is_array($meta['robots']) ? implode(',', $meta['robots']) : $meta['robots'];
     1018                $noindex = stripos($robots, 'noindex') !== false;
     1019                $nofollow = stripos($robots, 'nofollow') !== false;
     1020            }
     1021            // Also check individual meta flags if set by SEO detector
     1022            if (isset($meta['noindex'])) $noindex = (bool)$meta['noindex'];
     1023            if (isset($meta['nofollow'])) $nofollow = (bool)$meta['nofollow'];
    10061024
    10071025            $posts[] = [
     
    10141032                'modified' => $post->post_modified_gmt,
    10151033                'author' => get_the_author_meta('display_name', $post->post_author),
     1034                'word_count' => $word_count,
    10161035                'seo' => [
    10171036                    'meta_title' => $meta['title'] ?? '',
    10181037                    'meta_description' => $meta['description'] ?? '',
    1019                     'focus_keyword' => $meta['focus_keyword'] ?? ''
     1038                    'focus_keyword' => $meta['focus_keyword'] ?? '',
     1039                    'noindex' => $noindex,
     1040                    'nofollow' => $nofollow
    10201041                ]
    10211042            ];
     
    20222043        // Handle parent page (for hierarchical post types like pages)
    20232044        $parent_id = isset($params['parent']) ? intval($params['parent']) : 0;
     2045        // Parent slug for hierarchical post type URL resolution
     2046        $parent_slug_value = isset($params['parent_slug']) ? sanitize_title($params['parent_slug']) : '';
    20242047
    20252048        $post_data = [
     
    20662089                ['status' => 500]
    20672090            );
     2091        }
     2092
     2093        // Save parent slug meta for hierarchical post type URL resolution
     2094        if (!empty($parent_slug_value)) {
     2095            update_post_meta($post_id, '_instarank_parent_slug', $parent_slug_value);
    20682096        }
    20692097
  • instarank/tags/2.0.5/instarank.php

    r3427377 r3467961  
    44 * Plugin URI: https://instarank.com/wordpress-plugin
    55 * Description: Connect your WordPress site to InstaRank for AI-powered SEO optimization, schema markup generation, and programmatic SEO. Create and sync custom post types, automatically apply SEO improvements, and generate structured data with InstaRank's AI engine.
    6  * Version: 2.0.4
     6 * Version: 2.0.5
    77 * Author: InstaRank
    88 * Author URI: https://instarank.com
     
    1818
    1919// Define plugin constants
    20 define('INSTARANK_VERSION', '2.0.4');
     20define('INSTARANK_VERSION', '2.0.5');
    2121define('INSTARANK_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2222define('INSTARANK_PLUGIN_URL', plugin_dir_url(__FILE__));
     
    11121112    foreach ($stored_post_types as $slug => $data) {
    11131113        if (isset($data['config']) && !post_type_exists($slug)) {
    1114             register_post_type($slug, $data['config']);
     1114            $config = $data['config'];
     1115
     1116            // Handle parent post type rewrite chains
     1117            // If config has parent_post_type, set up rewrite with parent slug token
     1118            if (!empty($config['parent_post_type'])) {
     1119                $parent_slug = $config['parent_post_type'];
     1120                // Add a rewrite tag for the parent slug so WordPress can parse it
     1121                add_rewrite_tag('%' . $parent_slug . '%', '([^/]+)');
     1122            }
     1123
     1124            register_post_type($slug, $config);
     1125        }
     1126    }
     1127
     1128    // Add custom rewrite rules for parent-child post type chains
     1129    foreach ($stored_post_types as $slug => $data) {
     1130        if (!empty($data['config']['parent_post_type'])) {
     1131            $config = $data['config'];
     1132            $parent_slug = $config['parent_post_type'];
     1133            $rewrite_slug = isset($config['rewrite']['slug']) ? $config['rewrite']['slug'] : $slug;
     1134
     1135            // Check if the rewrite slug contains the parent token pattern
     1136            if (strpos($rewrite_slug, '%' . $parent_slug . '%') !== false) {
     1137                // Build regex from the rewrite slug by replacing the token
     1138                $regex_base = str_replace('%' . $parent_slug . '%', '([^/]+)', preg_quote($rewrite_slug, '#'));
     1139
     1140                // Single post: /{parent_slug}/{parent-item}/{child_slug}/{child-item}/
     1141                add_rewrite_rule(
     1142                    '^' . $regex_base . '/([^/]+)/?$',
     1143                    'index.php?post_type=' . $slug . '&' . $slug . '=$matches[2]&' . $parent_slug . '=$matches[1]',
     1144                    'top'
     1145                );
     1146
     1147                // Pagination: /{parent_slug}/{parent-item}/{child_slug}/{child-item}/page/2/
     1148                add_rewrite_rule(
     1149                    '^' . $regex_base . '/([^/]+)/page/([0-9]+)/?$',
     1150                    'index.php?post_type=' . $slug . '&' . $slug . '=$matches[2]&' . $parent_slug . '=$matches[1]&paged=$matches[3]',
     1151                    'top'
     1152                );
     1153
     1154                // Archive: /{parent_slug}/{parent-item}/{child_slug}/
     1155                add_rewrite_rule(
     1156                    '^' . $regex_base . '/?$',
     1157                    'index.php?post_type=' . $slug . '&' . $parent_slug . '=$matches[1]',
     1158                    'top'
     1159                );
     1160            }
    11151161        }
    11161162    }
    11171163}, 5); // Priority 5 to run before default post types
     1164
     1165/**
     1166 * Add parent post type query vars so WordPress recognizes them
     1167 */
     1168add_filter('query_vars', function($vars) {
     1169    $stored_post_types = get_option('instarank_custom_post_types', []);
     1170    foreach ($stored_post_types as $slug => $data) {
     1171        if (!empty($data['config']['parent_post_type'])) {
     1172            $parent_slug = $data['config']['parent_post_type'];
     1173            if (!in_array($parent_slug, $vars)) {
     1174                $vars[] = $parent_slug;
     1175            }
     1176        }
     1177    }
     1178    return $vars;
     1179});
     1180
     1181/**
     1182 * Filter post type link to replace parent slug token in URLs
     1183 */
     1184add_filter('post_type_link', function($post_link, $post) {
     1185    $stored_post_types = get_option('instarank_custom_post_types', []);
     1186
     1187    if (isset($stored_post_types[$post->post_type])) {
     1188        $config = $stored_post_types[$post->post_type]['config'];
     1189
     1190        if (!empty($config['parent_post_type'])) {
     1191            $parent_slug = $config['parent_post_type'];
     1192            $token = '%' . $parent_slug . '%';
     1193
     1194            if (strpos($post_link, $token) !== false) {
     1195                // Find the parent post via post meta or parent resolution
     1196                $parent_value = get_post_meta($post->ID, '_instarank_parent_slug', true);
     1197
     1198                if (!$parent_value && $post->post_parent) {
     1199                    // Try to get parent post's slug
     1200                    $parent_post = get_post($post->post_parent);
     1201                    if ($parent_post) {
     1202                        $parent_value = $parent_post->post_name;
     1203                    }
     1204                }
     1205
     1206                // Fallback: try to find parent value from resolution config meta
     1207                if (!$parent_value) {
     1208                    $parent_config = isset($config['parent_resolution_config']) ? $config['parent_resolution_config'] : null;
     1209                    if ($parent_config && !empty($parent_config['column_name'])) {
     1210                        $col = $parent_config['column_name'];
     1211                        $val = get_post_meta($post->ID, $col, true);
     1212                        if ($val) {
     1213                            $parent_value = sanitize_title($val);
     1214                        }
     1215                    }
     1216                }
     1217
     1218                // Final fallback: use "uncategorized" to avoid duplicating the post type slug in the URL
     1219                if (!$parent_value) {
     1220                    $parent_value = 'uncategorized';
     1221                }
     1222
     1223                $post_link = str_replace($token, $parent_value, $post_link);
     1224            }
     1225        }
     1226    }
     1227
     1228    return $post_link;
     1229}, 10, 2);
    11181230
    11191231/**
  • instarank/tags/2.0.5/readme.txt

    r3427377 r3467961  
    44Requires at least: 5.6
    55Tested up to: 6.9
    6 Stable tag: 2.0.4
     6Stable tag: 2.0.5
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    169169
    170170== Changelog ==
     171
     172= 2.0.5 =
     173* Feature: Hierarchical post type URL support with parent-child rewrite rules
     174* Feature: Search parameter for post listing API endpoint
     175* Feature: Word count and noindex/nofollow detection in post SEO data
     176* Feature: Parent slug resolution for custom post type URL chains
     177* Enhancement: Moved robots.txt, sitemap, and LLMs.txt settings management to SaaS dashboard
     178* Enhancement: Improved agent retry handling and error recovery
    171179
    172180= 2.0.4 =
  • instarank/trunk/api/endpoints.php

    r3427377 r3467961  
    989989        $limit = intval($request->get_param('limit') ?? 100);
    990990        $offset = intval($request->get_param('offset') ?? 0);
     991        $search = $request->get_param('search');
    991992
    992993        $args = [
     
    9991000        ];
    10001001
     1002        if (!empty($search)) {
     1003            $args['s'] = sanitize_text_field($search);
     1004        }
     1005
    10011006        $query = new WP_Query($args);
    10021007        $posts = [];
     
    10041009        foreach ($query->posts as $post) {
    10051010            $meta = $detector->get_post_meta($post->ID);
     1011            $word_count = str_word_count(wp_strip_all_tags($post->post_content));
     1012
     1013            // Detect noindex/nofollow from SEO plugin meta
     1014            $noindex = false;
     1015            $nofollow = false;
     1016            if (!empty($meta['robots'])) {
     1017                $robots = is_array($meta['robots']) ? implode(',', $meta['robots']) : $meta['robots'];
     1018                $noindex = stripos($robots, 'noindex') !== false;
     1019                $nofollow = stripos($robots, 'nofollow') !== false;
     1020            }
     1021            // Also check individual meta flags if set by SEO detector
     1022            if (isset($meta['noindex'])) $noindex = (bool)$meta['noindex'];
     1023            if (isset($meta['nofollow'])) $nofollow = (bool)$meta['nofollow'];
    10061024
    10071025            $posts[] = [
     
    10141032                'modified' => $post->post_modified_gmt,
    10151033                'author' => get_the_author_meta('display_name', $post->post_author),
     1034                'word_count' => $word_count,
    10161035                'seo' => [
    10171036                    'meta_title' => $meta['title'] ?? '',
    10181037                    'meta_description' => $meta['description'] ?? '',
    1019                     'focus_keyword' => $meta['focus_keyword'] ?? ''
     1038                    'focus_keyword' => $meta['focus_keyword'] ?? '',
     1039                    'noindex' => $noindex,
     1040                    'nofollow' => $nofollow
    10201041                ]
    10211042            ];
     
    20222043        // Handle parent page (for hierarchical post types like pages)
    20232044        $parent_id = isset($params['parent']) ? intval($params['parent']) : 0;
     2045        // Parent slug for hierarchical post type URL resolution
     2046        $parent_slug_value = isset($params['parent_slug']) ? sanitize_title($params['parent_slug']) : '';
    20242047
    20252048        $post_data = [
     
    20662089                ['status' => 500]
    20672090            );
     2091        }
     2092
     2093        // Save parent slug meta for hierarchical post type URL resolution
     2094        if (!empty($parent_slug_value)) {
     2095            update_post_meta($post_id, '_instarank_parent_slug', $parent_slug_value);
    20682096        }
    20692097
  • instarank/trunk/instarank.php

    r3427377 r3467961  
    44 * Plugin URI: https://instarank.com/wordpress-plugin
    55 * Description: Connect your WordPress site to InstaRank for AI-powered SEO optimization, schema markup generation, and programmatic SEO. Create and sync custom post types, automatically apply SEO improvements, and generate structured data with InstaRank's AI engine.
    6  * Version: 2.0.4
     6 * Version: 2.0.5
    77 * Author: InstaRank
    88 * Author URI: https://instarank.com
     
    1818
    1919// Define plugin constants
    20 define('INSTARANK_VERSION', '2.0.4');
     20define('INSTARANK_VERSION', '2.0.5');
    2121define('INSTARANK_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2222define('INSTARANK_PLUGIN_URL', plugin_dir_url(__FILE__));
     
    11121112    foreach ($stored_post_types as $slug => $data) {
    11131113        if (isset($data['config']) && !post_type_exists($slug)) {
    1114             register_post_type($slug, $data['config']);
     1114            $config = $data['config'];
     1115
     1116            // Handle parent post type rewrite chains
     1117            // If config has parent_post_type, set up rewrite with parent slug token
     1118            if (!empty($config['parent_post_type'])) {
     1119                $parent_slug = $config['parent_post_type'];
     1120                // Add a rewrite tag for the parent slug so WordPress can parse it
     1121                add_rewrite_tag('%' . $parent_slug . '%', '([^/]+)');
     1122            }
     1123
     1124            register_post_type($slug, $config);
     1125        }
     1126    }
     1127
     1128    // Add custom rewrite rules for parent-child post type chains
     1129    foreach ($stored_post_types as $slug => $data) {
     1130        if (!empty($data['config']['parent_post_type'])) {
     1131            $config = $data['config'];
     1132            $parent_slug = $config['parent_post_type'];
     1133            $rewrite_slug = isset($config['rewrite']['slug']) ? $config['rewrite']['slug'] : $slug;
     1134
     1135            // Check if the rewrite slug contains the parent token pattern
     1136            if (strpos($rewrite_slug, '%' . $parent_slug . '%') !== false) {
     1137                // Build regex from the rewrite slug by replacing the token
     1138                $regex_base = str_replace('%' . $parent_slug . '%', '([^/]+)', preg_quote($rewrite_slug, '#'));
     1139
     1140                // Single post: /{parent_slug}/{parent-item}/{child_slug}/{child-item}/
     1141                add_rewrite_rule(
     1142                    '^' . $regex_base . '/([^/]+)/?$',
     1143                    'index.php?post_type=' . $slug . '&' . $slug . '=$matches[2]&' . $parent_slug . '=$matches[1]',
     1144                    'top'
     1145                );
     1146
     1147                // Pagination: /{parent_slug}/{parent-item}/{child_slug}/{child-item}/page/2/
     1148                add_rewrite_rule(
     1149                    '^' . $regex_base . '/([^/]+)/page/([0-9]+)/?$',
     1150                    'index.php?post_type=' . $slug . '&' . $slug . '=$matches[2]&' . $parent_slug . '=$matches[1]&paged=$matches[3]',
     1151                    'top'
     1152                );
     1153
     1154                // Archive: /{parent_slug}/{parent-item}/{child_slug}/
     1155                add_rewrite_rule(
     1156                    '^' . $regex_base . '/?$',
     1157                    'index.php?post_type=' . $slug . '&' . $parent_slug . '=$matches[1]',
     1158                    'top'
     1159                );
     1160            }
    11151161        }
    11161162    }
    11171163}, 5); // Priority 5 to run before default post types
     1164
     1165/**
     1166 * Add parent post type query vars so WordPress recognizes them
     1167 */
     1168add_filter('query_vars', function($vars) {
     1169    $stored_post_types = get_option('instarank_custom_post_types', []);
     1170    foreach ($stored_post_types as $slug => $data) {
     1171        if (!empty($data['config']['parent_post_type'])) {
     1172            $parent_slug = $data['config']['parent_post_type'];
     1173            if (!in_array($parent_slug, $vars)) {
     1174                $vars[] = $parent_slug;
     1175            }
     1176        }
     1177    }
     1178    return $vars;
     1179});
     1180
     1181/**
     1182 * Filter post type link to replace parent slug token in URLs
     1183 */
     1184add_filter('post_type_link', function($post_link, $post) {
     1185    $stored_post_types = get_option('instarank_custom_post_types', []);
     1186
     1187    if (isset($stored_post_types[$post->post_type])) {
     1188        $config = $stored_post_types[$post->post_type]['config'];
     1189
     1190        if (!empty($config['parent_post_type'])) {
     1191            $parent_slug = $config['parent_post_type'];
     1192            $token = '%' . $parent_slug . '%';
     1193
     1194            if (strpos($post_link, $token) !== false) {
     1195                // Find the parent post via post meta or parent resolution
     1196                $parent_value = get_post_meta($post->ID, '_instarank_parent_slug', true);
     1197
     1198                if (!$parent_value && $post->post_parent) {
     1199                    // Try to get parent post's slug
     1200                    $parent_post = get_post($post->post_parent);
     1201                    if ($parent_post) {
     1202                        $parent_value = $parent_post->post_name;
     1203                    }
     1204                }
     1205
     1206                // Fallback: try to find parent value from resolution config meta
     1207                if (!$parent_value) {
     1208                    $parent_config = isset($config['parent_resolution_config']) ? $config['parent_resolution_config'] : null;
     1209                    if ($parent_config && !empty($parent_config['column_name'])) {
     1210                        $col = $parent_config['column_name'];
     1211                        $val = get_post_meta($post->ID, $col, true);
     1212                        if ($val) {
     1213                            $parent_value = sanitize_title($val);
     1214                        }
     1215                    }
     1216                }
     1217
     1218                // Final fallback: use "uncategorized" to avoid duplicating the post type slug in the URL
     1219                if (!$parent_value) {
     1220                    $parent_value = 'uncategorized';
     1221                }
     1222
     1223                $post_link = str_replace($token, $parent_value, $post_link);
     1224            }
     1225        }
     1226    }
     1227
     1228    return $post_link;
     1229}, 10, 2);
    11181230
    11191231/**
  • instarank/trunk/readme.txt

    r3427377 r3467961  
    44Requires at least: 5.6
    55Tested up to: 6.9
    6 Stable tag: 2.0.4
     6Stable tag: 2.0.5
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    169169
    170170== Changelog ==
     171
     172= 2.0.5 =
     173* Feature: Hierarchical post type URL support with parent-child rewrite rules
     174* Feature: Search parameter for post listing API endpoint
     175* Feature: Word count and noindex/nofollow detection in post SEO data
     176* Feature: Parent slug resolution for custom post type URL chains
     177* Enhancement: Moved robots.txt, sitemap, and LLMs.txt settings management to SaaS dashboard
     178* Enhancement: Improved agent retry handling and error recovery
    171179
    172180= 2.0.4 =
Note: See TracChangeset for help on using the changeset viewer.