get_charset_collate(); $meta_table = $wpdb->prefix . 'modulustools_meta'; $redirects_table = $wpdb->prefix . 'modulustools_redirects'; $logs_404_table = $wpdb->prefix . 'modulustools_404_logs'; $ai_bots_table = $wpdb->prefix . 'modulustools_ai_bots'; $sql_meta = "CREATE TABLE {$meta_table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, object_id bigint(20) unsigned NOT NULL DEFAULT 0, object_type varchar(20) NOT NULL DEFAULT 'post', title varchar(255) NOT NULL DEFAULT '', description text DEFAULT NULL, focus_keyword varchar(191) NOT NULL DEFAULT '', keyword_type varchar(32) NOT NULL DEFAULT 'body', secondary_keywords text DEFAULT NULL, lsi_keywords text DEFAULT NULL, canonical_url varchar(500) NOT NULL DEFAULT '', robots_index varchar(20) NOT NULL DEFAULT 'index', robots_follow varchar(20) NOT NULL DEFAULT 'follow', robots_advanced varchar(255) NOT NULL DEFAULT 'max-snippet:-1,max-image-preview:large,max-video-preview:-1', og_title varchar(255) NOT NULL DEFAULT '', og_description text DEFAULT NULL, og_image varchar(500) NOT NULL DEFAULT '', schema_type varchar(64) NOT NULL DEFAULT 'Article', product_brand varchar(191) NOT NULL DEFAULT '', product_gtin varchar(64) NOT NULL DEFAULT '', product_mpn varchar(64) NOT NULL DEFAULT '', eeat_reviewed_by varchar(255) NOT NULL DEFAULT '', eeat_citations text DEFAULT NULL, is_pillar tinyint(1) unsigned NOT NULL DEFAULT 0, seo_score smallint(5) unsigned NOT NULL DEFAULT 0, readability_score smallint(5) unsigned NOT NULL DEFAULT 0, geo_score smallint(5) unsigned NOT NULL DEFAULT 0, sge_ready tinyint(1) unsigned NOT NULL DEFAULT 0, inbound_links_count int(10) unsigned NOT NULL DEFAULT 0, outbound_links_count int(10) unsigned NOT NULL DEFAULT 0, external_links_count int(10) unsigned NOT NULL DEFAULT 0, updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY object_lookup (object_id,object_type), KEY focus_keyword_idx (focus_keyword), KEY seo_score_idx (seo_score), KEY is_pillar_idx (is_pillar), KEY inbound_links_idx (inbound_links_count) ) {$charset_collate};"; $sql_redirects = "CREATE TABLE {$redirects_table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, source_url varchar(500) NOT NULL, target_url varchar(500) NOT NULL DEFAULT '', status_code smallint(5) unsigned NOT NULL DEFAULT 301, is_regex tinyint(1) unsigned NOT NULL DEFAULT 0, is_active tinyint(1) unsigned NOT NULL DEFAULT 1, hits bigint(20) unsigned NOT NULL DEFAULT 0, last_accessed datetime DEFAULT NULL, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY status_regex_idx (is_active,is_regex), KEY source_url_idx (source_url(191)) ) {$charset_collate};"; $sql_404_logs = "CREATE TABLE {$logs_404_table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, request_url varchar(500) NOT NULL, referrer varchar(500) NOT NULL DEFAULT '', user_agent varchar(255) NOT NULL DEFAULT '', ip_hash varchar(64) NOT NULL DEFAULT '', hits bigint(20) unsigned NOT NULL DEFAULT 1, last_hit_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY request_url_uniq (request_url(191)), KEY hits_idx (hits) ) {$charset_collate};"; $sql_ai_bots = "CREATE TABLE {$ai_bots_table} ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, bot_key varchar(64) NOT NULL, bot_label varchar(100) NOT NULL, request_url varchar(500) NOT NULL, post_id bigint(20) unsigned NOT NULL DEFAULT 0, user_agent varchar(255) NOT NULL DEFAULT '', http_status smallint(5) unsigned NOT NULL DEFAULT 200, visited_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY bot_key_idx (bot_key), KEY post_id_idx (post_id), KEY visited_at_idx (visited_at) ) {$charset_collate};"; dbDelta( $sql_meta ); dbDelta( $sql_redirects ); dbDelta( $sql_404_logs ); dbDelta( $sql_ai_bots ); update_option( 'modulustools_seo_db_version', MODULUSTOOLS_SEO_DB_VERSION ); } /** * Initializes default module toggles, settings, and dedicated secret key. * * @return void */ public static function set_default_options(): void { $default_modules = array( 'frontend_head' => true, 'schema_graph' => true, 'sitemaps' => true, 'redirects_404' => true, 'geo_llms' => true, 'ai_byok' => true, 'image_seo' => true, 'link_assistant' => true, 'migrator' => true, ); if ( false === get_option( 'modulustools_seo_modules' ) ) { add_option( 'modulustools_seo_modules', $default_modules ); } $default_settings = array( 'title_separator' => '-', 'site_entity_type' => 'Organization', 'organization_name' => get_bloginfo( 'name' ), 'organization_logo' => '', 'default_schema_type' => 'Article', 'merchant_return_days' => 30, 'enable_news_sitemap' => true, 'enable_video_sitemap' => true, 'local_business_type' => 'LocalBusiness', 'local_phone' => '', 'local_price_range' => '$$', 'local_street' => '', 'local_city' => '', 'local_region' => '', 'local_postal' => '', 'local_country' => '', 'local_lat' => '', 'local_lng' => '', 'local_opening_hours' => 'Mo-Fr 09:00-18:00', 'rss_before_content' => '', 'rss_after_content' => 'The post %post_link% appeared first on %site_link%.', 'strip_category_base' => false, 'clean_wp_head' => true, 'allowed_roles_metabox' => array( 'administrator', 'editor', 'author' ), 'allowed_roles_redirects' => array( 'administrator', 'editor' ), 'image_alt_template' => '%post_title% - %site_name%', 'image_title_template' => '%post_title%', 'auto_redirect_slug_change' => true, 'enable_indexnow' => true, 'indexnow_webhook_url' => '', 'llms_txt_intro' => __( 'Official LLM & AI Search Knowledge Directory.', 'modulustools-seo-suite' ), 'custom_robots_rules' => '', 'ai_bots_rules' => array( 'GPTBot' => 'allow', 'OAI-SearchBot' => 'allow', 'ClaudeBot' => 'allow', 'Anthropic-ai' => 'allow', 'Google-Extended' => 'allow', 'PerplexityBot' => 'allow', 'Applebot-Extended' => 'allow', 'CCBot' => 'allow', ), 'delete_data_on_uninstall' => false, ); if ( false === get_option( 'modulustools_seo_settings' ) ) { add_option( 'modulustools_seo_settings', $default_settings ); } if ( false === get_option( 'modulustools_seo_indexnow_key' ) ) { $random_key = strtolower( wp_generate_password( 32, false, false ) ); add_option( 'modulustools_seo_indexnow_key', $random_key ); } if ( false === get_option( 'modulustools_seo_secret_key' ) ) { $secret_key = wp_generate_password( 64, true, true ); add_option( 'modulustools_seo_secret_key', $secret_key, '', false ); } } /** * Retrieves the dedicated plugin secret key (never touches WordPress auth salts). * * @return string */ public static function get_secret_key(): string { $secret = (string) get_option( 'modulustools_seo_secret_key', '' ); if ( '' === $secret ) { $secret = wp_generate_password( 64, true, true ); update_option( 'modulustools_seo_secret_key', $secret, false ); } return $secret; } }