array( 'label' => __( 'Publisher / Media', 'ai-o-optimiser' ), 'description' => __( 'News, magazines, editorial publishers and media organisations.', 'ai-o-optimiser' ), 'icon' => 'dashicons-media-document' ), 'editorial_content' => array( 'label' => __( 'Editorial / Content', 'ai-o-optimiser' ), 'description' => __( 'Blogs, knowledge sites and content-led publications.', 'ai-o-optimiser' ), 'icon' => 'dashicons-edit' ), 'commerce_marketplace' => array( 'label' => __( 'Commerce / Marketplace', 'ai-o-optimiser' ), 'description' => __( 'Product catalogues, stores and multi-seller marketplaces.', 'ai-o-optimiser' ), 'icon' => 'dashicons-cart' ), 'directory_listings' => array( 'label' => __( 'Directory / Listings', 'ai-o-optimiser' ), 'description' => __( 'Sites built around many individual organisations, places, people or listings.', 'ai-o-optimiser' ), 'icon' => 'dashicons-list-view' ), 'service_business' => array( 'label' => __( 'Services / Expertise', 'ai-o-optimiser' ), 'description' => __( 'Businesses centred on services, solutions, consulting or expertise.', 'ai-o-optimiser' ), 'icon' => 'dashicons-portfolio' ), 'software_platform' => array( 'label' => __( 'Software / Platform', 'ai-o-optimiser' ), 'description' => __( 'SaaS, applications, digital products and online platforms.', 'ai-o-optimiser' ), 'icon' => 'dashicons-admin-generic' ), 'organisation_community' => array( 'label' => __( 'Organisation / Community', 'ai-o-optimiser' ), 'description' => __( 'Associations, nonprofits, membership bodies and community organisations.', 'ai-o-optimiser' ), 'icon' => 'dashicons-groups' ), 'local_place_business' => array( 'label' => __( 'Local / Place-led', 'ai-o-optimiser' ), 'description' => __( 'A business or organisation whose physical place or service area is central.', 'ai-o-optimiser' ), 'icon' => 'dashicons-location' ), 'mixed_general' => array( 'label' => __( 'Mixed / General', 'ai-o-optimiser' ), 'description' => __( 'A mixed-purpose site that does not fit one dominant operating model.', 'ai-o-optimiser' ), 'icon' => 'dashicons-admin-site-alt3' ), ); } /** Optional subject/domain layer. Domain selection informs guidance, not requirements. */ public static function domains() { return array( 'general' => __( 'General / Mixed', 'ai-o-optimiser' ), 'technology_software' => __( 'Technology / Software', 'ai-o-optimiser' ), 'professional_services' => __( 'Professional Services', 'ai-o-optimiser' ), 'education' => __( 'Education / Training', 'ai-o-optimiser' ), 'healthcare' => __( 'Healthcare / Medical', 'ai-o-optimiser' ), 'fitness_wellness' => __( 'Fitness / Wellness', 'ai-o-optimiser' ), 'hospitality_travel' => __( 'Hospitality / Travel', 'ai-o-optimiser' ), 'food_dining' => __( 'Food / Dining', 'ai-o-optimiser' ), 'property_real_estate' => __( 'Property / Real Estate', 'ai-o-optimiser' ), 'jobs_recruitment' => __( 'Jobs / Recruitment', 'ai-o-optimiser' ), 'events_entertainment' => __( 'Events / Entertainment', 'ai-o-optimiser' ), 'retail_products' => __( 'Retail / Products', 'ai-o-optimiser' ), 'nonprofit_membership' => __( 'Nonprofit / Membership', 'ai-o-optimiser' ), 'local_places' => __( 'Places / Local Directory', 'ai-o-optimiser' ), ); } /** Render the site-model panel. */ public static function render() { if ( ! current_user_can( self::capability() ) || ! self::is_profile_screen() ) { return; } $page = self::current_page(); $current = AI_O_Optimiser_Site_Profile::current(); $confirmed = ! empty( $current['profile'] ); $suggested_profile = AI_O_Optimiser_Site_Profile::suggested_profile(); $suggested_model = self::model_from_legacy_profile( $suggested_profile ); $selected_model = ! empty( $current['site_model'] ) && isset( self::site_models()[ $current['site_model'] ] ) ? (string) $current['site_model'] : self::model_from_legacy_profile( (string) ( $current['profile'] ?? $suggested_profile ) ); $suggested_domain = self::suggested_domain(); $selected_domain = ! empty( $current['primary_domain'] ) && isset( self::domains()[ $current['primary_domain'] ] ) ? (string) $current['primary_domain'] : $suggested_domain; $news_selected = ! empty( $current['news_post_types'] ) ? AI_O_Optimiser_Site_Profile::validate_news_post_types( $current['news_post_types'] ) : AI_O_Optimiser_Site_Profile::suggested_news_post_types(); $models = self::site_models(); $domains = self::domains(); self::render_saved_notice(); ?>

$model ) : ?>

