set_basename( false, __FILE__ ); return; } /* --------------------------------------------------------------------------- * Freemius SDK bootstrap (licensing, Pro activation, updates). * The public id/key are safe to ship; the SDK is bundled in /vendor/freemius. * Guarded so a build without the SDK degrades to the free tier instead of * fataling (ADG_AIM_Plugin::is_pro() falls back to the adg_aim_is_pro filter). * ------------------------------------------------------------------------- */ if ( !function_exists( 'amfac_fs' ) ) { /** * Freemius SDK accessor. * * @return Freemius|null Freemius instance, or null when the SDK is absent. */ function amfac_fs() { global $amfac_fs; if ( !isset( $amfac_fs ) ) { $fs_start = dirname( __FILE__ ) . '/vendor/freemius/start.php'; if ( !file_exists( $fs_start ) ) { return null; // SDK not bundled (stripped build) — run as free tier. } // Include Freemius SDK. require_once $fs_start; $amfac_fs = fs_dynamic_init( array( 'id' => '39140', 'slug' => 'adg-markdown-for-ai-crawlers', 'type' => 'plugin', 'public_key' => 'pk_70c20eb5151c82d0272776e323052', 'is_premium' => false, 'premium_suffix' => 'Pro', 'has_addons' => false, 'has_paid_plans' => true, 'is_org_compliant' => true, 'trial' => array( 'days' => 3, 'is_require_payment' => true, ), 'menu' => array( 'slug' => 'adg-markdown-for-ai-crawlers', 'parent' => array( 'slug' => 'options-general.php', ), 'support' => false, ), 'is_live' => true, ) ); } return $amfac_fs; } // Init Freemius. amfac_fs(); // Signal that SDK was initiated. do_action( 'amfac_fs_loaded' ); } define( 'ADG_AIM_VERSION', '2.1.1' ); define( 'ADG_AIM_FILE', __FILE__ ); define( 'ADG_AIM_DIR', plugin_dir_path( __FILE__ ) ); define( 'ADG_AIM_BASENAME', plugin_basename( __FILE__ ) ); // Unified option prefix. define( 'ADG_AIM_OPT_SETTINGS', 'adg_aim_settings' ); define( 'ADG_AIM_OPT_BOTS', 'adg_aim_bots' ); define( 'ADG_AIM_OPT_STATS', 'adg_aim_stats' ); // Pro — bot-visit analytics. define( 'ADG_AIM_OPT_REFERRALS', 'adg_aim_referrals' ); // Pro — AI referral tracking. define( 'ADG_AIM_OPT_LAST_SELFTEST', 'adg_aim_last_selftest' ); // Pro — self-test/readiness. define( 'ADG_AIM_OPT_LLMS_SELFTEST', 'adg_aim_llms_selftest' ); // Free — llms.txt server-block detection. require_once ADG_AIM_DIR . 'includes/class-html-to-markdown.php'; require_once ADG_AIM_DIR . 'includes/class-markdown-cache.php'; require_once ADG_AIM_DIR . 'includes/class-schema-generator.php'; require_once ADG_AIM_DIR . 'includes/class-frontmatter.php'; require_once ADG_AIM_DIR . 'includes/class-bot-detector.php'; require_once ADG_AIM_DIR . 'includes/class-content-negotiator.php'; require_once ADG_AIM_DIR . 'includes/class-host-detector.php'; require_once ADG_AIM_DIR . 'includes/class-llms-physical-writer.php'; require_once ADG_AIM_DIR . 'includes/class-llms-htaccess-fixer.php'; require_once ADG_AIM_DIR . 'includes/class-llms-generator.php'; require_once ADG_AIM_DIR . 'includes/class-admin-settings.php'; // Pro-only modules (stripped from the free wp.org build by Freemius). Guarded so // a build without these files degrades gracefully instead of fataling. foreach ( array( 'includes/class-analytics.php', // @fs_premium_only 'includes/class-referrals.php', // @fs_premium_only 'includes/class-token-savings.php', // @fs_premium_only 'includes/class-selftest.php', // @fs_premium_only 'includes/class-readiness.php', // @fs_premium_only 'includes/class-digest.php', // @fs_premium_only 'includes/class-robots.php', ) as $adg_aim_pro_file ) { $adg_aim_pro_path = ADG_AIM_DIR . $adg_aim_pro_file; if ( file_exists( $adg_aim_pro_path ) ) { require_once $adg_aim_pro_path; } } unset($adg_aim_pro_file, $adg_aim_pro_path); /** * Main plugin container. */ final class ADG_AIM_Plugin { /** * Singleton. * * @var ADG_AIM_Plugin|null */ private static $instance = null; /** * @var ADG_AIM_Bot_Detector */ public $bot_detector; /** * @var ADG_AIM_Content_Negotiator */ public $negotiator; /** * @var ADG_AIM_LLMS_Generator */ public $llms; /** * @var ADG_AIM_Admin_Settings */ public $admin; /** * @var ADG_AIM_Robots|null */ public $robots; /** * @var ADG_AIM_Referrals|null */ public $referrals; /** * @var ADG_AIM_Digest|null */ public $digest; /** * @return ADG_AIM_Plugin */ public static function instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->bot_detector = new ADG_AIM_Bot_Detector(); $this->negotiator = new ADG_AIM_Content_Negotiator($this->bot_detector); $this->llms = new ADG_AIM_LLMS_Generator(); if ( is_admin() ) { $this->admin = new ADG_AIM_Admin_Settings($this->bot_detector, $this->llms); $this->admin->hooks(); } $this->negotiator->hooks(); $this->llms->hooks(); ADG_AIM_Markdown_Cache::hooks(); // Pro: robots.txt management (only when licensed and the class is present). if ( self::is_pro() && class_exists( 'ADG_AIM_Robots' ) ) { $this->robots = new ADG_AIM_Robots($this->bot_detector); $this->robots->hooks(); } // Pro: AI referral tracking (human visits arriving from AI assistants). if ( self::is_pro() && class_exists( 'ADG_AIM_Referrals' ) ) { $this->referrals = new ADG_AIM_Referrals($this->bot_detector); $this->referrals->hooks(); } // Pro: weekly email digest (WP-Cron). if ( self::is_pro() && class_exists( 'ADG_AIM_Digest' ) ) { $this->digest = new ADG_AIM_Digest(); $this->digest->hooks(); } } /** * Default settings (used on activation and as a fallback). * * @return array */ public static function default_settings() { return array( 'negotiation_enabled' => true, 'post_types' => array('post', 'page'), 'allow_training' => true, 'allow_citation' => true, 'robots_manage' => false, 'stats_enabled' => true, 'referrals_enabled' => true, 'ip_verification' => false, 'weekly_digest' => false, 'digest_email' => '', 'schema_enabled' => true, 'markdown_cache' => true, 'tax_archives' => true, 'llms_enabled' => true, 'llms_site_desc' => '', 'llms_full_types' => array('post', 'page'), 'fm_title' => true, 'fm_url' => true, 'fm_date' => true, 'fm_author' => true, 'fm_tags' => true, 'fm_canonical' => true, 'meta_keys' => '', 'excluded_terms' => '', ); } /** * Term slugs the site owner chose to keep away from AI bots. * * @return string[] */ public static function excluded_term_slugs() { $raw = (string) self::get_setting( 'excluded_terms', '' ); $parts = preg_split( '/[\\r\\n,]+/', $raw ); $out = array(); foreach ( (array) $parts as $slug ) { $slug = sanitize_title( trim( $slug ) ); if ( '' !== $slug ) { $out[$slug] = $slug; } } return array_values( $out ); } /** * Whether a post must be hidden from AI bots: per-post checkbox * or membership in an excluded category/tag/term. * * @param WP_Post $post Post. * @return bool */ public static function is_hidden_from_ai( WP_Post $post ) { if ( get_post_meta( $post->ID, '_adg_hide_from_ai_bots', true ) ) { return true; } $slugs = self::excluded_term_slugs(); if ( empty( $slugs ) ) { return false; } $taxes = get_object_taxonomies( $post->post_type ); if ( empty( $taxes ) ) { return false; } $terms = wp_get_object_terms( $post->ID, $taxes, array( 'fields' => 'slugs', ) ); if ( is_wp_error( $terms ) || empty( $terms ) ) { return false; } return (bool) array_intersect( $slugs, $terms ); } /** * Get a setting. * * @param string $key Key. * @param mixed $default Default value. * @return mixed */ public static function get_setting( $key, $default = null ) { $settings = get_option( ADG_AIM_OPT_SETTINGS, array() ); $defaults = self::default_settings(); if ( is_array( $settings ) && isset( $settings[$key] ) ) { return $settings[$key]; } if ( null !== $default ) { return $default; } return ( isset( $defaults[$key] ) ? $defaults[$key] : null ); } /** * Whether Pro features are unlocked for this site. * * Backed by Freemius when the SDK is present (a valid license or active * trial → true); otherwise falls back to the `adg_aim_is_pro` filter, which * defaults to false (free tier). This is the single gate the whole plugin * checks — wrap every Pro-only feature and dashboard behind it. * * @return bool */ public static function is_pro() { if ( function_exists( 'amfac_fs' ) ) { $fs = amfac_fs(); if ( $fs && method_exists( $fs, 'can_use_premium_code' ) ) { return (bool) $fs->can_use_premium_code(); } } return (bool) apply_filters( 'adg_aim_is_pro', false ); } /** * Whether a given access category may receive the Pro treatment (Markdown * negotiation, robots Allow). Only meaningful when is_pro() is true. * * @param string $category 'training'|'citation'. * @return bool */ public static function category_allowed( $category ) { if ( 'training' === $category ) { return (bool) self::get_setting( 'allow_training', true ); } return (bool) self::get_setting( 'allow_citation', true ); } } /** * Activation: default settings, initial bot list, rewrite rules. */ function adg_aim_activate() { if ( false === get_option( ADG_AIM_OPT_SETTINGS ) ) { add_option( ADG_AIM_OPT_SETTINGS, ADG_AIM_Plugin::default_settings() ); } ADG_AIM_Bot_Detector::install_default_bots(); ADG_AIM_LLMS_Generator::register_rewrite_rules(); flush_rewrite_rules(); // Schedule the llms.txt self-test to run ~30 seconds after activation. if ( !wp_next_scheduled( 'adg_aim_llms_selftest_cron' ) ) { wp_schedule_single_event( time() + 30, 'adg_aim_llms_selftest_cron' ); } // Pro: schedule the weekly digest if enabled and licensed. if ( class_exists( 'ADG_AIM_Digest' ) ) { ADG_AIM_Digest::ensure_schedule(); } } register_activation_hook( __FILE__, 'adg_aim_activate' ); /** * Deactivation: clear rewrite rules and scheduled events (data stays until uninstall). */ function adg_aim_deactivate() { if ( class_exists( 'ADG_AIM_Digest' ) ) { ADG_AIM_Digest::clear_schedule(); } flush_rewrite_rules(); } register_deactivation_hook( __FILE__, 'adg_aim_deactivate' ); /** * Full-removal cleanup. Registered on Freemius's `after_uninstall` action * (Freemius requires the uninstall logic here rather than in an uninstall.php). */ function adg_aim_uninstall_cleanup() { if ( class_exists( 'ADG_AIM_LLMS_Generator' ) ) { ADG_AIM_LLMS_Generator::cleanup_physical_files(); } delete_option( ADG_AIM_OPT_SETTINGS ); delete_option( ADG_AIM_OPT_BOTS ); delete_option( ADG_AIM_OPT_STATS ); delete_option( ADG_AIM_OPT_REFERRALS ); delete_option( ADG_AIM_OPT_LAST_SELFTEST ); delete_option( ADG_AIM_OPT_LLMS_SELFTEST ); delete_transient( 'adg_aim_llms_txt_cache' ); delete_transient( 'adg_aim_token_savings' ); delete_transient( 'adg_aim_selftest' ); delete_transient( 'adg_aim_selftest_report' ); wp_clear_scheduled_hook( 'adg_aim_weekly_digest' ); wp_clear_scheduled_hook( 'adg_aim_llms_selftest_cron' ); delete_post_meta_by_key( '_adg_hide_from_ai_bots' ); delete_post_meta_by_key( '_adg_markdown_cache' ); } if ( function_exists( 'amfac_fs' ) && amfac_fs() ) { amfac_fs()->add_action( 'after_uninstall', 'adg_aim_uninstall_cleanup' ); } /** * WP-Cron hook: run the llms.txt self-test. */ function adg_aim_run_llms_selftest() { if ( class_exists( 'ADG_AIM_LLMS_Generator' ) ) { ADG_AIM_LLMS_Generator::run_selftest(); } } add_action( 'adg_aim_llms_selftest_cron', 'adg_aim_run_llms_selftest' ); // Go. add_action( 'plugins_loaded', array('ADG_AIM_Plugin', 'instance') );