prefix . 'citolume_crawler_hits'; } /** * Runs on activation. * * @return void */ function citolume_activate() { citolume_install(); if ( ! wp_next_scheduled( 'citolume_prune_hits' ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'citolume_prune_hits' ); } // The virtual /llms.txt route is a rewrite rule, so the rules have to be // rebuilt before it will resolve. citolume_register_rewrites(); flush_rewrite_rules(); } /** * Runs on deactivation. * * @return void */ function citolume_deactivate() { wp_clear_scheduled_hook( 'citolume_prune_hits' ); wp_clear_scheduled_hook( 'citolume_ping' ); flush_rewrite_rules(); } add_action( 'plugins_loaded', 'citolume_maybe_install' ); /** * Creates or upgrades the table when the schema version moves. * * Activation alone is not enough: a plugin updated in place never re-runs its * activation hook. * * @return void */ function citolume_maybe_install() { if ( (string) get_option( 'citolume_schema_version' ) === CITOLUME_SCHEMA_VERSION ) { return; } citolume_install(); } /** * Creates the crawler hit table. * * Hits are aggregated per bot, per URL, per day rather than stored one row per * request. A busy site can be crawled thousands of times a week, and nothing in * the product needs request level detail. * * @return void */ function citolume_install() { global $wpdb; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $table = citolume_table(); $collate = $wpdb->get_charset_collate(); // url_hash rather than the URL itself carries the unique index: a URL long // enough to matter would overflow the index size limit on older MySQL. $sql = "CREATE TABLE {$table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, bot varchar(32) NOT NULL, url varchar(255) NOT NULL, url_hash char(32) NOT NULL, hit_date date NOT NULL, hits int(10) unsigned NOT NULL DEFAULT 1, PRIMARY KEY (id), UNIQUE KEY bot_url_date (bot,url_hash,hit_date), KEY hit_date (hit_date), KEY bot_date (bot,hit_date) ) {$collate};"; dbDelta( $sql ); update_option( 'citolume_schema_version', CITOLUME_SCHEMA_VERSION, true ); } add_action( 'citolume_prune_hits', 'citolume_prune_hits' ); /** * Drops hits older than the retention window. * * @return void */ function citolume_prune_hits() { global $wpdb; $days = (int) apply_filters( 'citolume_retention_days', (int) get_option( 'citolume_retention_days', 90 ) ); $days = max( 7, $days ); $table = citolume_table(); $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- scheduled cleanup of the plugin's own table. $wpdb->prepare( "DELETE FROM {$wpdb->prefix}citolume_crawler_hits WHERE hit_date < %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is built from $wpdb->prefix. gmdate( 'Y-m-d', time() - ( $days * DAY_IN_SECONDS ) ) ) ); }