*/ public static function map(): array { $out = array(); foreach ( self::all( false ) as $id => $meta ) { $out[ $id ] = array( 'optional' => (bool) $meta['optional'], 'group' => (string) $meta['group'] ); } return $out; } /** * @param bool $labels Whether to translate the labels — false while the * plugin is still booting; see map(). * @return array */ public static function all( bool $labels = true ): array { if ( ! $labels ) { return self::structure(); } return array( 'core' => array( 'label' => __( 'Core', 'ai-rank-suite' ), 'desc' => __( 'Admin app, capabilities, shared services.', 'ai-rank-suite' ), 'optional' => false, 'group' => 'system' ), 'autopilot' => array( 'label' => __( 'AI Autopilot', 'ai-rank-suite' ), 'desc' => __( 'One button that writes the missing descriptions, FAQs and titles across the whole site — and can put every change back.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'ai' ), 'backlinks' => array( 'label' => __( 'Backlinks', 'ai-rank-suite' ), 'desc' => __( 'Records which sites actually send you visitors, then re-reads those pages to see whether the link is still there, still followed, and what it says.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'blocks' => array( 'label' => __( 'Blocks', 'ai-rank-suite' ), 'desc' => __( 'FAQ, how-to and table-of-contents blocks whose structured data comes from the block itself.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'settings' => array( 'label' => __( 'Settings', 'ai-rank-suite' ), 'desc' => __( 'Plugin configuration.', 'ai-rank-suite' ), 'optional' => false, 'group' => 'system' ), 'licensing' => array( 'label' => __( 'Licensing', 'ai-rank-suite' ), 'desc' => __( 'License activation and updates.', 'ai-rank-suite' ), 'optional' => false, 'group' => 'system' ), 'roles' => array( 'label' => __( 'Roles', 'ai-rank-suite' ), 'desc' => __( 'Capability management per role.', 'ai-rank-suite' ), 'optional' => false, 'group' => 'system' ), 'ai' => array( 'label' => __( 'AI Engine', 'ai-rank-suite' ), 'desc' => __( 'Bring-your-own-key AI provider powering descriptions, FAQs and audits.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'ai' ), 'ai_visibility' => array( 'label' => __( 'AI Answer Simulation', 'ai-rank-suite' ), 'desc' => __( 'Run repeatable brand-mention simulations against your configured AI model.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'ai' ), 'llms_txt' => array( 'label' => __( 'llms.txt', 'ai-rank-suite' ), 'desc' => __( 'Curated site map for LLMs at /llms.txt.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'ai' ), 'crawler_controls' => array( 'label' => __( 'AI Crawlers', 'ai-rank-suite' ), 'desc' => __( 'Allow or block GPTBot, ClaudeBot, PerplexityBot and friends.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'ai' ), 'geo_score' => array( 'label' => __( 'AI Score', 'ai-rank-suite' ), 'desc' => __( 'Site-wide AI readiness scoring.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'ai' ), 'insights' => array( 'label' => __( 'Insights', 'ai-rank-suite' ), 'desc' => __( 'Analytical overview: trends, movers, issue leaderboard and share of voice.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'ai' ), 'business_packs' => array( 'label' => __( 'Industry packs', 'ai-rank-suite' ), 'desc' => __( 'One-click setup per industry and market — schema type, subjects, prompts and regional defaults.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'site_audit' => array( 'label' => __( 'Site Audit', 'ai-rank-suite' ), 'desc' => __( 'Site-wide audit: duplicates, indexability, thin content, orphans and shop checks.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'seo' => array( 'label' => __( 'SEO output', 'ai-rank-suite' ), 'desc' => __( 'Titles, meta description, canonical, social tags.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'social' => array( 'label' => __( 'Social & sharing', 'ai-rank-suite' ), 'desc' => __( 'Open Graph, Twitter Cards, share images, social profiles.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'schema' => array( 'label' => __( 'Schema', 'ai-rank-suite' ), 'desc' => __( '77-type structured data generator.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'sitemap' => array( 'label' => __( 'Sitemaps', 'ai-rank-suite' ), 'desc' => __( 'XML, news and video sitemaps.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'breadcrumbs' => array( 'label' => __( 'Breadcrumbs', 'ai-rank-suite' ), 'desc' => __( 'Breadcrumb trail and BreadcrumbList schema.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'robots' => array( 'label' => __( 'Robots', 'ai-rank-suite' ), 'desc' => __( 'Robots meta rules and robots.txt editor.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'local_seo' => array( 'label' => __( 'Local SEO', 'ai-rank-suite' ), 'desc' => __( 'LocalBusiness schema, opening hours, maps.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'image_seo' => array( 'label' => __( 'Image SEO', 'ai-rank-suite' ), 'desc' => __( 'Automatic alt text and image optimization.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'search_console' => array( 'label' => __( 'Search Console', 'ai-rank-suite' ), 'desc' => __( 'Google Search Console performance data.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'google' => array( 'label' => __( 'Google & indexing', 'ai-rank-suite' ), 'desc' => __( 'Indexing API, site verification.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'indexnow' => array( 'label' => __( 'IndexNow', 'ai-rank-suite' ), 'desc' => __( 'Instant indexing for Bing, Yandex & co.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'bing' => array( 'label' => __( 'Bing Webmaster Tools', 'ai-rank-suite' ), 'desc' => __( 'Bing performance, queries and direct URL submission — the index Copilot retrieves from.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'rank_tracker' => array( 'label' => __( 'Search Console Position Tracking', 'ai-rank-suite' ), 'desc' => __( 'Keyword position tracking from Google Search Console.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'analytics' => array( 'label' => __( 'Analytics', 'ai-rank-suite' ), 'desc' => __( 'GA4 tag and traffic reports.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'pagespeed' => array( 'label' => __( 'PageSpeed', 'ai-rank-suite' ), 'desc' => __( 'Core Web Vitals monitoring.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'search' ), 'content_analyzer' => array( 'label' => __( 'Content analysis', 'ai-rank-suite' ), 'desc' => __( 'Per-page GEO + SEO scoring and keywords.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'editor' => array( 'label' => __( 'Editor integration', 'ai-rank-suite' ), 'desc' => __( 'Post-list columns and block editor sidebar.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'bulk' => array( 'label' => __( 'Bulk actions', 'ai-rank-suite' ), 'desc' => __( 'AI descriptions and FAQs for many pages at once.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'internal_links' => array( 'label' => __( 'Internal links', 'ai-rank-suite' ), 'desc' => __( 'TF-IDF based link suggestions.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'redirects' => array( 'label' => __( 'Redirects & 404s', 'ai-rank-suite' ), 'desc' => __( 'Redirect manager and 404 monitor.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'urls' => array( 'label' => __( 'URL inventory', 'ai-rank-suite' ), 'desc' => __( 'Every indexable URL per language — discovered from posts, translation plugins and the sitemap, scanned as rendered HTML, scored per URL.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'link_checker' => array( 'label' => __( 'Link checker', 'ai-rank-suite' ), 'desc' => __( 'Find broken, redirected and dead links site-wide.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'content' ), 'coexistence' => array( 'label' => __( 'Coexistence', 'ai-rank-suite' ), 'desc' => __( 'Run safely alongside Yoast, Rank Math, AIOSEO or SEOPress — one owner per tag, no duplicates.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), 'hardening' => array( 'label' => __( 'Ghost Mode', 'ai-rank-suite' ), 'desc' => __( 'Hide WordPress fingerprints and close probe doors.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), 'dev_tools' => array( 'label' => __( 'Developer tools', 'ai-rank-suite' ), 'desc' => __( 'System status, hooks & API reference, maintenance.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), 'import_export' => array( 'label' => __( 'Import & backup', 'ai-rank-suite' ), 'desc' => __( 'Migrate from Yoast/Rank Math, settings backups.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), 'reports' => array( 'label' => __( 'Email reports', 'ai-rank-suite' ), 'desc' => __( 'Weekly or monthly HTML email reports.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), 'woocommerce' => array( 'label' => __( 'WooCommerce', 'ai-rank-suite' ), 'desc' => __( 'Product schema and shop optimizations.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), 'feeds' => array( 'label' => __( 'Catalog feeds', 'ai-rank-suite' ), 'desc' => __( 'Google Merchant and Meta/Instagram product XML feeds.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), 'integrations' => array( 'label' => __( 'Plugin integrations', 'ai-rank-suite' ), 'desc' => __( 'Reads recipes, events, jobs, authors, ratings and brands from other plugins.', 'ai-rank-suite' ), 'optional' => true, 'group' => 'system' ), ); } /** * The same list without a single translated string — see map(). * * Kept beside all() on purpose: two lists that must not drift, next to * each other, with a test that fails the build if they ever do. * * @return array */ private static function structure(): array { $out = array(); foreach ( array( 'core' => array( 'optional' => false, 'group' => 'system' ), 'autopilot' => array( 'optional' => true, 'group' => 'ai' ), 'backlinks' => array( 'optional' => true, 'group' => 'search' ), 'blocks' => array( 'optional' => true, 'group' => 'content' ), 'settings' => array( 'optional' => false, 'group' => 'system' ), 'licensing' => array( 'optional' => false, 'group' => 'system' ), 'roles' => array( 'optional' => false, 'group' => 'system' ), 'ai' => array( 'optional' => true, 'group' => 'ai' ), 'ai_visibility' => array( 'optional' => true, 'group' => 'ai' ), 'llms_txt' => array( 'optional' => true, 'group' => 'ai' ), 'crawler_controls' => array( 'optional' => true, 'group' => 'ai' ), 'geo_score' => array( 'optional' => true, 'group' => 'ai' ), 'insights' => array( 'optional' => true, 'group' => 'ai' ), 'business_packs' => array( 'optional' => true, 'group' => 'content' ), 'site_audit' => array( 'optional' => true, 'group' => 'content' ), 'seo' => array( 'optional' => true, 'group' => 'search' ), 'social' => array( 'optional' => true, 'group' => 'search' ), 'schema' => array( 'optional' => true, 'group' => 'search' ), 'sitemap' => array( 'optional' => true, 'group' => 'search' ), 'breadcrumbs' => array( 'optional' => true, 'group' => 'search' ), 'robots' => array( 'optional' => true, 'group' => 'search' ), 'local_seo' => array( 'optional' => true, 'group' => 'search' ), 'image_seo' => array( 'optional' => true, 'group' => 'search' ), 'search_console' => array( 'optional' => true, 'group' => 'search' ), 'google' => array( 'optional' => true, 'group' => 'search' ), 'indexnow' => array( 'optional' => true, 'group' => 'search' ), 'bing' => array( 'optional' => true, 'group' => 'search' ), 'rank_tracker' => array( 'optional' => true, 'group' => 'search' ), 'analytics' => array( 'optional' => true, 'group' => 'search' ), 'pagespeed' => array( 'optional' => true, 'group' => 'search' ), 'content_analyzer' => array( 'optional' => true, 'group' => 'content' ), 'editor' => array( 'optional' => true, 'group' => 'content' ), 'bulk' => array( 'optional' => true, 'group' => 'content' ), 'internal_links' => array( 'optional' => true, 'group' => 'content' ), 'redirects' => array( 'optional' => true, 'group' => 'content' ), 'urls' => array( 'optional' => true, 'group' => 'content' ), 'link_checker' => array( 'optional' => true, 'group' => 'content' ), 'coexistence' => array( 'optional' => true, 'group' => 'system' ), 'hardening' => array( 'optional' => true, 'group' => 'system' ), 'dev_tools' => array( 'optional' => true, 'group' => 'system' ), 'import_export' => array( 'optional' => true, 'group' => 'system' ), 'reports' => array( 'optional' => true, 'group' => 'system' ), 'woocommerce' => array( 'optional' => true, 'group' => 'system' ), 'feeds' => array( 'optional' => true, 'group' => 'system' ), 'integrations' => array( 'optional' => true, 'group' => 'system' ), ) as $id => $meta ) { $out[ $id ] = array( 'label' => $id, 'desc' => '', 'optional' => $meta['optional'], 'group' => $meta['group'] ); } return $out; } /** * Module id for a Kernel class name (Modules\Foo\FooModule → id used by * that module). Derived from the namespace segment, matching id() output. */ public static function id_for_class( string $class ): ?string { if ( 1 !== preg_match( '/Modules\\\\([A-Za-z]+)\\\\/', $class, $m ) ) { return null; } // Ids that do not follow the CamelCase → snake_case convention. $aliases = array( 'PageSpeed' => 'pagespeed', 'WooCommerce' => 'woocommerce', 'IndexNow' => 'indexnow', ); if ( isset( $aliases[ $m[1] ] ) ) { return $aliases[ $m[1] ]; } // CamelCase namespace → snake_case id, e.g. AiVisibility → ai_visibility. return strtolower( (string) preg_replace( '/(? */ public static function disabled_by_default(): array { return array(); } /** * Modules that may only be switched on with an explicit acknowledgement. * * The same list, deliberately: what is off by default because it executes * code is exactly what must not come on through a stray "Enable all". */ public static function needs_acknowledgement( string $id ): bool { return in_array( $id, self::disabled_by_default(), true ); } /** * Write the default-off list — on a fresh install only. * * "Fresh" is judged from the options table, not from which hook called * this: the activation hook does not fire for network-activated sub-sites * or an in-place update, so the migrator calls this too, and both must * agree. An install that has ever recorded a version, saved a module list * or stored a snippet has made its choice and keeps it — the "re-run * migration" maintenance button drops the schema version on purpose and * must not read as a first run. */ public static function seed_fresh_install(): void { foreach ( array( 'airanksuite_version', 'airanksuite_db_version', 'airanksuite_disabled_modules', 'airanksuite_snippets' ) as $option ) { if ( null !== get_option( $option, null ) ) { return; } } update_option( 'airanksuite_disabled_modules', self::disabled_by_default(), false ); } }