get_var( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s LIMIT 1", $wpdb->esc_like( 'byfaro_' ) . '%', $wpdb->esc_like( '_transient_byfaro_' ) . '%' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- once per site, when byfaro sets it up. Byfaro_Visitors::create_table(); // The always-there byfaro button in the editors starts off on a new install // (Jack, 20 Oct); a site that already had byfaro keeps it on. add_option( Byfaro_Guide::ALWAYS, $earlier ? 1 : 0 ); // New installs start with the summary for AI (llms.txt) off: turning it on // is one of byfaro's fixes, from Site settings or the Issues screen // (Jack, 2026-09-17). add_option leaves an existing choice alone, and an // earlier install without one keeps what it had (on). add_option( 'byfaro_llms_enabled', $earlier ? 1 : 0 ); if ( ! wp_next_scheduled( 'byfaro_daily_maintenance' ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'byfaro_daily_maintenance' ); } if ( ! wp_next_scheduled( 'byfaro_weekly_scan' ) ) { wp_schedule_event( time() + DAY_IN_SECONDS, 'weekly', 'byfaro_weekly_scan' ); } update_option( 'byfaro_site_ready', 1 ); } register_activation_hook( __FILE__, function () { byfaro_setup_site(); // One notice on the Plugins screen, after the first activation only: no // redirect, and once dismissed it stays dismissed (WordPress.org // guideline 11, review of 26 Oct). add_option( 'byfaro_welcome_notice', 1, '', false ); } ); // Multisite: "Network Activate" runs the activation above on the main site only, so // every other site sets itself up on its first page load, before byfaro's visitor log // needs its table (V1 mock test P2-8, 21 Oct: AI visits to a subsite hit a missing // table, llms.txt started on and no weekly scan was scheduled). Single sites never // need it: their activation did it. add_action( 'init', function () { if ( is_multisite() && ! get_option( 'byfaro_site_ready' ) ) { byfaro_setup_site(); } }, 1 ); register_deactivation_hook( __FILE__, function () { wp_clear_scheduled_hook( 'byfaro_daily_maintenance' ); wp_clear_scheduled_hook( 'byfaro_weekly_scan' ); wp_clear_scheduled_hook( 'byfaro_inside_gather' ); wp_clear_scheduled_hook( 'byfaro_live_retry' ); wp_clear_scheduled_hook( 'byfaro_live_saved' ); wp_clear_scheduled_hook( 'byfaro_business_send' ); } ); // Purge visitor-log rows older than 90 days, keep the cached status warm, send // any change-history entries that didn't reach the byfaro account yet, and scan // from inside WordPress when byfaro.ai asks because the score it shows is old. add_action( 'byfaro_daily_maintenance', function () { Byfaro_Visitors::purge_old(); if ( Byfaro_Api::is_connected() ) { $status = Byfaro_Api::refresh_status(); Byfaro_Changes::mirror_pending(); Byfaro_Api::maybe_requested_scan( $status ); } } ); // The weekly automatic scan. The server decides whether the plan allows it: // this cron only asks. Free accounts get a polite 403 and nothing happens. // The scan runs in the background on byfaro; the next status refresh shows it. add_action( 'byfaro_weekly_scan', function () { if ( Byfaro_Api::is_connected() ) { Byfaro_Api::request_scan( true ); } } );