Plugin Directory

Changeset 3488558


Ignore:
Timestamp:
03/23/2026 05:34:01 AM (9 days ago)
Author:
bsolveit
Message:

v1.3.1: CDN-compatible discovery file analytics

  • Move bot detection into serve_file() for reliable CDN/cache compatibility
  • Set s-maxage=0 on discovery file responses
  • Scope analytics to discovery file access only
  • Consolidate to single dashboard widget
  • Update UI labels for file-access-only scope
Location:
ai-discovery-files
Files:
56 added
1 deleted
10 edited

Legend:

Unmodified
Added
Removed
  • ai-discovery-files/trunk/admin/class-admin.php

    r3488445 r3488558  
    700700
    701701        wp_add_dashboard_widget(
    702             'aidf_crawler_activity',
    703             __( 'AI Crawler Activity', 'ai-discovery-files' ),
    704             array( __CLASS__, 'render_crawler_activity_widget' )
    705         );
    706 
    707         wp_add_dashboard_widget(
    708702            'aidf_file_access',
    709703            __( 'AI Discovery File Access', 'ai-discovery-files' ),
     
    713707
    714708    /**
    715      * Render the Crawler Activity dashboard widget.
    716      *
    717      * @since 1.3.0
    718      */
    719     public static function render_crawler_activity_widget() {
    720         include AIDF_PLUGIN_DIR . 'admin/views/partials/crawler-dashboard-widget.php';
    721     }
    722 
    723     /**
    724709     * Render the File Access dashboard widget.
    725710     *
     
    746731        <style>
    747732        /* AI Discovery Files — Dashboard Widget Styles */
    748         .aidf-widget-top-bots { color: #5a5d6b; font-size: 13px; }
    749         .aidf-widget-warning { color: #d97706; font-size: 13px; }
    750         .aidf-widget-warning .dashicons { font-size: 16px; width: 16px; height: 16px; vertical-align: text-bottom; }
    751733        .aidf-widget-empty { color: #8b8fa3; font-style: italic; }
    752734        .aidf-widget-link { text-align: right; margin-bottom: 0; }
     
    796778     *
    797779     * Returns detailed analytics for a single bot: daily trend, status code
    798      * breakdown, pages visited, discovery file access, and first/last seen.
     780     * breakdown, files accessed, discovery file access, and first/last seen.
    799781     *
    800782     * @since 1.3.0
  • ai-discovery-files/trunk/admin/js/crawlers.js

    r3488445 r3488558  
    44 * Dashboard: period toggle, AJAX loading, chart rendering, summary cards,
    55 * breakdown table, file access panel, insights alerts.
     6 * Only discovery-file-level access is logged (not general page visits).
    67 * Settings: bot selector presets, save settings, clear log.
    78 *
     
    179180        $dashboard.addClass('aidf-dashboard--loading');
    180181
    181         // Load overview + file access in parallel.
     182        // Load overview + file access data in parallel.
    182183        var overviewReq = $.ajax({
    183184            url: aidf.ajaxUrl,
     
    435436                statusHtml = '<span class="aidf-status-badge aidf-status-badge--green">Active</span>';
    436437            } else {
    437                 statusHtml = '<span class="aidf-status-badge aidf-status-badge--gray">No visits</span>';
     438                statusHtml = '<span class="aidf-status-badge aidf-status-badge--gray">No activity</span>';
    438439            }
    439440
     
    445446                '<td>' + formatNumber(row.visit_count) + '</td>' +
    446447                '<td title="' + escHtml(row.last_seen || '') + '">' + timeAgo(row.last_seen) + '</td>' +
    447                 '<td><code class="aidf-mono-sm">' + escHtml(row.top_page || '\u2014') + '</code></td>' +
     448                '<td><code class="aidf-mono-sm">' + escHtml(row.top_file || row.top_page || '\u2014') + '</code></td>' +
    448449                '<td>' + statusHtml + '</td>' +
    449450            '</tr>';
     
    902903
    903904    /**
    904      * Render a single-colour bar chart for daily visit trend.
     905     * Render a single-colour bar chart for daily access trend.
    905906     * Bars are proportional to the day with the highest count.
    906907     */
     
    910911        var dates = Object.keys(dailyTrend).sort();
    911912        if (!dates.length) {
    912             $container.html('<p class="aidf-text-muted">No visit data in this period.</p>');
     913            $container.html('<p class="aidf-text-muted">No access data in this period.</p>');
    913914            return;
    914915        }
     
    936937            var label = parts.length === 3 ? parseInt(parts[1], 10) + '/' + parseInt(parts[2], 10) : date;
    937938
    938             html += '<div class="aidf-detail-trend__bar" title="' + escHtml(date) + ': ' + formatNumber(count) + ' visits">' +
     939            html += '<div class="aidf-detail-trend__bar" title="' + escHtml(date) + ': ' + formatNumber(count) + ' accesses">' +
    939940                '<div class="aidf-detail-trend__fill" style="height:' + pct + '%;"></div>' +
    940941                '<span class="aidf-detail-trend__label">' + escHtml(label) + '</span>' +
     
    10071008
    10081009    /**
    1009      * Render the pages visited table, sorted by visit count descending.
    1010      * Limited to top 50 pages to keep the view manageable.
     1010     * Render the files accessed table, sorted by access count descending.
     1011     * Limited to top 50 files to keep the view manageable.
    10111012     */
    10121013    function renderDetailPages(pages) {
     
    10151016        var paths = Object.keys(pages);
    10161017        if (!paths.length) {
    1017             $container.html('<p class="aidf-text-muted">No page visit data available.</p>');
     1018            $container.html('<p class="aidf-text-muted">No file access data available.</p>');
    10181019            return;
    10191020        }
     
    10291030        var html = '<table class="aidf-detail-table">' +
    10301031            '<thead><tr>' +
    1031             '<th>URL Path</th>' +
    1032             '<th>Visits</th>' +
     1032            '<th>File</th>' +
     1033            '<th>Accesses</th>' +
    10331034            '<th>Last Access</th>' +
    10341035            '</tr></thead>' +
     
    10481049
    10491050        if (paths.length > 50) {
    1050             html += '<p class="aidf-detail-table__note">Showing top 50 of ' + formatNumber(paths.length) + ' pages. Export the log for the full list.</p>';
     1051            html += '<p class="aidf-detail-table__note">Showing top 50 of ' + formatNumber(paths.length) + ' files. Export the log for the full list.</p>';
    10511052        }
    10521053
     
    12721273            '<th>Time</th>' +
    12731274            '<th>Bot</th>' +
    1274             '<th>URL Path</th>' +
     1275            '<th>File</th>' +
    12751276            '<th>Status</th>' +
    12761277            '</tr></thead>' +
  • ai-discovery-files/trunk/admin/views/partials/crawler-bot-detail.php

    r3488445 r3488558  
    3535    </div>
    3636
    37     <!-- Visit trend chart -->
     37    <!-- Access trend chart -->
    3838    <div class="aidf-panel aidf-bot-detail__panel">
    3939        <div class="aidf-panel-header">
    40             <h4 class="aidf-panel-title"><?php esc_html_e( 'Visit Trend', 'ai-discovery-files' ); ?></h4>
     40            <h4 class="aidf-panel-title"><?php esc_html_e( 'Access Trend', 'ai-discovery-files' ); ?></h4>
    4141        </div>
    4242        <div class="aidf-panel-body" id="aidf-bot-detail-chart"></div>
     
    5151    </div>
    5252
    53     <!-- Pages visited -->
     53    <!-- Files accessed -->
    5454    <div class="aidf-panel aidf-bot-detail__panel">
    5555        <div class="aidf-panel-header">
    56             <h4 class="aidf-panel-title"><?php esc_html_e( 'Pages Visited', 'ai-discovery-files' ); ?></h4>
     56            <h4 class="aidf-panel-title"><?php esc_html_e( 'Files Accessed', 'ai-discovery-files' ); ?></h4>
    5757        </div>
    5858        <div class="aidf-panel-body" id="aidf-bot-detail-pages"></div>
  • ai-discovery-files/trunk/admin/views/partials/crawler-log-viewer.php

    r3488445 r3488558  
    5151                    id="aidf-log-filter-path"
    5252                    class="aidf-log-filter__path"
    53                     placeholder="<?php esc_attr_e( 'Filter by URL path...', 'ai-discovery-files' ); ?>"
     53                    placeholder="<?php esc_attr_e( 'Filter by file...', 'ai-discovery-files' ); ?>"
    5454                >
    5555                <button type="button" class="aidf-btn aidf-btn--sm aidf-btn--primary" id="aidf-apply-log-filters">
  • ai-discovery-files/trunk/admin/views/tab-crawlers.php

    r3488445 r3488558  
    7373                <span class="dashicons dashicons-chart-bar"></span>
    7474                <h3 class="aidf-empty-state__heading"><?php esc_html_e( 'AI Crawler Analytics', 'ai-discovery-files' ); ?></h3>
    75                 <p class="aidf-empty-state__description"><?php esc_html_e( 'See which AI bots visit your site, which pages they access, and whether any are being blocked.', 'ai-discovery-files' ); ?></p>
     75                <p class="aidf-empty-state__description"><?php esc_html_e( 'See which AI bots access your discovery files and whether any are being blocked.', 'ai-discovery-files' ); ?></p>
    7676                <button type="button" class="aidf-btn aidf-btn--primary" id="aidf-enable-logging-cta"><?php esc_html_e( 'Enable Logging', 'ai-discovery-files' ); ?></button>
    7777            </div>
     
    9393                <div class="aidf-stat-card aidf-stat-card--muted">
    9494                    <div class="aidf-stat-card__value">0</div>
    95                     <div class="aidf-stat-card__label"><?php esc_html_e( 'Total Visits', 'ai-discovery-files' ); ?></div>
     95                    <div class="aidf-stat-card__label"><?php esc_html_e( 'File Accesses', 'ai-discovery-files' ); ?></div>
    9696                </div>
    9797                <div class="aidf-stat-card aidf-stat-card--muted">
     
    112112            <div class="aidf-notice aidf-notice--info" style="margin: 0;">
    113113                <span class="dashicons dashicons-info"></span>
    114                 <span><?php esc_html_e( 'Logging is active. Data will appear as AI crawlers visit your site — this typically takes 24-48 hours.', 'ai-discovery-files' ); ?></span>
     114                <span><?php esc_html_e( 'Logging is active. Data will appear as AI crawlers access your discovery files — this typically takes 24-48 hours.', 'ai-discovery-files' ); ?></span>
    115115            </div>
    116116
     
    131131                <div class="aidf-stat-card">
    132132                    <div class="aidf-stat-card__value" data-card="total_visits"><?php echo esc_html( number_format_i18n( $aidf_summary['total_visits'] ) ); ?></div>
    133                     <div class="aidf-stat-card__label"><?php esc_html_e( 'Total Visits', 'ai-discovery-files' ); ?></div>
     133                    <div class="aidf-stat-card__label"><?php esc_html_e( 'File Accesses', 'ai-discovery-files' ); ?></div>
    134134                </div>
    135135                <div class="aidf-stat-card">
     
    160160                <table class="aidf-sr-only" id="aidf-chart-data-table">
    161161                    <caption><?php esc_html_e( 'AI crawler activity over the selected period', 'ai-discovery-files' ); ?></caption>
    162                     <thead><tr><th><?php esc_html_e( 'Date', 'ai-discovery-files' ); ?></th><th><?php esc_html_e( 'Bot', 'ai-discovery-files' ); ?></th><th><?php esc_html_e( 'Visits', 'ai-discovery-files' ); ?></th></tr></thead>
     162                    <thead><tr><th><?php esc_html_e( 'Date', 'ai-discovery-files' ); ?></th><th><?php esc_html_e( 'Bot', 'ai-discovery-files' ); ?></th><th><?php esc_html_e( 'Accesses', 'ai-discovery-files' ); ?></th></tr></thead>
    163163                    <tbody id="aidf-chart-data-tbody"></tbody>
    164164                </table>
     
    175175                            <th><?php esc_html_e( 'Bot', 'ai-discovery-files' ); ?></th>
    176176                            <th><?php esc_html_e( 'Category', 'ai-discovery-files' ); ?></th>
    177                             <th><?php esc_html_e( 'Visits', 'ai-discovery-files' ); ?></th>
     177                            <th><?php esc_html_e( 'Accesses', 'ai-discovery-files' ); ?></th>
    178178                            <th><?php esc_html_e( 'Last Seen', 'ai-discovery-files' ); ?></th>
    179                             <th><?php esc_html_e( 'Top Page', 'ai-discovery-files' ); ?></th>
     179                            <th><?php esc_html_e( 'Top File', 'ai-discovery-files' ); ?></th>
    180180                            <th><?php esc_html_e( 'Status', 'ai-discovery-files' ); ?></th>
    181181                        </tr>
     
    207207                                    <span class="aidf-status-badge aidf-status-badge--green"><?php esc_html_e( 'Active', 'ai-discovery-files' ); ?></span>
    208208                                <?php else : ?>
    209                                     <span class="aidf-status-badge aidf-status-badge--gray"><?php esc_html_e( 'No visits', 'ai-discovery-files' ); ?></span>
     209                                    <span class="aidf-status-badge aidf-status-badge--gray"><?php esc_html_e( 'No activity', 'ai-discovery-files' ); ?></span>
    210210                                <?php endif; ?>
    211211                            </td>
     
    316316                <div class="aidf-setting-row__label">
    317317                    <label for="aidf-crawler-logging"><?php esc_html_e( 'Enable Crawler Logging', 'ai-discovery-files' ); ?></label>
    318                     <p class="aidf-setting-row__help"><?php esc_html_e( 'When enabled, the plugin detects and logs AI crawler visits to your site. Disabled by default — no performance impact when off.', 'ai-discovery-files' ); ?></p>
     318                    <p class="aidf-setting-row__help"><?php esc_html_e( 'When enabled, the plugin detects and logs AI crawler access to your discovery files. Disabled by default — no performance impact when off.', 'ai-discovery-files' ); ?></p>
    319319                </div>
    320320                <div class="aidf-setting-row__control">
  • ai-discovery-files/trunk/ai-discovery-files.php

    r3488445 r3488558  
    44 * Plugin URI:        https://www.ai-visibility.org.uk/wordpress-plugin/ai-discovery-files/
    55 * Description:       The only WordPress plugin that generates all 10 AI Discovery Files and tracks which AI bots visit your site. Built by 365i.
    6  * Version:           1.3.0
     6 * Version:           1.3.1
    77 * Requires at least: 6.2
    88 * Requires PHP:      8.0
     
    2424 * Plugin constants.
    2525 */
    26 define( 'AIDF_VERSION', '1.3.0' );
     26define( 'AIDF_VERSION', '1.3.1' );
    2727define( 'AIDF_PLUGIN_FILE', __FILE__ );
    2828define( 'AIDF_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
  • ai-discovery-files/trunk/includes/class-crawler-logger.php

    r3488445 r3488558  
    11<?php
    22/**
    3  * Crawler logger — captures AI bot hits and flushes them to the database.
     3 * Crawler logger — logs AI bot access to discovery files served by AIDF_Server.
    44 *
    5  * Hooks into `init` at priority 1 to detect bots by user agent, buffers
    6  * any matched hit in memory, then writes to the database on `shutdown`
    7  * so the final HTTP status code is available at flush time.
     5 * Called directly from AIDF_Server::serve_file() after a file is generated
     6 * successfully. This approach guarantees every request reaches PHP even on
     7 * sites with page caching or CDN edge caching, because discovery file
     8 * responses use s-maxage=0.
    89 *
    910 * @package AIDF
     
    1617
    1718/**
    18  * Handles AI crawler detection, hit buffering, and log persistence.
     19 * Handles AI crawler detection and log persistence for discovery file requests.
    1920 *
    20  * All public methods are static so the class can be called without an
    21  * instance — it is effectively a service with class-level state held
    22  * in the private static $buffer array.
     21 * All public methods are static so the class can be called without an instance.
    2322 *
    2423 * @since 1.3.0
     
    2726
    2827    /**
    29      * In-memory buffer of detected bot hits for the current request.
     28     * Detect and log an AI crawler hit for a discovery file request.
    3029     *
    31      * Each entry is an associative array with keys:
    32      *   - bot_name (string) Human-readable bot name.
    33      *   - bot_ua   (string) Raw user-agent string, truncated to 500 chars.
    34      *   - url_path (string) Requested URL path, truncated to 500 chars.
     30     * Called from AIDF_Server::serve_file() after the file content has been
     31     * generated successfully. Checks the user agent against the enabled bot
     32     * registry and writes a single row to the crawler log table when matched.
     33     *
     34     * Reads the raw aidf_settings option directly instead of
     35     * AIDF_Plugin::get_settings() to avoid triggering page URL lookups
     36     * that require $wp_rewrite — which may not be fully initialised at
     37     * serve_file() time.
    3538     *
    3639     * @since 1.3.0
    37      * @var   array<int, array<string, string>>
    38      */
    39     private static $buffer = array();
    40 
    41     /**
    42      * Initialise the logger for the current request.
    4340     *
    44      * Reads the plugin settings and, when logging is enabled, registers
    45      * the `detect_bot` and `flush_buffer` hooks. Admin, AJAX, and cron
    46      * requests are skipped immediately; REST API requests are skipped
    47      * inside `detect_bot` because the REST_REQUEST constant is not yet
    48      * defined at `plugins_loaded` time.
    49      *
    50      * @since 1.3.0
     41     * @param string $file_slug The file type slug (e.g. 'llms-txt', 'ai-txt').
    5142     * @return void
    5243     */
    53     public static function init() {
    54         // Read the raw option directly instead of AIDF_Plugin::get_settings()
    55         // because get_settings() calls get_defaults() which triggers page URL
    56         // lookups that need $wp_rewrite — not yet available at plugins_loaded time.
     44    public static function maybe_log_hit( $file_slug ) {
     45        // Read the raw option to avoid the $wp_rewrite timing issue.
    5746        $saved = get_option( 'aidf_settings', array() );
    5847
    5948        if ( empty( $saved['crawler_logging_enabled'] ) ) {
    60             return;
    61         }
    62 
    63         // Only log front-end requests.
    64         if ( is_admin() || wp_doing_ajax() || wp_doing_cron() ) {
    65             return;
    66         }
    67 
    68         add_action( 'init', array( __CLASS__, 'detect_bot' ), 1 );
    69         add_action( 'shutdown', array( __CLASS__, 'flush_buffer' ) );
    70     }
    71 
    72     /**
    73      * Match the current request's user agent against enabled bot definitions.
    74      *
    75      * Runs on the `init` hook at priority 1. Skips REST API requests.
    76      * When a match is found the hit is added to the in-memory buffer;
    77      * the buffer is written to the database later in `flush_buffer`.
    78      *
    79      * @since 1.3.0
    80      * @return void
    81      */
    82     public static function detect_bot() {
    83         // Skip REST API requests (constant not available at plugins_loaded time).
    84         if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
    8549            return;
    8650        }
     
    9458        }
    9559
    96         $settings     = AIDF_Plugin::get_settings();
    97         $enabled_keys = $settings['crawler_enabled_bots'];
     60        // Build the enabled-only bot subset from the registry.
     61        $enabled_keys = isset( $saved['crawler_enabled_bots'] ) ? (array) $saved['crawler_enabled_bots'] : array();
    9862        $all_bots     = AIDF_Crawler_Registry::get_bots();
    9963
    100         // Build the enabled-only subset from the full bot registry.
    10164        $enabled_bots = array();
    10265        foreach ( $enabled_keys as $key ) {
     
    11275        }
    11376
    114         $url_path = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '/';
    115         $url_path = wp_parse_url( $url_path, PHP_URL_PATH );
    116         if ( ! is_string( $url_path ) ) {
    117             $url_path = '/';
    118         }
    119 
    120         self::$buffer[] = array(
    121             'bot_name' => $all_bots[ $matched_key ]['name'],
    122             'bot_ua'   => mb_substr( $ua, 0, 500 ),
    123             'url_path' => mb_substr( $url_path, 0, 500 ),
    124         );
    125     }
    126 
    127     /**
    128      * Write buffered hits to the database on shutdown.
    129      *
    130      * The status code is read here rather than at detection time because
    131      * WordPress has not determined the final HTTP response code until the
    132      * end of the request lifecycle. After flushing, the buffer is cleared.
    133      *
    134      * @since 1.3.0
    135      * @return void
    136      */
    137     public static function flush_buffer() {
    138         if ( empty( self::$buffer ) ) {
    139             return;
    140         }
     77        // Build the URL path from the known file type metadata.
     78        $file_types = AIDF_Plugin::get_file_types();
     79        $url_path   = isset( $file_types[ $file_slug ] )
     80            ? '/' . $file_types[ $file_slug ]['filename']
     81            : '/' . $file_slug;
    14182
    14283        global $wpdb;
    14384
    14485        $table       = $wpdb->prefix . 'aidf_crawler_log';
    145         $status_code = http_response_code();
     86        $status_code = 200;
    14687        $now         = current_time( 'mysql', true );
    14788
    148         foreach ( self::$buffer as $hit ) {
    149             $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
    150                 $table,
    151                 array(
    152                     'bot_name'    => $hit['bot_name'],
    153                     'bot_ua'      => $hit['bot_ua'],
    154                     'url_path'    => $hit['url_path'],
    155                     'status_code' => $status_code ? $status_code : 200,
    156                     'created_at'  => $now,
    157                 ),
    158                 array( '%s', '%s', '%s', '%d', '%s' )
    159             );
     89        $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
     90            $table,
     91            array(
     92                'bot_name'    => $all_bots[ $matched_key ]['name'],
     93                'bot_ua'      => mb_substr( $ua, 0, 500 ),
     94                'url_path'    => mb_substr( $url_path, 0, 500 ),
     95                'status_code' => $status_code,
     96                'created_at'  => $now,
     97            ),
     98            array( '%s', '%s', '%s', '%d', '%s' )
     99        );
    160100
    161             /**
    162              * Fires after a crawler hit has been written to the log.
    163              *
    164              * @since 1.3.0
    165              *
    166              * @param string $bot_name    Human-readable bot name.
    167              * @param string $url_path    Requested URL path.
    168              * @param int    $status_code HTTP response status code.
    169              */
    170             do_action( 'aidf_crawler_hit_logged', $hit['bot_name'], $hit['url_path'], $status_code );
    171         }
    172 
    173         self::$buffer = array();
     101        /**
     102         * Fires after a crawler hit has been written to the log.
     103         *
     104         * @since 1.3.0
     105         *
     106         * @param string $bot_name    Human-readable bot name.
     107         * @param string $url_path    Requested URL path (e.g. /llms.txt).
     108         * @param int    $status_code HTTP response status code (always 200).
     109         */
     110        do_action( 'aidf_crawler_hit_logged', $all_bots[ $matched_key ]['name'], $url_path, $status_code );
    174111    }
    175112
  • ai-discovery-files/trunk/includes/class-plugin.php

    r3488445 r3488558  
    3939     */
    4040    private function __construct() {
    41         AIDF_Crawler_Logger::init();
    4241        AIDF_Server::init();
    4342
  • ai-discovery-files/trunk/includes/class-server.php

    r3474794 r3488558  
    143143        }
    144144
     145        // Log AI crawler access when logging is enabled.
     146        if ( class_exists( 'AIDF_Crawler_Logger' ) ) {
     147            AIDF_Crawler_Logger::maybe_log_hit( $file_slug );
     148        }
     149
    145150        // Send appropriate headers.
    146151        status_header( 200 );
    147152        header( 'Content-Type: ' . $meta['content_type'] );
    148         header( 'Cache-Control: public, max-age=3600, s-maxage=43200' );
     153        header( 'Cache-Control: public, max-age=3600, s-maxage=0' );
    149154        header( 'X-Robots-Tag: noindex' );
    150155        header( 'X-AIDF-Generator: AI Discovery Files for WordPress/' . AIDF_VERSION );
  • ai-discovery-files/trunk/readme.txt

    r3488445 r3488558  
    55Tested up to: 6.9
    66Requires PHP: 8.0
    7 Stable tag: 1.3.0
     7Stable tag: 1.3.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 The only WordPress plugin that generates all 10 AI Discovery Files and tracks which AI bots visit your site. Built by 365i.
     11The only WordPress plugin that generates all 10 AI Discovery Files and shows which AI bots read them. Built by 365i.
    1212
    1313== Description ==
     
    6666* **Live preview** — see exactly what each file contains before enabling it
    6767* **Validation** — checks files against the specification and flags issues
    68 * **AI Crawler Analytics** — see which AI bots visit your site, how often, and which pages they access
     68* **AI Crawler Analytics** — see which AI bots read your AI Discovery Files, with visual dashboards and CSV export
    6969* **Discovery File Access tracking** — proof that AI bots are reading the files this plugin generates
    7070* **robots.txt conflict detection** — warns when your robots.txt contradicts your AI visibility settings
     
    150150= What is AI Crawler Analytics? =
    151151
    152 AI Crawler Analytics shows you which AI bots are visiting your site — GPTBot, ClaudeBot, PerplexityBot, Applebot, and 40+ others. You can see how often they visit, which pages they access, and whether any are being blocked by your robots.txt. It also shows which of your AI Discovery Files are being read by AI crawlers, giving you proof that the plugin is working.
     152AI Crawler Analytics tracks which AI bots read the AI Discovery Files this plugin generates — llms.txt, ai.txt, identity.json, and the rest. You see exactly which bots accessed which files, how often, and whether any are being blocked by your robots.txt. This gives you direct proof that GPTBot, ClaudeBot, PerplexityBot, and other AI crawlers are consuming your files. The feature works reliably on every hosting platform — including sites with CDN edge caching — because the plugin controls the discovery file responses. Includes bot detail drill-downs, a filterable activity log with CSV export, and two WordPress dashboard widgets.
    153153
    154154= Will the crawler analytics slow down my website? =
    155155
    156 No. When crawler logging is enabled, the plugin checks each request's user agent against a list of known AI bots. For non-bot requests (99.9% of traffic), this check takes less than a millisecond and involves no database queries. Bot hits are buffered in memory and written to the database once per request on shutdown. When logging is disabled (the default), the check is not registered at all — zero overhead.
     156No. The plugin only tracks access to AI Discovery File URLs that it serves (e.g., `/llms.txt`, `/ai.txt`). Normal page loads are completely unaffected — no user agent checks, no database queries, no overhead at all. When a discovery file is served, the plugin logs which bot accessed it. When logging is disabled (the default), no tracking runs at all — zero overhead.
    157157
    158158= Will this slow down my website? =
    159159
    160 No. The plugin only runs when its specific URLs are requested (e.g., `/llms.txt`). It adds zero overhead to your normal page loads. Files are generated on-the-fly from cached settings data. The crawler analytics feature is disabled by default and adds negligible overhead when enabled.
     160No. The plugin only runs when its specific URLs are requested (e.g., `/llms.txt`). It adds zero overhead to your normal page loads. Files are generated on-the-fly from cached settings data. The crawler analytics feature is disabled by default and only tracks discovery file access when enabled.
    161161
    162162= What happens if I deactivate the plugin? =
     
    179179== Changelog ==
    180180
     181= 1.3.1 =
     182* Fix: Move bot detection into discovery file server for reliable CDN/cache compatibility
     183* Fix: Set s-maxage=0 on discovery file responses so CDN edge caches pass through to origin
     184* Fix: Remove page-level tracking — all analytics now scoped to discovery file access only
     185* Fix: Consolidate to single dashboard widget (Discovery File Access)
     186* Fix: Update all UI labels and descriptions for file-access-only scope
     187
    181188= 1.3.0 =
    182 * New: AI Crawler Analytics — see which AI bots visit your site
    183 * New: Dashboard showing bot visits, frequency, and pages accessed
    184 * New: Discovery File Access panel — see which bots read your AI Discovery Files
     189* New: AI Crawler Analytics — see which AI bots access your AI Discovery Files
     190* New: Discovery File Access dashboard with bot breakdown and visual access bars
    185191* New: robots.txt conflict detection with actionable alerts
    186 * New: Categorised bot registry with 43 AI crawlers across 8 categories
     192* New: Bot detail drill-down with access trends and file breakdown
    187193* New: Filterable activity log viewer with CSV export
    188 * New: Bot detail drill-down with visit trends and page breakdown
    189 * New: Two WordPress dashboard widgets (Crawler Activity + File Access)
     194* New: WordPress dashboard widget (Discovery File Access)
     195* New: 50+ AI crawler definitions (GPTBot, ClaudeBot, PerplexityBot, GrokBot, and more)
    190196* New: User controls — enable/disable logging, data retention, bot selection
    191197
     
    229235== Upgrade Notice ==
    230236
     237= 1.3.1 =
     238Fix: Bot detection moved into discovery file server for reliable CDN/cache compatibility. All analytics now scoped to discovery file access. Works on every hosting platform.
     239
    231240= 1.3.0 =
    232 New: AI Crawler Analytics. See which AI bots visit your site, track Discovery File access, and detect robots.txt conflicts. Includes dashboard widgets, bot detail drill-downs, filterable log viewer, and CSV export.
     241New: AI Crawler Analytics. See which AI bots access your AI Discovery Files, detect robots.txt conflicts, and get proof your files are working. Includes dashboard widget, bot detail drill-downs, filterable log viewer, and CSV export.
    233242
    234243= 1.1.0 =
Note: See TracChangeset for help on using the changeset viewer.