>

$label ) : ?>
1, 'ai_o_optimiser_profile_scope' => $scope_applied ? 1 : 0 ), $redirect ); wp_safe_redirect( $redirect ); exit; } /** Convert old saved profiles to the richer model without a migration write. */ private static function model_from_legacy_profile( $profile ) { $map = array( 'news_publisher' => 'publisher_media', 'blog_editorial' => 'editorial_content', 'business_services' => 'service_business', 'ecommerce' => 'commerce_marketplace', 'local_business' => 'local_place_business', 'general' => 'mixed_general', ); return $map[ sanitize_key( $profile ) ] ?? 'mixed_general'; } /** Preserve the existing profile policy contract for current schema behaviour. */ private static function legacy_profile_from_model( $model ) { $map = array( 'publisher_media' => 'news_publisher', 'editorial_content' => 'blog_editorial', 'commerce_marketplace' => 'ecommerce', 'service_business' => 'business_services', 'local_place_business' => 'local_business', 'directory_listings' => 'general', 'software_platform' => 'general', 'organisation_community' => 'general', 'mixed_general' => 'general', ); return $map[ $model ] ?? 'general'; } /** Conservative local-only domain suggestion; never saved automatically. */ private static function suggested_domain() { $objects = get_post_types( array( 'public' => true ), 'objects' ); $objects = is_array( $objects ) ? $objects : array(); $haystack = ''; foreach ( $objects as $type => $object ) { $label = ! empty( $object->labels->name ) ? (string) $object->labels->name : (string) $type; $haystack .= ' ' . $type . ' ' . $label; } $tests = array( 'education' => '/\b(school|schools|college|university|course|courses|education|training)\b/i', 'healthcare' => '/\b(health|healthcare|medical|clinic|dentist|dental|doctor|hospital)\b/i', 'fitness_wellness' => '/\b(gym|gyms|fitness|wellness|health club)\b/i', 'hospitality_travel' => '/\b(hotel|hotels|travel|tour|tourism|accommodation)\b/i', 'food_dining' => '/\b(restaurant|restaurants|food|dining|menu)\b/i', 'property_real_estate' => '/\b(property|properties|real estate|listing|listings)\b/i', 'jobs_recruitment' => '/\b(job|jobs|career|careers|recruitment|vacancy|vacancies)\b/i', 'events_entertainment' => '/\b(event|events|entertainment|venue|venues)\b/i', 'technology_software' => '/\b(software|saas|app|apps|application|platform|technology|tech)\b/i', 'professional_services' => '/\b(service|services|consulting|consultant|agency|expertise)\b/i', ); foreach ( $tests as $domain => $pattern ) { if ( preg_match( $pattern, $haystack ) ) { return $domain; } } if ( class_exists( 'WooCommerce' ) || isset( $objects['product'] ) ) { return 'retail_products'; } return 'general'; } /** Save confirmation notice. */ private static function render_saved_notice() { if ( empty( $_GET['ai_o_optimiser_profile_saved'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen state (page slug, tab, redirect result notices). Nothing is written here, so there is no form submission to carry a nonce. return; } echo '

' . esc_html__( 'Site model saved.', 'ai-o-optimiser' ) . ' '; if ( ! empty( $_GET['ai_o_optimiser_profile_scope'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen state (page slug, tab, redirect result notices). Nothing is written here, so there is no form submission to carry a nonce. echo esc_html__( 'Schema Audit scope was updated to the selected publisher content types.', 'ai-o-optimiser' ) . ' '; } echo esc_html__( 'Run a fresh Schema Audit after changing model or mappings; cached evidence is not silently rewritten.', 'ai-o-optimiser' ) . '

'; } private static function current_page() { return isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only screen state (page slug, tab, redirect result notices). Nothing is written here, so there is no form submission to carry a nonce. } private static function is_profile_screen() { return in_array( self::current_page(), array( 'ai-o-optimiser-dashboard', 'ai-o-optimiser-schema-review', 'ai-o-optimiser-settings' ), true ); } private static function capability() { return defined( 'AI_O_Optimiser_SENSITIVE_CAPABILITY' ) ? AI_O_Optimiser_SENSITIVE_CAPABILITY : 'manage_options'; } }