Plugin Directory

Changeset 3693501


Ignore:
Timestamp:
09/13/2026 09:56:37 AM (6 weeks ago)
Author:
aivc
Message:

Release 34.1.8: new llms.txt module (llms.txt + llms-full.txt generation, pinned pages, live validation), SQL hardening and whitelisting in orphan-rescue/404-scout, PHPCS compliance fixes, refreshed banners. Bump Tested up to 7.1.

Location:
ailinkmaster-internal-auto-manager
Files:
13 added
1 deleted
12 edited

Legend:

Unmodified
Added
Removed
  • ailinkmaster-internal-auto-manager/trunk/ailinkmaster-internal-auto-manager.php

    r3651068 r3693501  
    33Plugin Name: LinkMesh: Semantic AI Internal Links for SEO
    44Description: Automatically create smart internal links with AI-generated anchor text. Features orphan page rescue, keyword intelligence, 404 link monitoring, and click analytics for maximum SEO impact.
    5 Version: 34.1.7
     5Version: 34.1.8
    66Requires at least: 6.2
    77Requires PHP: 7.4
    … …  
    2727}
    2828
     29
     30// Load the llms.txt module (self-contained: hooks, cron, serving).
     31require_once AILM_REFACTORED_PATH . 'modules/llms-txt/class-ailm-llmstxt-init.php';
     32AILM_LlmsTxt_Init::init();
    2933
    3034// Load the core
  • ailinkmaster-internal-auto-manager/trunk/includes/class-ailm-link-repository.php

    r3624641 r3693501  
    229229            array(
    230230                'numberposts' => -1,
    231                 'post_type' => 'post',
     231                'post_type' => get_option('ailm_allowed_post_types', array('post', 'page')),
    232232                'post_status' => 'publish',
    233233                'fields' => 'ids',
    … …  
    271271        $edges = array();
    272272
    273         // Collect all nodes (all published posts + pages)
     273        // Collect all nodes (all published allowed post types)
    274274        $all_ids = get_posts(
    275275            array(
    276276                'numberposts' => -1,
    277                 'post_type' => array('post', 'page'),
     277                'post_type' => get_option('ailm_allowed_post_types', array('post', 'page')),
    278278                'post_status' => 'publish',
    279279                'fields' => 'ids',
  • ailinkmaster-internal-auto-manager/trunk/includes/class-ailm-reconciler.php

    r3624641 r3693501  
    9090        if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) {
    9191            return;
     92        }
     93
     94        // Skip non-content post types (e.g. WooCommerce shop_order/shop_coupon
     95        // saves) — reconciliation is only meaningful for allowed content types.
     96        if (null === $post) {
     97            $post = get_post($post_id);
     98        }
     99        if ($post) {
     100            $allowed_types = get_option('ailm_allowed_post_types', array('post', 'page'));
     101            if (!in_array($post->post_type, (array) $allowed_types, true)) {
     102                return;
     103            }
    92104        }
    93105
  • ailinkmaster-internal-auto-manager/trunk/includes/utils/class-ailm-text-processor.php

    r3624641 r3693501  
    1717        $html = preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $html );
    1818        // Удаляем HTML-комментарии (разметка Gutenberg)
    19         $html = preg_replace( '//is', '', $html );
     19        $html = preg_replace( '@<!--.*?-->@s', '', $html );
    2020       
    2121        return $html;
  • ailinkmaster-internal-auto-manager/trunk/modules/404-scout/class-ailm-scout-background-processor.php

    r3624641 r3693501  
    4545
    4646        global $wpdb;
     47        // User-configurable list: sanitize + whitelist before it is turned
     48        // into a placeholder list, so nothing user-supplied reaches the SQL.
     49        $allowed_types = get_option('ailm_allowed_post_types', array('post', 'page'));
     50        $allowed_types = array_values(array_unique(array_filter(array_map('sanitize_key', (array) $allowed_types))));
     51        if (empty($allowed_types)) {
     52            $allowed_types = array('post');
     53        }
     54        $type_placeholders = implode(',', array_fill(0, count($allowed_types), '%s'));
     55        $posts_table       = $wpdb->posts; // Core table name, never user input.
     56        // The interpolations below are trusted: $posts_table is the core posts
     57        // table and $type_placeholders is a fixed run of "%s" built from the
     58        // sanitized slugs above — no user input reaches the query string.
    4759        // Quickly get total post count for progress bar
    48         // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    49         $total_posts = (int) $wpdb->get_var("SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type IN ('post', 'page') AND post_status = 'publish'");
     60        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- see note above: core table + an equivalent number of %s placeholders
     61        $total_posts = (int) $wpdb->get_var($wpdb->prepare(
     62            "SELECT COUNT(ID) FROM {$posts_table} WHERE post_type IN ($type_placeholders) AND post_status = 'publish'",
     63            $allowed_types
     64        ));
     65        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
    5066
    5167        if ($total_posts === 0) {
    … …  
    117133        $batch_size = 100;
    118134
    119         // Lightweight query: get 25 posts with ID greater than the last checked one
    120         // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     135        // Sanitized + whitelisted exactly as in start_scan().
     136        $allowed_types = get_option('ailm_allowed_post_types', array('post', 'page'));
     137        $allowed_types = array_values(array_unique(array_filter(array_map('sanitize_key', (array) $allowed_types))));
     138        if (empty($allowed_types)) {
     139            $allowed_types = array('post');
     140        }
     141        $type_placeholders = implode(',', array_fill(0, count($allowed_types), '%s'));
     142        $posts_table       = $wpdb->posts; // Core table name, never user input.
     143        // Lightweight query: get the next batch of posts after $last_id. The
     144        // interpolations are trusted exactly as in start_scan(): core table +
     145        // a fixed run of "%s" placeholders. Note the disable/enable form — the
     146        // sniff reports the line of the $wpdb->prepare() call, not the query,
     147        // so a per-line ignore on the string would leave a warning behind.
     148        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- see note above: core table + an equivalent number of placeholders
    121149        $posts = $wpdb->get_results($wpdb->prepare(
    122             "SELECT ID FROM {$wpdb->posts} WHERE post_type IN ('post', 'page') AND post_status = 'publish' AND ID > %d ORDER BY ID ASC LIMIT %d",
    123             $last_id,
    124             $batch_size
    125         ));
     150            "SELECT ID FROM {$posts_table} WHERE post_type IN ($type_placeholders) AND post_status = 'publish' AND ID > %d ORDER BY ID ASC LIMIT %d",
     151            array_merge($allowed_types, array($last_id, $batch_size))
     152        ));
     153        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
    126154
    127155        if (empty($posts)) {
  • ailinkmaster-internal-auto-manager/trunk/modules/404-scout/class-ailm-scout-scanner.php

    r3624641 r3693501  
    3131        $post = get_post($post_id);
    3232        if (!$post) {
     33            return;
     34        }
     35
     36        // Only scan content post types configured for the plugin. This keeps
     37        // WooCommerce order/coupon saves (shop_order, shop_coupon, ...) from
     38        // triggering a full link scan on every storefront transaction.
     39        $allowed_types = get_option('ailm_allowed_post_types', array('post', 'page'));
     40        if (!in_array($post->post_type, (array) $allowed_types, true)) {
    3341            return;
    3442        }
  • ailinkmaster-internal-auto-manager/trunk/modules/llms-txt/class-ailm-llmstxt-admin.php

    r3626135 r3693501  
    22/**
    33 * Admin UI for the llms.txt module.
     4 *
     5 * Preview-first workflow: Generate preview → review/edit the draft →
     6 * Publish. Manual curation of posts via select2.
    47 *
    58 * @since      34.1.2
    … …  
    1518
    1619        /**
    17          * Render the module's admin page (placeholder).
     20         * Register asset enqueue on the proper hook.
     21         *
     22         * render_tab() runs during page body output, far too late for
     23         * wp_enqueue_style (which prints in <head>). The assets must be
     24         * hooked to admin_enqueue_scripts instead.
     25         *
     26         * @return void
     27         */
     28        public static function init() {
     29                // Priority 20: the main admin controller enqueues the plugin-wide
     30                // select2 at priority 10, so this handler must run afterwards to be
     31                // able to detect (and reuse) that handle instead of adding a second
     32                // copy of select2 on the same page.
     33                add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ), 20 );
     34        }
     35
     36        /**
     37         * Render the module's admin page.
    1838         *
    1939         * @return void
    … …  
    2343                        return;
    2444                }
     45
     46                $settings     = AILM_LlmsTxt_Settings::get_all();
     47                $public_types = get_post_types( array( 'public' => true ), 'objects' );
     48                // Attachment is technically public but never a content page.
     49                unset( $public_types['attachment'] );
     50
     51                $draft = AILM_LlmsTxt_Rebuilder::get_draft();
    2552                ?>
    26                 <div class="ailm-wrap" style="margin-top: 24px;">
     53                <div class="ailm-wrap" id="ailm-llmstxt-wrap" style="margin-top: 24px;">
    2754
    2855                        <div class="ailm-header-bar">
    … …  
    3663                        </div>
    3764
    38                         <div class="ailm-card" style="text-align:center; padding:60px 24px;">
    39                                 <span class="dashicons dashicons-hammer" style="font-size:48px; width:48px; height:48px; color:#86868b;"></span>
    40                                 <h2 style="margin:16px 0 8px; font-size:22px; color:#1d1d1f;">
    41                                         <?php esc_html_e( 'Coming soon', 'ailinkmaster-internal-auto-manager' ); ?>
    42                                 </h2>
    43                                 <p class="ailm-description" style="font-size:14px; color:#86868b; margin:0;">
    44                                         <?php esc_html_e( 'llms.txt module', 'ailinkmaster-internal-auto-manager' ); ?>
     65                        <?php if ( empty( $settings['enabled'] ) ) : ?>
     66                                <div class="notice notice-warning" style="margin: 12px 0;">
     67                                        <p>
     68                                                <?php esc_html_e( 'The module is currently disabled. Click "Generate preview" to enable it and build a draft, or tick "Generate and serve llms.txt" in the settings below and save.', 'ailinkmaster-internal-auto-manager' ); ?>
     69                                        </p>
     70                                </div>
     71                        <?php endif; ?>
     72
     73                        <div id="ailm-llmstxt-notice" class="notice is-dismissible" style="display:none; margin: 12px 0;"></div>
     74
     75                        <div class="ailm-card" style="padding: 20px; margin-bottom: 16px;">
     76                                <h3 style="margin-top:0;"><?php esc_html_e( 'Status', 'ailinkmaster-internal-auto-manager' ); ?></h3>
     77                                <table class="widefat striped" style="max-width: 720px;">
     78                                        <tbody>
     79                                                <tr>
     80                                                        <td><strong><?php esc_html_e( 'Module enabled', 'ailinkmaster-internal-auto-manager' ); ?></strong></td>
     81                                                        <td id="ailm-llmstxt-status-enabled">—</td>
     82                                                </tr>
     83                                                <tr>
     84                                                        <td><strong><?php esc_html_e( 'llms.txt file (live)', 'ailinkmaster-internal-auto-manager' ); ?></strong></td>
     85                                                        <td><span id="ailm-llmstxt-status-file">—</span> <a href="<?php echo esc_url( home_url( '/llms.txt' ) ); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html( home_url( '/llms.txt' ) ); ?></a></td>
     86                                                </tr>
     87                                                <tr>
     88                                                        <td><strong><?php esc_html_e( 'llms-full.txt file', 'ailinkmaster-internal-auto-manager' ); ?></strong></td>
     89                                                        <td><span id="ailm-llmstxt-status-full">—</span> <a href="<?php echo esc_url( home_url( '/llms-full.txt' ) ); ?>" target="_blank" rel="noopener noreferrer"><?php echo esc_html( home_url( '/llms-full.txt' ) ); ?></a></td>
     90                                                </tr>
     91                                                <tr>
     92                                                        <td><strong><?php esc_html_e( 'Site root writable', 'ailinkmaster-internal-auto-manager' ); ?></strong></td>
     93                                                        <td id="ailm-llmstxt-status-writable">—</td>
     94                                                </tr>
     95                                                <tr>
     96                                                        <td><strong><?php esc_html_e( 'Cached AI summaries', 'ailinkmaster-internal-auto-manager' ); ?></strong></td>
     97                                                        <td id="ailm-llmstxt-status-summaries">—</td>
     98                                                </tr>
     99                                                <tr>
     100                                                        <td><strong><?php esc_html_e( 'Last rebuild', 'ailinkmaster-internal-auto-manager' ); ?></strong></td>
     101                                                        <td id="ailm-llmstxt-status-last-run">—</td>
     102                                                </tr>
     103                                                <tr>
     104                                                        <td><strong><?php esc_html_e( 'Last summaries sync', 'ailinkmaster-internal-auto-manager' ); ?></strong></td>
     105                                                        <td id="ailm-llmstxt-status-last-sync">—</td>
     106                                                </tr>
     107                                        </tbody>
     108                                </table>
     109
     110                                <p style="margin-top: 16px; display: flex; gap: 8px; flex-wrap: wrap;">
     111                                        <button type="button" class="button button-primary" id="ailm-llmstxt-rebuild"><?php esc_html_e( 'Generate preview', 'ailinkmaster-internal-auto-manager' ); ?></button>
     112                                        <button type="button" class="button" id="ailm-llmstxt-validate"><?php esc_html_e( 'Validate live file', 'ailinkmaster-internal-auto-manager' ); ?></button>
     113                                        <button type="button" class="button" id="ailm-llmstxt-sync"><?php esc_html_e( 'Sync AI descriptions', 'ailinkmaster-internal-auto-manager' ); ?></button>
    45114                                </p>
     115                        </div>
     116
     117                        <div class="ailm-card" style="padding: 20px; margin-bottom: 16px;">
     118                                <h3 style="margin-top:0;"><?php esc_html_e( 'Draft preview', 'ailinkmaster-internal-auto-manager' ); ?></h3>
     119                                <p class="description">
     120                                        <?php esc_html_e( 'Review the generated draft below. You can edit it freely — the live file is only updated when you press "Publish".', 'ailinkmaster-internal-auto-manager' ); ?>
     121                                </p>
     122                                <textarea id="ailm-llmstxt-draft" class="large-text" rows="18" style="font-family: monospace;" spellcheck="false"><?php echo esc_textarea( $draft ); ?></textarea>
     123                                <p style="display:flex; gap:8px; flex-wrap:wrap;">
     124                                        <button type="button" class="button button-primary" id="ailm-llmstxt-publish"><?php esc_html_e( 'Publish', 'ailinkmaster-internal-auto-manager' ); ?></button>
     125                                        <button type="button" class="button" id="ailm-llmstxt-regenerate"><?php esc_html_e( 'Regenerate (discard edits)', 'ailinkmaster-internal-auto-manager' ); ?></button>
     126                                </p>
     127                        </div>
     128
     129                        <div class="ailm-card" style="padding: 20px;">
     130                                <h3 style="margin-top:0;"><?php esc_html_e( 'Settings', 'ailinkmaster-internal-auto-manager' ); ?></h3>
     131
     132                                <form id="ailm-llmstxt-settings-form">
     133                                        <table class="form-table" role="presentation">
     134                                                <tbody>
     135                                                        <tr>
     136                                                                <th scope="row"><?php esc_html_e( 'Enable module', 'ailinkmaster-internal-auto-manager' ); ?></th>
     137                                                                <td>
     138                                                                        <label>
     139                                                                                <input type="checkbox" id="ailm-llmstxt-enabled" value="1" <?php checked( ! empty( $settings['enabled'] ) ); ?> />
     140                                                                                <?php esc_html_e( 'Generate and serve llms.txt', 'ailinkmaster-internal-auto-manager' ); ?>
     141                                                                        </label>
     142                                                                </td>
     143                                                        </tr>
     144                                                        <tr>
     145                                                                <th scope="row"><?php esc_html_e( 'Content mode', 'ailinkmaster-internal-auto-manager' ); ?></th>
     146                                                                <td>
     147                                                                        <label style="display:block; margin-bottom:6px;">
     148                                                                                <input type="radio" name="ailm_llmstxt_curation_mode" value="auto" <?php checked( 'auto', $settings['curation_mode'] ); ?> />
     149                                                                                <?php esc_html_e( 'Automatic — top posts by AILM Rank (per post type)', 'ailinkmaster-internal-auto-manager' ); ?>
     150                                                                        </label>
     151                                                                        <label style="display:block;">
     152                                                                                <input type="radio" name="ailm_llmstxt_curation_mode" value="manual" <?php checked( 'manual', $settings['curation_mode'] ); ?> />
     153                                                                                <?php esc_html_e( 'Manual — I select the pages myself', 'ailinkmaster-internal-auto-manager' ); ?>
     154                                                                        </label>
     155                                                                </td>
     156                                                        </tr>
     157                                                        <tr id="ailm-llmstxt-manual-row" style="<?php echo 'manual' === $settings['curation_mode'] ? '' : 'display:none;'; ?>">
     158                                                                <th scope="row"><?php esc_html_e( 'Selected pages', 'ailinkmaster-internal-auto-manager' ); ?></th>
     159                                                                <td>
     160                                                                        <select id="ailm-llmstxt-selected-posts" multiple="multiple" style="width: 100%; min-width: 320px;">
     161                                                                                <?php foreach ( self::get_picker_items( (array) $settings['selected_posts'] ) as $item ) : ?>
     162                                                                                        <option value="<?php echo esc_attr( $item['id'] ); ?>" selected="selected"><?php echo esc_html( $item['text'] ); ?></option>
     163                                                                                <?php endforeach; ?>
     164                                                                        </select>
     165                                                                        <p style="margin-top:8px;">
     166                                                                                <button type="button" class="button" id="ailm-llmstxt-prefill"><?php esc_html_e( 'Prefill top by AILM Rank', 'ailinkmaster-internal-auto-manager' ); ?></button>
     167                                                                        </p>
     168                                                                        <p class="description"><?php esc_html_e( 'Search and pick pages; drag to reorder. The file lists them in this exact order.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     169                                                                </td>
     170                                                        </tr>
     171                                                        <tr>
     172                                                                <th scope="row"><?php esc_html_e( 'Pinned pages (listed first)', 'ailinkmaster-internal-auto-manager' ); ?></th>
     173                                                                <td>
     174                                                                        <select id="ailm-llmstxt-pinned-pages" multiple="multiple" style="width: 100%; min-width: 320px;">
     175                                                                                <?php foreach ( self::get_picker_items( (array) $settings['pinned_pages'] ) as $item ) : ?>
     176                                                                                        <option value="<?php echo esc_attr( $item['id'] ); ?>" selected="selected"><?php echo esc_html( $item['text'] ); ?></option>
     177                                                                                <?php endforeach; ?>
     178                                                                        </select>
     179                                                                        <p class="description"><?php esc_html_e( 'Pages such as About or Contacts, listed first so LLMs read them before everything else. Works in both modes; up to 20 pages.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     180                                                                        <p style="margin-top:8px;">
     181                                                                                <label for="ailm-llmstxt-pinned-title"><?php esc_html_e( 'Section heading', 'ailinkmaster-internal-auto-manager' ); ?></label><br />
     182                                                                                <input type="text" class="regular-text" id="ailm-llmstxt-pinned-title" value="<?php echo esc_attr( $settings['pinned_title'] ); ?>" placeholder="<?php echo esc_attr__( 'Key Pages', 'ailinkmaster-internal-auto-manager' ); ?>" />
     183                                                                        </p>
     184                                                                        <p class="description"><?php esc_html_e( 'These pages are not repeated in the sections below.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     185                                                                </td>
     186                                                        </tr>
     187                                                        <tr>
     188                                                                <th scope="row"><?php esc_html_e( 'Post types', 'ailinkmaster-internal-auto-manager' ); ?></th>
     189                                                                <td>
     190                                                                        <?php foreach ( $public_types as $type_obj ) : ?>
     191                                                                                <label style="display:inline-block; margin-right:16px;">
     192                                                                                        <input type="checkbox" name="ailm_llmstxt_post_types[]" value="<?php echo esc_attr( $type_obj->name ); ?>" <?php checked( in_array( $type_obj->name, (array) $settings['post_types'], true ) ); ?> />
     193                                                                                        <?php echo esc_html( $type_obj->labels->name ); ?>
     194                                                                                </label>
     195                                                                        <?php endforeach; ?>
     196                                                                        <p class="description"><?php esc_html_e( 'Used in Automatic mode and as the search scope in Manual mode.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     197                                                                </td>
     198                                                        </tr>
     199                                                        <tr>
     200                                                                <th scope="row"><label for="ailm-llmstxt-description"><?php esc_html_e( 'Site description', 'ailinkmaster-internal-auto-manager' ); ?></label></th>
     201                                                                <td>
     202                                                                        <input type="text" class="regular-text" id="ailm-llmstxt-description" value="<?php echo esc_attr( $settings['site_description'] ); ?>" />
     203                                                                        <p class="description"><?php esc_html_e( 'Used for the "> description" line. Falls back to the site tagline when empty.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     204                                                                </td>
     205                                                        </tr>
     206                                                        <tr>
     207                                                                <th scope="row"><label for="ailm-llmstxt-exclude-ids"><?php esc_html_e( 'Exclude post IDs', 'ailinkmaster-internal-auto-manager' ); ?></label></th>
     208                                                                <td>
     209                                                                        <input type="text" class="regular-text" id="ailm-llmstxt-exclude-ids" value="<?php echo esc_attr( implode( ', ', (array) $settings['exclude_ids'] ) ); ?>" />
     210                                                                        <p class="description"><?php esc_html_e( 'Comma-separated post IDs to exclude from both files (Automatic mode).', 'ailinkmaster-internal-auto-manager' ); ?></p>
     211                                                                </td>
     212                                                        </tr>
     213                                                        <tr>
     214                                                                <th scope="row"><label for="ailm-llmstxt-max-posts"><?php esc_html_e( 'Max posts', 'ailinkmaster-internal-auto-manager' ); ?></label></th>
     215                                                                <td>
     216                                                                        <input type="number" min="1" max="500" step="1" class="small-text" id="ailm-llmstxt-max-posts" value="<?php echo esc_attr( (int) $settings['max_posts'] ); ?>" />
     217                                                                        <p class="description"><?php esc_html_e( 'Hard cap of 500 posts in v1 (Automatic mode).', 'ailinkmaster-internal-auto-manager' ); ?></p>
     218                                                                </td>
     219                                                        </tr>
     220                                                        <tr>
     221                                                                <th scope="row"><?php esc_html_e( 'llms-full.txt', 'ailinkmaster-internal-auto-manager' ); ?></th>
     222                                                                <td>
     223                                                                        <label>
     224                                                                                <input type="checkbox" id="ailm-llmstxt-generate-full" value="1" <?php checked( ! empty( $settings['generate_full'] ) ); ?> />
     225                                                                                <?php esc_html_e( 'Also generate llms-full.txt (full content in Markdown)', 'ailinkmaster-internal-auto-manager' ); ?>
     226                                                                        </label>
     227                                                                        <p class="description"><?php esc_html_e( 'Not recommended for sites with more than 500 posts.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     228                                                                </td>
     229                                                        </tr>
     230                                                        <tr>
     231                                                                <th scope="row"><?php esc_html_e( 'Automatic rebuild', 'ailinkmaster-internal-auto-manager' ); ?></th>
     232                                                                <td>
     233                                                                        <label>
     234                                                                                <input type="checkbox" id="ailm-llmstxt-auto-rebuild" value="1" <?php checked( ! empty( $settings['auto_rebuild'] ) ); ?> />
     235                                                                                <?php esc_html_e( 'Refresh the draft automatically when posts change', 'ailinkmaster-internal-auto-manager' ); ?>
     236                                                                        </label>
     237                                                                </td>
     238                                                        </tr>
     239                                                        <tr>
     240                                                                <th scope="row"><?php esc_html_e( 'Autopublish', 'ailinkmaster-internal-auto-manager' ); ?></th>
     241                                                                <td>
     242                                                                        <label>
     243                                                                                <input type="checkbox" id="ailm-llmstxt-autopublish" value="1" <?php checked( ! empty( $settings['autopublish'] ) ); ?> />
     244                                                                                <?php esc_html_e( 'Publish the live file automatically without manual review', 'ailinkmaster-internal-auto-manager' ); ?>
     245                                                                        </label>
     246                                                                        <p class="description"><?php esc_html_e( 'Off (recommended): automatic rebuilds only update the draft; you publish manually. On: every rebuild writes the live file directly.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     247                                                                </td>
     248                                                        </tr>
     249                                                        <tr>
     250                                                                <th scope="row"><?php esc_html_e( 'AI descriptions', 'ailinkmaster-internal-auto-manager' ); ?></th>
     251                                                                <td>
     252                                                                        <label>
     253                                                                                <input type="checkbox" id="ailm-llmstxt-use-summaries" value="1" <?php checked( ! empty( $settings['use_summaries'] ) ); ?> />
     254                                                                                <?php esc_html_e( 'Use cached AI summaries (from the LinkMesh index) as descriptions', 'ailinkmaster-internal-auto-manager' ); ?>
     255                                                                        </label>
     256                                                                        <p class="description"><?php esc_html_e( 'Summaries are synced from the server once per day or via the button above. Falls back to post excerpts.', 'ailinkmaster-internal-auto-manager' ); ?></p>
     257                                                                </td>
     258                                                        </tr>
     259                                                        <tr>
     260                                                                <th scope="row"><label for="ailm-llmstxt-manual-sections"><?php esc_html_e( 'Manual sections', 'ailinkmaster-internal-auto-manager' ); ?></label></th>
     261                                                                <td>
     262                                                                        <textarea class="large-text" rows="6" id="ailm-llmstxt-manual-sections"><?php echo esc_textarea( $settings['manual_sections'] ); ?></textarea>
     263                                                                        <p class="description"><?php esc_html_e( 'Optional Markdown appended after the site description (e.g. custom H2 sections with links).', 'ailinkmaster-internal-auto-manager' ); ?></p>
     264                                                                </td>
     265                                                        </tr>
     266                                                </tbody>
     267                                        </table>
     268
     269                                        <p>
     270                                                <button type="submit" class="button button-primary" id="ailm-llmstxt-save"><?php esc_html_e( 'Save settings', 'ailinkmaster-internal-auto-manager' ); ?></button>
     271                                        </p>
     272                                </form>
    46273                        </div>
    47274
    … …  
    49276                <?php
    50277        }
     278
     279        /**
     280         * Enqueue the module's admin script + select2 (admin_enqueue_scripts).
     281         *
     282         * @param string $hook_suffix Current admin page hook suffix.
     283         * @return void
     284         */
     285        public static function enqueue_assets( $hook_suffix = '' ) {
     286                // Only load on the module's own page (hook contains the plugin
     287                // slug + '-llms-txt').
     288                if ( false === strpos( (string) $hook_suffix, 'llms-txt' ) ) {
     289                        return;
     290                }
     291
     292                if ( ! current_user_can( 'manage_options' ) ) {
     293                        return;
     294                }
     295
     296                $plugin_url = plugins_url( '', dirname( dirname( dirname( __FILE__ ) ) ) . '/ailinkmaster-internal-auto-manager.php' );
     297
     298                // Reuse the plugin-wide select2 when it is already queued. Loading a
     299                // second select2 build on the same page replaces $.fn.select2 and
     300                // leaves the pickers without a working widget, so the bundled copy
     301                // is only a fallback for pages where the shared handle is missing.
     302                $select2_handle = 'select2';
     303                if ( ! wp_script_is( 'select2', 'enqueued' ) && ! wp_script_is( 'select2', 'registered' ) ) {
     304                        $select2_handle = 'ailm-select2';
     305                        wp_enqueue_style( 'ailm-select2', $plugin_url . '/assets/select2.min.css', array(), '4.0.13' );
     306                        wp_enqueue_script( 'ailm-select2', $plugin_url . '/assets/select2.min.js', array( 'jquery' ), '4.0.13', true );
     307                }
     308
     309                // Cache-bust on file mtime so an updated picker script is never
     310                // served from the browser cache after a plugin update.
     311                $script_path = dirname( __FILE__ ) . '/assets/admin-llmstxt.js';
     312                $script_ver  = file_exists( $script_path ) ? (string) filemtime( $script_path ) : '34.1.8';
     313
     314                wp_enqueue_script(
     315                        'ailm-llmstxt-admin',
     316                        plugins_url( 'assets/admin-llmstxt.js', __FILE__ ),
     317                        array( 'jquery', $select2_handle ),
     318                        $script_ver,
     319                        true
     320                );
     321
     322                // Pre-select the currently chosen posts in the select2 boxes.
     323                $selected = self::get_picker_items( (array) AILM_LlmsTxt_Settings::get( 'selected_posts', array() ) );
     324                $pinned   = self::get_picker_items( (array) AILM_LlmsTxt_Settings::get( 'pinned_pages', array() ) );
     325
     326                wp_localize_script(
     327                        'ailm-llmstxt-admin',
     328                        'ailm_llmstxt',
     329                        array(
     330                                'nonce'    => wp_create_nonce( AILM_LlmsTxt_Ajax::NONCE_ACTION ),
     331                                'ajaxurl'  => admin_url( 'admin-ajax.php' ),
     332                                'selected' => $selected,
     333                                'pinned'   => $pinned,
     334                        )
     335                );
     336        }
     337
     338        /**
     339         * Map stored post IDs to select2 items ({id, text}).
     340         *
     341         * @param array $ids Post IDs in the stored order.
     342         * @return array List of picker items.
     343         */
     344        private static function get_picker_items( array $ids ) {
     345                $items = array();
     346
     347                foreach ( $ids as $pid ) {
     348                        $pid  = absint( $pid );
     349                        $post = $pid ? get_post( $pid ) : null;
     350
     351                        if ( ! $post ) {
     352                                continue;
     353                        }
     354
     355                        // select2 renders the text as plain text, and the label is
     356                        // stored as-is for display only (no HTML is emitted).
     357                        $items[] = array(
     358                                'id'   => (int) $pid,
     359                                'text' => $post->post_title . ' (' . $post->post_type . ')',
     360                        );
     361                }
     362
     363                return $items;
     364        }
    51365}
  • ailinkmaster-internal-auto-manager/trunk/modules/orphan-rescue/class-ailm-orphan-admin.php

    r3624641 r3693501  
    4040        $all_post_ids = get_posts(array(
    4141            'numberposts' => -1,
    42             'post_type' => 'post',
     42            'post_type' => get_option('ailm_allowed_post_types', array('post', 'page')),
    4343            'post_status' => 'publish',
    4444            'fields' => 'ids',
    … …  
    331331        global $wpdb;
    332332
     333        // Only valid post type slugs may enter the SQL fragment below. The
     334        // option is sanitized and whitelisted here, so the placeholder list
     335        // can never carry user input into the query string.
     336        $allowed_types = get_option('ailm_allowed_post_types', array('post', 'page'));
     337        $allowed_types = array_values(array_unique(array_filter(array_map('sanitize_key', (array) $allowed_types))));
     338        if (empty($allowed_types)) {
     339            $allowed_types = array('post');
     340        }
     341        $type_placeholders = implode(',', array_fill(0, count($allowed_types), '%s'));
     342        $posts_table       = $wpdb->posts; // Core table name, never user input.
     343
     344        // The two interpolations below are trusted: $posts_table is the core
     345        // posts table and $type_placeholders is a fixed run of "%s" built from
     346        // the sanitized slugs above — no user input reaches the query string.
     347        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- see note above: core table + an equivalent number of %s placeholders
    333348        $total_posts = 0;
    334349        if ($offset === 0) {
    335             // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    336             $total_posts = (int) $wpdb->get_var("SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post'");
    337         }
    338 
    339         // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     350            $total_posts = (int) $wpdb->get_var($wpdb->prepare(
     351                "SELECT COUNT(ID) FROM {$posts_table} WHERE post_status = 'publish' AND post_type IN ($type_placeholders)",
     352                $allowed_types
     353            ));
     354        }
     355
    340356        $posts = $wpdb->get_results($wpdb->prepare(
    341             "SELECT ID, post_title, post_content FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type = 'post' ORDER BY ID DESC LIMIT %d OFFSET %d",
    342             $limit,
    343             $offset
     357            "SELECT ID, post_title, post_content FROM {$posts_table} WHERE post_status = 'publish' AND post_type IN ($type_placeholders) ORDER BY ID DESC LIMIT %d OFFSET %d",
     358            array_merge($allowed_types, array($limit, $offset))
    344359        ));
     360        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber
    345361
    346362        if (empty($posts)) {
  • ailinkmaster-internal-auto-manager/trunk/readme.txt

    r3651068 r3693501  
    44Requires at least: 6.2
    55Requires PHP: 7.4
    6 Tested up to: 7.0
    7 Stable tag: 34.1.7
     6Tested up to: 7.1
     7Stable tag: 34.1.8
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    6868Find and fix pages with zero inbound links – invisible to both users and search engines.
    6969
     70### LLMS.txt (AI Control)
     71Take charge of how ChatGPT, Claude, and other AI agents read and index your site's content.
     72
    7073### 404 Scout
    7174A background scanner that finds broken links before your visitors (or Google) do.
    … …  
    119122
    120123### Q3 2026 (In Progress)
    121 - llms.txt & Markdown Endpoint
     124- DONE! llms.txt & Markdown Endpoint
    122125- DONE! (Bulk URL Update on Site Restructure)
    123126
  • ailinkmaster-internal-auto-manager/trunk/uninstall.php

    r3645483 r3693501  
    152152    'ailm_url_rewrite_last_heartbeat',
    153153    'ailm_url_rewrite_batch_lock',
     154
     155    // --- llms.txt module (modules/llms-txt/...) ---
     156    'ailm_llmstxt_settings',
     157    'ailm_llmstxt_summaries',
     158    'ailm_llmstxt_summaries_synced_at',
     159    'ailm_llmstxt_status',
     160    'ailm_llmstxt_file_llms',
     161    'ailm_llmstxt_file_llms_full',
     162    'ailm_llmstxt_draft',
    154163];
    155164
    … …  
    178187    '_ailm_pagerank',
    179188    '_ailm_needs_reindex',
     189
     190    // --- llms.txt module (modules/llms-txt/...) ---
     191    '_ailm_llmstxt_dirty',
     192    '_ailm_llmstxt_md_hash',
     193    '_ailm_llmstxt_md_body',
    180194];
    181195
    … …  
    228242    // Auto-reindex cron (includes/class-ailm-core.php).
    229243    'ailm_auto_reindex_cron',
     244    // llms.txt module crons (modules/llms-txt/class-ailm-llmstxt-rebuilder.php).
     245    'ailm_llmstxt_rebuild_cron',
     246    'ailm_llmstxt_daily_sync',
    230247];
    231248
    … …  
    233250    wp_clear_scheduled_hook( $ailm_cron_hook );
    234251}
     252
     253/*
     254 * ===========================================================================
     255 * 6. PHYSICAL FILES (llms.txt module)
     256 * ===========================================================================
     257 *
     258 * Static files written to the site root by the llms.txt module. Removed on
     259 * uninstall so no stale llms.txt / llms-full.txt is left behind.
     260 */
     261$ailm_llmstxt_home = function_exists( 'get_home_path' ) ? get_home_path() : ABSPATH;
     262foreach ( array( 'llms.txt', 'llms-full.txt' ) as $ailm_llmstxt_file ) {
     263    $ailm_llmstxt_path = wp_normalize_path( $ailm_llmstxt_home . $ailm_llmstxt_file );
     264    if ( is_file( $ailm_llmstxt_path ) ) {
     265        wp_delete_file( $ailm_llmstxt_path );
     266    }
     267}
     268
     269foreach ( $ailm_cron_hooks as $ailm_cron_hook ) {
     270    wp_clear_scheduled_hook( $ailm_cron_hook );
     271}
Note: See TracChangeset for help on using the changeset viewer.