Changeset 3470017
- Timestamp:
- 02/26/2026 08:45:57 AM (2 weeks ago)
- Location:
- coding-bunny-llms-generator
- Files:
-
- 32 added
- 7 edited
-
tags/1.2.1 (added)
-
tags/1.2.1/admin (added)
-
tags/1.2.1/admin/class-cbllms-ai-bots.php (added)
-
tags/1.2.1/admin/class-cbllms-fields-render.php (added)
-
tags/1.2.1/admin/class-cbllms-sanitize.php (added)
-
tags/1.2.1/admin/class-cbllms-server-config.php (added)
-
tags/1.2.1/admin/class-cbllms-server-tools.php (added)
-
tags/1.2.1/admin/class-cbllms-settings.php (added)
-
tags/1.2.1/admin/css (added)
-
tags/1.2.1/admin/css/admin-styles.css (added)
-
tags/1.2.1/admin/images (added)
-
tags/1.2.1/admin/images/logo.svg (added)
-
tags/1.2.1/admin/js (added)
-
tags/1.2.1/admin/js/admin-scripts.js (added)
-
tags/1.2.1/admin/js/server-tools.js (added)
-
tags/1.2.1/coding-bunny-llms-generator.php (added)
-
tags/1.2.1/includes (added)
-
tags/1.2.1/includes/class-cbllms-admin.php (added)
-
tags/1.2.1/includes/class-cbllms-generator.php (added)
-
tags/1.2.1/includes/class-cbllms-scheduler.php (added)
-
tags/1.2.1/includes/generator (added)
-
tags/1.2.1/includes/generator/class-cbllms-attachments.php (added)
-
tags/1.2.1/includes/generator/class-cbllms-content-builder.php (added)
-
tags/1.2.1/includes/generator/class-cbllms-engine.php (added)
-
tags/1.2.1/includes/generator/class-cbllms-file-writer.php (added)
-
tags/1.2.1/includes/generator/class-cbllms-items.php (added)
-
tags/1.2.1/includes/generator/class-cbllms-metadata-builder.php (added)
-
tags/1.2.1/includes/generator/class-cbllms-utils.php (added)
-
tags/1.2.1/index.php (added)
-
tags/1.2.1/languages (added)
-
tags/1.2.1/readme.txt (added)
-
tags/1.2.1/uninstall.php (added)
-
trunk/admin/class-cbllms-fields-render.php (modified) (2 diffs)
-
trunk/admin/class-cbllms-sanitize.php (modified) (18 diffs)
-
trunk/admin/class-cbllms-settings.php (modified) (1 diff)
-
trunk/coding-bunny-llms-generator.php (modified) (2 diffs)
-
trunk/includes/generator/class-cbllms-items.php (modified) (2 diffs)
-
trunk/includes/generator/class-cbllms-metadata-builder.php (modified) (16 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
coding-bunny-llms-generator/trunk/admin/class-cbllms-fields-render.php
r3467642 r3470017 357 357 358 358 /** 359 * Renders the custom disallowed paths field.359 * Renders the custom disallowed paths/IDs field. 360 360 */ 361 361 public function render_disallowed_paths_field() { … … 363 363 $value = isset( $options['custom_disallowed_paths'] ) ? (string) $options['custom_disallowed_paths'] : ''; 364 364 printf( 365 '<textarea name="%s[custom_disallowed_paths]" rows="6" class="large-text code" placeholder="/wp-content/uploads/2024/ /private/ ">%s</textarea>',365 '<textarea name="%s[custom_disallowed_paths]" rows="6" class="large-text code" placeholder="/wp-content/uploads/2024/ /private/ 42 128">%s</textarea>', 366 366 esc_attr( $this->option_name ), 367 367 esc_textarea( $value ) 368 368 ); 369 echo '<p class="description">' . esc_html__( 'One path per line. They will be added after defaults (cart, checkout, admin, etc.). Must start with "/".', 'coding-bunny-llms-generator' ) . '</p>';369 echo '<p class="description">' . esc_html__( 'One entry per line. Paths must start with "/". You can also enter post/page/product IDs (numeric) to exclude specific content from crawling.', 'coding-bunny-llms-generator' ) . '</p>'; 370 370 } 371 371 -
coding-bunny-llms-generator/trunk/admin/class-cbllms-sanitize.php
r3467642 r3470017 8 8 9 9 /** 10 * AI bots handler instance.11 *12 10 * @var CodingBunny_LLMs_AI_Bots 13 11 */ … … 15 13 16 14 /** 17 * Constructor.18 *19 15 * @param CodingBunny_LLMs_AI_Bots $ai AI bots handler instance. 20 16 */ … … 32 28 $san = array(); 33 29 34 // Sanitize post types.35 30 $all_pts = get_post_types( array( 'public' => true ), 'names' ); 36 31 if ( isset( $input['include_post_types'] ) && is_array( $input['include_post_types'] ) ) { … … 45 40 } 46 41 47 // Sanitize max urls.48 42 $san['max_urls'] = isset( $input['max_urls'] ) ? absint( $input['max_urls'] ) : 0; 49 43 $san['max_attachments'] = isset( $input['max_attachments'] ) ? absint( $input['max_attachments'] ) : 20; 50 44 51 // Sanitize update frequency.52 45 $allowed_freq = array( 'none', 'immediate', 'daily', 'weekly' ); 53 46 $san['update_frequency'] = ( isset( $input['update_frequency'] ) && in_array( $input['update_frequency'], $allowed_freq, true ) ) … … 55 48 : 'daily'; 56 49 57 // Sanitize all checkboxes.58 50 $checkboxes = array( 59 51 'include_disallow', … … 79 71 } 80 72 81 // Sanitize priority sort.82 73 $allowed_sort = array( 'date_desc', 'date_asc', 'relevance' ); 83 74 $san['priority_sort'] = ( isset( $input['priority_sort'] ) && in_array( $input['priority_sort'], $allowed_sort, true ) ) … … 85 76 : 'relevance'; 86 77 87 // Sanitize priority default.88 78 if ( isset( $input['priority_default'] ) ) { 89 79 $val = (float) $input['priority_default']; … … 94 84 } 95 85 96 // Sanitize daily time.97 86 if ( isset( $input['daily_time'] ) && preg_match( '/^\d{2}:\d{2}$/', $input['daily_time'] ) ) { 98 87 $san['daily_time'] = $input['daily_time']; … … 101 90 } 102 91 103 // Sanitize weekly day & time.104 92 $allowed_days = array( 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun' ); 105 93 if ( isset( $input['weekly_day'] ) && in_array( $input['weekly_day'], $allowed_days, true ) ) { … … 114 102 } 115 103 116 // Sanitize custom disallowed paths.117 104 if ( ! empty( $input['custom_disallowed_paths'] ) ) { 118 105 $lines = explode( "\n", (string) $input['custom_disallowed_paths'] ); … … 120 107 foreach ( $lines as $line ) { 121 108 $line = trim( sanitize_text_field( $line ) ); 122 if ( '' !== $line && '/' === $line[0] ) { 109 if ( '' === $line ) { 110 continue; 111 } 112 if ( '/' === $line[0] || ctype_digit( $line ) ) { 123 113 $clean[] = $line; 124 114 } … … 129 119 } 130 120 131 // Sanitize custom taxonomies.132 121 $all_taxes = get_taxonomies( array( 'public' => true, '_builtin' => false ), 'names' ); 133 122 if ( isset( $input['custom_taxonomies'] ) && is_array( $input['custom_taxonomies'] ) ) { … … 142 131 } 143 132 144 // Sanitize additional content (textarea).145 133 if ( isset( $input['additional_content'] ) ) { 146 134 $san['additional_content'] = sanitize_textarea_field( $input['additional_content'] ); … … 149 137 } 150 138 151 // Sanitize allowed AI bots.152 139 $known = $this->ai->get_known_ai_bots(); 153 140 $uas = array(); … … 168 155 } 169 156 170 // Sanitize custom site description.171 157 if ( isset( $input['custom_site_description'] ) ) { 172 158 $san['custom_site_description'] = sanitize_textarea_field( $input['custom_site_description'] ); … … 175 161 } 176 162 177 // Sanitize AI license.178 163 $allowed_licenses = array_keys( $this->ai->get_ai_license_options() ); 179 164 if ( isset( $input['ai_license'] ) && in_array( $input['ai_license'], $allowed_licenses, true ) ) { … … 183 168 } 184 169 185 // Sanitize attachment license.186 170 if ( isset( $input['attachment_license'] ) && in_array( $input['attachment_license'], $allowed_licenses, true ) ) { 187 171 $san['attachment_license'] = sanitize_text_field( $input['attachment_license'] ); … … 190 174 } 191 175 192 // Sanitize summary length (words).193 176 if ( isset( $input['summary_length'] ) ) { 194 177 $len = absint( $input['summary_length'] ); -
coding-bunny-llms-generator/trunk/admin/class-cbllms-settings.php
r3467642 r3470017 264 264 add_settings_field( 265 265 'custom_disallowed_paths', 266 __( 'Custom blocked paths ', 'coding-bunny-llms-generator' ),266 __( 'Custom blocked paths/IDs', 'coding-bunny-llms-generator' ), 267 267 array( $this->fields, 'render_disallowed_paths_field' ), 268 268 'coding-bunny-llms-generator', -
coding-bunny-llms-generator/trunk/coding-bunny-llms-generator.php
r3467642 r3470017 4 4 * Plugin URI: https://coding-bunny.com/llms-generator/ 5 5 * Description: Generates and maintains an llms.txt file at site root to guide LLM/AI crawlers about your content structure and priorities. 6 * Version: 1.2. 06 * Version: 1.2.1 7 7 * Requires at least: 6.0 8 8 * Requires PHP: 8.0 … … 21 21 } 22 22 23 define( 'CBLLMS_VERSION', '1.2. 0' );23 define( 'CBLLMS_VERSION', '1.2.1' ); 24 24 define( 'CBLLMS_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 25 25 define( 'CBLLMS_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -
coding-bunny-llms-generator/trunk/includes/generator/class-cbllms-items.php
r3467642 r3470017 28 28 } 29 29 30 $excluded_ids = CodingBunny_LLMs_Generator_Metadata_Builder::get_excluded_post_ids( $options ); 31 30 32 $args = array( 31 33 'post_type' => $post_types, … … 49 51 } 50 52 51 if ( ! empty( $items ) ) { 52 $items = self::sort_items_by_relevance( $items ); 53 } 54 55 return apply_filters( 'cbllms_items', $items ); 53 if ( ! empty( $excluded_ids ) ) { 54 $items = array_filter( 55 $items, 56 function ( $item ) use ( $excluded_ids ) { 57 return ! in_array( $item['post_id'], $excluded_ids, true ); 58 } 59 ); 60 $items = array_values( $items ); 56 61 } 57 62 58 /** 59 * Gets non-media (non-attachment) items. 60 * 61 * @param array $args WP_Query args. 62 * @param array $post_types Post types. 63 * @return array 64 */ 65 public static function get_non_media_items( $args, $post_types ) { 66 $items = array(); 67 $args_normal = $args; 68 $args_normal['post_type'] = $post_types; 69 $args_normal['post_status'] = 'publish'; 70 71 $options = CodingBunny_LLMs_Generator::instance()->get_options(); 72 $summary_length = isset( $options['summary_length'] ) ? absint( $options['summary_length'] ) : 40; 73 74 $query = new WP_Query( $args_normal ); 75 if ( $query->have_posts() ) { 76 foreach ( $query->posts as $post_id ) { 77 $permalink = get_permalink( $post_id ); 78 if ( ! $permalink ) { 79 continue; 80 } 81 82 $raw_title = get_the_title( $post_id ); 83 $title = CodingBunny_LLMs_Generator_Utils::normalize_text( $raw_title ); 84 85 $raw_excerpt = get_the_excerpt( $post_id ); 86 if ( empty( $raw_excerpt ) ) { 87 $raw_excerpt = get_post_field( 'post_content', $post_id ); 88 } 89 90 $summary = CodingBunny_LLMs_Generator_Utils::build_summary( $post_id, (string) $raw_excerpt, $summary_length ); 91 92 $items[] = array( 93 'post_id' => $post_id, 94 'url' => $permalink, 95 'path' => (string) wp_parse_url( $permalink, PHP_URL_PATH ), 96 'title' => $title, 97 'excerpt' => $summary, 98 'date' => get_the_date( 'c', $post_id ), 99 'post_type' => get_post_type( $post_id ), 100 'menu_order' => absint( get_post_field( 'menu_order', $post_id ) ), 101 ); 102 } 103 wp_reset_postdata(); 104 } 105 106 return $items; 63 if ( ! empty( $items ) ) { 64 $items = self::sort_items_by_relevance( $items ); 107 65 } 108 66 109 /** 110 * Sorts items by relevance (page > post > product > attachment). 111 * 112 * @param array $items Items array. 113 * @return array 114 */ 115 public static function sort_items_by_relevance( $items ) { 116 usort( 117 $items, 118 function ( $a, $b ) { 119 $priority_order = array( 120 'page' => 1, 121 'post' => 2, 122 'product' => 3, 123 'attachment' => 4, 124 ); 67 return apply_filters( 'cbllms_items', $items ); 68 } 125 69 126 $a_priority = isset( $priority_order[ $a['post_type'] ] ) ? $priority_order[ $a['post_type'] ] : 99; 127 $b_priority = isset( $priority_order[ $b['post_type'] ] ) ? $priority_order[ $b['post_type'] ] : 99; 70 /** 71 * Gets non-media (non-attachment) items. 72 * 73 * @param array $args WP_Query args. 74 * @param array $post_types Post types. 75 * @return array 76 */ 77 public static function get_non_media_items( $args, $post_types ) { 78 $items = array(); 79 $args_normal = $args; 80 $args_normal['post_type'] = $post_types; 81 $args_normal['post_status'] = 'publish'; 128 82 129 if ( $a_priority !== $b_priority ) { 130 return $a_priority <=> $b_priority; 83 $options = CodingBunny_LLMs_Generator::instance()->get_options(); 84 $summary_length = isset( $options['summary_length'] ) ? absint( $options['summary_length'] ) : 40; 85 86 $query = new WP_Query( $args_normal ); 87 if ( $query->have_posts() ) { 88 foreach ( $query->posts as $post_id ) { 89 $permalink = get_permalink( $post_id ); 90 if ( ! $permalink ) { 91 continue; 131 92 } 132 93 133 if ( $a['post_type'] === 'page' && $b['post_type'] === 'page' ) { 134 if ( $a['menu_order'] !== $b['menu_order'] ) { 135 return $a['menu_order'] <=> $b['menu_order']; 136 } 94 $raw_title = get_the_title( $post_id ); 95 $title = CodingBunny_LLMs_Generator_Utils::normalize_text( $raw_title ); 96 97 $raw_excerpt = get_the_excerpt( $post_id ); 98 if ( empty( $raw_excerpt ) ) { 99 $raw_excerpt = get_post_field( 'post_content', $post_id ); 137 100 } 138 101 139 return strcmp( $b['date'], $a['date'] ); 102 $summary = CodingBunny_LLMs_Generator_Utils::build_summary( $post_id, (string) $raw_excerpt, $summary_length ); 103 104 $items[] = array( 105 'post_id' => $post_id, 106 'url' => $permalink, 107 'path' => (string) wp_parse_url( $permalink, PHP_URL_PATH ), 108 'title' => $title, 109 'excerpt' => $summary, 110 'date' => get_the_date( 'c', $post_id ), 111 'post_type' => get_post_type( $post_id ), 112 'menu_order' => absint( get_post_field( 'menu_order', $post_id ) ), 113 ); 140 114 } 141 ); 115 wp_reset_postdata(); 116 } 142 117 143 118 return $items; 144 119 } 120 121 /** 122 * Sorts items by relevance (page > post > product > attachment). 123 * 124 * @param array $items Items array. 125 * @return array 126 */ 127 public static function sort_items_by_relevance( $items ) { 128 usort( 129 $items, 130 function ( $a, $b ) { 131 $priority_order = array( 132 'page' => 1, 133 'post' => 2, 134 'product' => 3, 135 'attachment' => 4, 136 ); 137 138 $a_priority = isset( $priority_order[ $a['post_type'] ] ) ? $priority_order[ $a['post_type'] ] : 99; 139 $b_priority = isset( $priority_order[ $b['post_type'] ] ) ? $priority_order[ $b['post_type'] ] : 99; 140 141 if ( $a_priority !== $b_priority ) { 142 return $a_priority <=> $b_priority; 143 } 144 145 if ( $a['post_type'] === 'page' && $b['post_type'] === 'page' ) { 146 if ( $a['menu_order'] !== $b['menu_order'] ) { 147 return $a['menu_order'] <=> $b['menu_order']; 148 } 149 } 150 151 return strcmp( $b['date'], $a['date'] ); 152 } 153 ); 154 155 return $items; 145 156 } 157 } -
coding-bunny-llms-generator/trunk/includes/generator/class-cbllms-metadata-builder.php
r3393019 r3470017 34 34 $lines[] = ''; 35 35 36 $excluded_ids = self::get_excluded_post_ids( $options ); 37 36 38 foreach ( $items as $item ) { 39 if ( ! empty( $excluded_ids ) && isset( $item['post_id'] ) && in_array( $item['post_id'], $excluded_ids, true ) ) { 40 continue; 41 } 42 37 43 if ( ! empty( $disallowed ) && self::is_path_disallowed( $item['path'], $disallowed ) ) { 38 44 continue; … … 518 524 /** 519 525 * Parses custom disallowed paths from textarea input. 526 * Supports both paths (starting with /) and post IDs (numeric). 520 527 * 521 528 * @param string $custom_paths Custom paths input. … … 528 535 foreach ( $lines as $line ) { 529 536 $line = trim( $line ); 530 if ( '' !== $line ) { 537 if ( '' === $line ) { 538 continue; 539 } 540 541 if ( ctype_digit( $line ) ) { 542 $post_id = absint( $line ); 543 $permalink = get_permalink( $post_id ); 544 if ( $permalink ) { 545 $path = wp_parse_url( $permalink, PHP_URL_PATH ); 546 if ( $path ) { 547 $paths[] = trailingslashit( $path ); 548 } 549 } 550 } else { 531 551 $paths[] = $line; 532 552 } … … 534 554 535 555 return $paths; 556 } 557 558 /** 559 * Gets excluded post IDs from custom disallowed paths. 560 * 561 * @param array $options Plugin options. 562 * @return array Array of excluded post IDs. 563 */ 564 public static function get_excluded_post_ids( $options ) { 565 $ids = array(); 566 567 if ( empty( $options['custom_disallowed_paths'] ) ) { 568 return $ids; 569 } 570 571 $lines = explode( "\n", (string) $options['custom_disallowed_paths'] ); 572 573 foreach ( $lines as $line ) { 574 $line = trim( $line ); 575 if ( '' !== $line && ctype_digit( $line ) ) { 576 $ids[] = absint( $line ); 577 } 578 } 579 580 return array_unique( $ids ); 536 581 } 537 582 … … 674 719 $languages = array(); 675 720 676 // Polylang support.677 721 if ( function_exists( 'pll_get_post_language' ) ) { 678 722 $current_lang = pll_get_post_language( $post_id, 'locale' ); … … 694 738 } 695 739 696 // WPML support.697 740 if ( function_exists( 'apply_filters' ) && empty( $languages ) ) { 698 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WPML third-party hook.741 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound 699 742 $details = apply_filters( 'wpml_post_language_details', null, $post_id ); 700 743 if ( is_array( $details ) && ! empty( $details['locale'] ) ) { … … 703 746 } 704 747 705 // Fallback to site language.706 748 if ( empty( $languages ) ) { 707 749 $site_lang = get_bloginfo( 'language' ); … … 721 763 */ 722 764 public static function get_canonical_url( $post_id ) { 723 // Yoast SEO.724 765 if ( class_exists( 'WPSEO_Meta' ) ) { 725 766 $canonical = WPSEO_Meta::get_value( 'canonical', $post_id ); … … 729 770 } 730 771 731 // Rank Math.732 772 if ( class_exists( 'RankMath' ) ) { 733 773 $canonical = get_post_meta( $post_id, 'rank_math_canonical_url', true ); … … 741 781 742 782 /** 743 * Gets related URLs for a post (parent/children/pages/products/etc).783 * Gets related URLs for a post. 744 784 * 745 785 * @param int $post_id Post ID. … … 750 790 $related = array(); 751 791 752 // Pages: parent and children.753 792 if ( 'page' === $post_type ) { 754 793 $parent_id = wp_get_post_parent_id( $post_id ); … … 770 809 } 771 810 772 // Posts: related by category.773 811 if ( 'post' === $post_type ) { 774 812 $categories = wp_get_post_categories( $post_id, array( 'fields' => 'ids' ) ); … … 795 833 } 796 834 797 // WooCommerce products.798 835 if ( 'product' === $post_type && function_exists( 'wc_get_related_products' ) ) { 799 836 $related_ids = wc_get_related_products( $post_id, 3 ); … … 815 852 */ 816 853 public static function detect_schema_type( $post_id, $post_type ) { 817 // WooCommerce products.818 854 if ( 'product' === $post_type || ( class_exists( 'WooCommerce' ) && 'product' === get_post_type( $post_id ) ) ) { 819 855 return 'Product'; 820 856 } 821 857 822 // Attachments/media.823 858 if ( 'attachment' === $post_type ) { 824 859 $mime = get_post_mime_type( $post_id ); … … 832 867 } 833 868 834 // Pages.835 869 if ( 'page' === $post_type ) { 836 870 $title_lower = strtolower( get_the_title( $post_id ) ); … … 851 885 } 852 886 853 // Posts.854 887 if ( 'post' === $post_type ) { 855 888 return 'BlogPosting'; 856 889 } 857 890 858 // Events.859 891 if ( 'event' === $post_type || 'tribe_events' === $post_type ) { 860 892 return 'Event'; -
coding-bunny-llms-generator/trunk/readme.txt
r3467642 r3470017 5 5 Tested up to: 6.9 6 6 Requires PHP: 8.0 7 Stable tag: 1.2. 07 Stable tag: 1.2.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 73 73 == Changelog == 74 74 75 = 1.2.1: 2026-02-26 = 76 * Improved: Added the ability to exclude pages, posts, and products by ID 77 * Fix: Fixed a bug in Priority URLs ordering 78 75 79 = 1.2.0: 2026-02-23 = 76 80 * New: Added "Additional content" field
Note: See TracChangeset
for help on using the changeset viewer.