gmdate( 'c' ), 'resolver_mode' => '', 'cache_key' => '', 'url' => '', 'result' => '', 'reason' => '', 'response_code' => '', 'payload_version' => '', 'vertical_count' => 0, ); update_option( 'matewa_verticals_last_diagnostic', array_merge( $defaults, $data ), false ); } } /** * Helper: sanitize a key-like value safely. * * @param mixed $value * @param string $fallback * @return string */ if ( ! function_exists( 'matewa_catalog_safe_key' ) ) { function matewa_catalog_safe_key( $value, $fallback = '' ) { $value = is_string( $value ) || is_numeric( $value ) ? trim( (string) $value ) : ''; $value = sanitize_key( $value ); return '' !== $value ? $value : sanitize_key( (string) $fallback ); } } /** * Helper: sanitize visible text safely. * * @param mixed $value * @param string $fallback * @return string */ if ( ! function_exists( 'matewa_catalog_safe_text' ) ) { function matewa_catalog_safe_text( $value, $fallback = '' ) { if ( is_string( $value ) || is_numeric( $value ) ) { $value = trim( wp_strip_all_tags( (string) $value ) ); $value = preg_replace( '/\s+/u', ' ', $value ); if ( '' !== $value ) { return (string) $value; } } return (string) $fallback; } } /** * Helper: format machine keys into visible labels while preserving common acronyms. * * @param string $value * @param string $fallback * @return string */ if ( ! function_exists( 'matewa_catalog_format_key_label' ) ) { function matewa_catalog_format_key_label( $value, $fallback = 'General' ) { $value = is_string( $value ) ? trim( $value ) : ''; if ( '' === $value ) { return $fallback; } $value = str_replace( array( 'intent.', 'vertical.' ), '', $value ); $value = str_replace( array( '_', '-' ), ' ', $value ); $value = preg_replace( '/\s+/u', ' ', $value ); $value = trim( (string) $value ); if ( '' === $value ) { return $fallback; } $words = preg_split( '/\s+/u', strtolower( $value ) ); $upper_words = array( 'hgv', 'mot', 'cbt', 'das', 'cpc', 'seo', 'ppc', 'vat', 'ev', 'gp', 'it', 'ai', 'av', 'tv', 'cctv', 'wifi', 'voip', 'epos', ); $out = array(); if ( is_array( $words ) ) { foreach ( $words as $word ) { $word = trim( (string) $word ); if ( '' === $word ) { continue; } if ( in_array( strtolower( $word ), $upper_words, true ) ) { $out[] = strtoupper( $word ); continue; } $out[] = ucfirst( $word ); } } $label = trim( implode( ' ', $out ) ); return '' !== $label ? $label : $fallback; } } /** * Central visible chip label authority. * Keys remain machine-safe. Values are user-facing. * * @return array */ if ( ! function_exists( 'matewa_catalog_intent_label_authority' ) ) { function matewa_catalog_intent_label_authority() { return array( // Existing explicit normalization fixes 'intent.driveway_cleaning' => 'Driveway cleaning', 'intent.patio_cleaning' => 'Patio cleaning', 'intent.render_cleaning' => 'Render cleaning', 'intent.room_info' => 'Room info', 'intent.special_request' => 'Special request', 'intent.urgent_help' => 'Urgent help', // Stable common wording 'intent.quote' => 'Get a quote', 'intent.quote_request' => 'Get a quote', 'intent.pricing' => 'Pricing', 'intent.availability' => 'Availability', 'intent.location' => 'Location', 'intent.site_visit' => 'Site visit', 'intent.same_day_service' => 'Same-day service', 'intent.same_day_quote' => 'Same-day quote', 'intent.book_job' => 'Book a job', 'intent.ask_question' => 'Ask a question', 'intent.recovery' => 'Recovery', 'intent.breakdown' => 'Breakdown', 'intent.vehicle_fault' => 'Vehicle fault', 'intent.battery_help' => 'Battery help', 'intent.roadside_help' => 'Roadside help', // Driving / training 'intent.manual_lessons' => 'Manual lessons', 'intent.automatic_lessons' => 'Automatic lessons', 'intent.intensive_course' => 'Intensive course', 'intent.test_preparation' => 'Test preparation', 'intent.refresher_lessons' => 'Refresher lessons', 'intent.cbt_training' => 'CBT training', 'intent.das_training' => 'DAS training', 'intent.bike_test_prep' => 'Bike test prep', 'intent.gear_advice' => 'Gear advice', 'intent.class_1_training' => 'Class 1 training', 'intent.class_2_training' => 'Class 2 training', 'intent.cpc_training' => 'CPC training', 'intent.theory_support' => 'Theory support', 'intent.refresher_training' => 'Refresher training', // Tree services 'intent.tree_pruning' => 'Tree pruning', 'intent.tree_removal' => 'Tree removal', 'intent.stump_grinding' => 'Stump grinding', 'intent.hedge_cutting' => 'Hedge cutting', 'intent.emergency_tree_work' => 'Emergency tree work', // Minimal fit fixes for long installation chips 'intent.floor_installation' => 'New flooring', 'intent.kitchen_installation' => 'New kitchen', 'intent.bathroom_installation' => 'New bathroom', 'intent.driveway_installation' => 'New driveway', 'intent.patio_installation' => 'New patio', 'intent.window_installation' => 'New windows', 'intent.door_installation' => 'New doors', // A few key acronym / UX fixes from current catalog 'intent.mot_booking' => 'MOT booking', 'intent.ev_charger' => 'EV charger', 'intent.ev_charging' => 'EV charging', 'intent.cctv_survey' => 'CCTV survey', 'intent.lasting_power_of_attorney' => 'Lasting power of attorney', 'intent.vat_support' => 'VAT support', 'intent.one_to_one_training' => '1-to-1 training', 'intent.in_person_session' => 'In-person session', 'intent.online_session' => 'Online session', 'intent.online_lessons' => 'Online lessons', 'intent.online_coaching' => 'Online coaching', // Agency, technical trade and creative verticals — Phase 5-A-14K 'intent.add_client_sites' => 'Add client sites', 'intent.agency_pricing' => 'Agency pricing', 'intent.book_agency_demo' => 'Book agency demo', 'intent.client_lead_examples' => 'Client lead examples', 'intent.white_label_options' => 'White-label options', 'intent.fix_poor_tv_signal' => 'Fix poor TV signal', 'intent.install_new_aerial' => 'Install new aerial', 'intent.fix_satellite_dish' => 'Fix satellite dish', 'intent.add_extra_tv_points' => 'Add extra TV points', 'intent.book_visit' => 'Book a visit', 'intent.fix_wifi_connection' => 'Fix Wi-Fi connection', 'intent.improve_coverage' => 'Improve coverage', 'intent.garden_room_wifi' => 'Garden room Wi-Fi', 'intent.add_network_points' => 'Add network points', 'intent.book_survey' => 'Book a survey', 'intent.mount_my_tv' => 'Mount my TV', 'intent.home_cinema_setup' => 'Home cinema setup', 'intent.smart_home_setup' => 'Set up smart home', 'intent.audio_installation' => 'Audio installation', 'intent.book_quote' => 'Book a quote', 'intent.install_cctv' => 'Install CCTV', 'intent.install_alarm' => 'Install alarm', 'intent.repair_security_system' => 'Repair security system', 'intent.upgrade_system' => 'Upgrade system', 'intent.urgent_it_support' => 'Urgent IT support', 'intent.set_up_device' => 'Set up device', 'intent.business_it_support' => 'Business IT support', 'intent.managed_it_plan' => 'Managed IT plan', 'intent.book_support_session' => 'Book support session', 'intent.new_website_design' => 'New website design', 'intent.logo_or_branding' => 'Logo or branding', 'intent.design_project' => 'Design project', 'intent.website_updates' => 'Website updates', 'intent.request_quote' => 'Request a quote', 'intent.shop_signage' => 'Shop signage', 'intent.vehicle_graphics' => 'Vehicle graphics', 'intent.window_graphics' => 'Window graphics', 'intent.design_print' => 'Design & print', // Childcare & early years 'intent.after_school_places' => 'After-school places', 'intent.age_groups' => 'Age groups', 'intent.assessment_call' => 'Assessment call', 'intent.before_after_school' => 'Before/after school', 'intent.book_call' => 'Book a call', 'intent.book_session' => 'Book a session', 'intent.childcare_places' => 'Childcare places', 'intent.class_times' => 'Class times', 'intent.consultation' => 'Consultation', 'intent.dates_times' => 'Dates & times', 'intent.fees' => 'Fees', 'intent.fees_funding' => 'Fees & funding', 'intent.holiday_club_places' => 'Holiday club places', 'intent.nanny_enquiry' => 'Nanny enquiry', 'intent.preschool_places' => 'Preschool places', 'intent.requirements' => 'Requirements', 'intent.school_pickup' => 'School pickup', 'intent.support_needs' => 'Support needs', 'intent.term_times' => 'Term times', 'intent.opening_hours' => 'Opening hours', // Final chip 'intent.other' => 'Something else', ); } } /** * Central visible vertical label authority. * * @return array */ if ( ! function_exists( 'matewa_catalog_vertical_label_authority' ) ) { function matewa_catalog_vertical_label_authority() { return array( 'vertical.tree_surgeon' => 'Tree Surgeon', 'vertical.hvac' => 'HVAC', 'vertical.ev_charging_installation' => 'EV Charging Installation', 'vertical.solar_pv' => 'Solar PV', 'vertical.mot_garage' => 'MOT Garage', 'vertical.it_services' => 'IT Services', 'vertical.private_gp' => 'Private GP', 'vertical.driving_instructor_hgv' => 'Driving Instructor HGV', // Agency, technical trade and creative verticals — Phase 5-A-14K 'vertical.agency' => 'Agency', 'vertical.web_design_agency' => 'Web Design Agency', 'vertical.wordpress_agency' => 'WordPress Agency', 'vertical.seo_agency' => 'SEO Agency', 'vertical.ppc_agency' => 'PPC Agency', 'vertical.local_marketing_agency' => 'Local Marketing Agency', 'vertical.trades_marketing_agency' => 'Trades Marketing Agency', 'vertical.website_maintenance_agency' => 'Website Maintenance Agency', 'vertical.ai_automation_agency' => 'AI Automation Agency', 'vertical.branding_agency' => 'Branding Agency', 'vertical.graphic_design_agency' => 'Graphic Design Agency', 'vertical.signage_agency' => 'Signage Agency', 'vertical.aerial_installer' => 'Aerial Installer', 'vertical.satellite_installer' => 'Satellite Installer', 'vertical.tv_aerial_repairs' => 'TV Aerial Repairs', 'vertical.tv_wall_mounting' => 'TV Wall Mounting', 'vertical.extra_tv_points' => 'Extra TV Points', 'vertical.home_networking' => 'Home Networking', 'vertical.wifi_installation' => 'WiFi Installation', 'vertical.wifi_repair' => 'WiFi Repair', 'vertical.garden_room_wifi' => 'Garden Room WiFi', 'vertical.network_cabling' => 'Network Cabling', 'vertical.data_cabling_contractor' => 'Data Cabling Contractor', 'vertical.office_cabling' => 'Office Cabling', 'vertical.home_av_installer' => 'Home AV Installer', 'vertical.commercial_av_installer' => 'Commercial AV Installer', 'vertical.smart_home_installer' => 'Smart Home Installer', 'vertical.home_cinema_installer' => 'Home Cinema Installer', 'vertical.media_wall_installer' => 'Media Wall Installer', 'vertical.audio_installer' => 'Audio Installer', 'vertical.cctv_installer' => 'CCTV Installer', 'vertical.alarm_installer' => 'Alarm Installer', 'vertical.access_control' => 'Access Control', 'vertical.door_entry_systems' => 'Door Entry Systems', 'vertical.electric_gates' => 'Electric Gates', 'vertical.security_system_repairs' => 'Security System Repairs', 'vertical.it_support' => 'IT Support', 'vertical.managed_it_services' => 'Managed IT Services', 'vertical.computer_repair' => 'Computer Repair', 'vertical.laptop_repair' => 'Laptop Repair', 'vertical.business_it_support' => 'Business IT Support', 'vertical.voip_installer' => 'VoIP Installer', 'vertical.cybersecurity_consultant' => 'Cybersecurity Consultant', 'vertical.epos_installer' => 'EPOS Installer', 'vertical.web_design' => 'Web Design', 'vertical.graphic_design' => 'Graphic Design', 'vertical.branding_design' => 'Branding Design', 'vertical.logo_design' => 'Logo Design', 'vertical.website_maintenance' => 'Website Maintenance', 'vertical.seo_services' => 'SEO Services', 'vertical.content_design' => 'Content Design', 'vertical.marketing_design' => 'Marketing Design', 'vertical.signage' => 'Signage', 'vertical.vehicle_graphics' => 'Vehicle Graphics', 'vertical.vehicle_wraps' => 'Vehicle Wraps', 'vertical.shop_signage' => 'Shop Signage', 'vertical.window_graphics' => 'Window Graphics', 'vertical.exhibition_graphics' => 'Exhibition Graphics', 'vertical.print_design' => 'Print Design', 'vertical.workwear_printing' => 'Workwear Printing', // Childcare & early years verticals 'vertical.after_school_club' => 'After-School Club', 'vertical.baby_toddler_classes' => 'Baby & Toddler Classes', 'vertical.childcare_provider' => 'Childcare Provider', 'vertical.childminder' => 'Childminder', 'vertical.day_nursery' => 'Day Nursery', 'vertical.holiday_club' => 'Holiday Club', 'vertical.nanny_agency' => 'Nanny Agency', 'vertical.preschool' => 'Preschool', 'vertical.send_support_provider' => 'SEND Support Provider', 'vertical.wraparound_care' => 'Wraparound Care', ); } } /** * Resolve a visible label for a chip intent key. * * @param string $intent_key * @return string */ if ( ! function_exists( 'matewa_catalog_resolve_intent_label' ) ) { function matewa_catalog_resolve_intent_label( $intent_key ) { $intent_key = is_string( $intent_key ) ? trim( $intent_key ) : ''; $labels = matewa_catalog_intent_label_authority(); if ( '' !== $intent_key && isset( $labels[ $intent_key ] ) && '' !== $labels[ $intent_key ] ) { return (string) $labels[ $intent_key ]; } return matewa_catalog_format_key_label( $intent_key, 'Something else' ); } } /** * Resolve a visible label for a vertical key. * * @param string $vertical_key * @param string $label_key * @return string */ if ( ! function_exists( 'matewa_catalog_resolve_vertical_label' ) ) { function matewa_catalog_resolve_vertical_label( $vertical_key, $label_key = '' ) { $vertical_key = is_string( $vertical_key ) ? trim( $vertical_key ) : ''; $label_key = is_string( $label_key ) ? trim( $label_key ) : ''; $labels = matewa_catalog_vertical_label_authority(); if ( '' !== $label_key && isset( $labels[ $label_key ] ) && '' !== $labels[ $label_key ] ) { return (string) $labels[ $label_key ]; } return matewa_catalog_format_key_label( $vertical_key, 'General' ); } } /** * Return generic fill intents for malformed catalogs. * * @return array */ if ( ! function_exists( 'matewa_catalog_generic_fill_intents' ) ) { function matewa_catalog_generic_fill_intents() { return array( 'intent.quote', 'intent.pricing', 'intent.availability', 'intent.ask_question', 'intent.book_job', ); } } /** * Normalize a chip list: * - max 6 total * - final chip always intent.other * - chips 1-5 always populated * * @param array $chips * @return array */ if ( ! function_exists( 'matewa_catalog_normalize_chip_list' ) ) { function matewa_catalog_normalize_chip_list( $chips ) { $normalized = array(); if ( is_array( $chips ) ) { foreach ( $chips as $chip ) { if ( is_string( $chip ) ) { $chip = trim( $chip ); if ( '' !== $chip ) { $normalized[] = $chip; } } } } $normalized = array_values( array_filter( $normalized, static function( $chip ) { return 'intent.other' !== $chip; } ) ); $normalized = array_slice( $normalized, 0, 5 ); $fills = matewa_catalog_generic_fill_intents(); foreach ( $fills as $fill ) { if ( count( $normalized ) >= 5 ) { break; } if ( ! in_array( $fill, $normalized, true ) ) { $normalized[] = $fill; } } while ( count( $normalized ) < 5 ) { $normalized[] = 'intent.quote'; } $normalized[] = 'intent.other'; return array_values( $normalized ); } } /** * Normalize categories: * - category labels cleaned * - category vertical keys cleaned * - only keep vertical references that exist * - preserve payload authority; do not inject legacy branches * * @param array $categories * @param array $verticals * @return array */ if ( ! function_exists( 'matewa_catalog_normalize_categories' ) ) { function matewa_catalog_normalize_categories( $categories, $verticals ) { $categories = is_array( $categories ) ? $categories : array(); $verticals = is_array( $verticals ) ? $verticals : array(); $out = array(); foreach ( $categories as $category_key => $category_config ) { $category_key = matewa_catalog_safe_key( $category_key ); if ( '' === $category_key ) { continue; } $category_config = is_array( $category_config ) ? $category_config : array(); $label = matewa_catalog_safe_text( isset( $category_config['label'] ) ? $category_config['label'] : '', matewa_catalog_format_key_label( $category_key, 'General' ) ); $refs = isset( $category_config['verticals'] ) && is_array( $category_config['verticals'] ) ? $category_config['verticals'] : array(); $valid_refs = array(); foreach ( $refs as $vertical_key ) { $vertical_key = matewa_catalog_safe_key( $vertical_key ); if ( '' === $vertical_key ) { continue; } if ( ! isset( $verticals[ $vertical_key ] ) ) { continue; } $valid_refs[ $vertical_key ] = $vertical_key; } $out[ $category_key ] = array( 'label' => $label, 'verticals' => array_values( $valid_refs ), ); } if ( empty( $out ) ) { $out['general'] = array( 'label' => 'General', 'verticals' => isset( $verticals['general'] ) ? array( 'general' ) : array(), ); } return $out; } } /** * Apply controlled catalog normalization. * Trust the remote payload as authority. * Do not inject legacy categories or legacy vertical aliases. * * @param array $data * @return array */ if ( ! function_exists( 'matewa_catalog_apply_controlled_normalisation' ) ) { function matewa_catalog_apply_controlled_normalisation( $data ) { if ( ! is_array( $data ) ) { $data = array(); } if ( empty( $data['version'] ) ) { $data['version'] = '1.2-main'; } $default_vertical = matewa_catalog_safe_key( isset( $data['default_vertical'] ) ? $data['default_vertical'] : 'general', 'general' ); $data['default_vertical'] = '' !== $default_vertical ? $default_vertical : 'general'; if ( empty( $data['rules'] ) || ! is_array( $data['rules'] ) ) { $data['rules'] = array(); } $data['rules']['max_chips'] = 6; $data['rules']['ensure_other_last'] = true; $data['rules']['other_intent_key'] = 'intent.other'; $data['rules']['other_type'] = isset( $data['rules']['other_type'] ) ? $data['rules']['other_type'] : 'service'; $data['rules']['other_encourage_notes'] = array_key_exists( 'other_encourage_notes', $data['rules'] ) ? (bool) $data['rules']['other_encourage_notes'] : true; $input_verticals = isset( $data['verticals'] ) && is_array( $data['verticals'] ) ? $data['verticals'] : array(); $normalized_verticals = array(); foreach ( $input_verticals as $vertical_key => $vertical_config ) { $vertical_key = matewa_catalog_safe_key( $vertical_key ); if ( '' === $vertical_key ) { continue; } $vertical_config = is_array( $vertical_config ) ? $vertical_config : array(); $label_key = matewa_catalog_safe_text( isset( $vertical_config['label_key'] ) ? $vertical_config['label_key'] : '', 'vertical.' . $vertical_key ); $chips = isset( $vertical_config['chips'] ) && is_array( $vertical_config['chips'] ) ? $vertical_config['chips'] : array(); $dropdown = isset( $vertical_config['dropdown'] ) && is_array( $vertical_config['dropdown'] ) ? $vertical_config['dropdown'] : array(); $rules = isset( $vertical_config['rules'] ) && is_array( $vertical_config['rules'] ) ? $vertical_config['rules'] : array(); $search_terms = isset( $vertical_config['search_terms'] ) && is_array( $vertical_config['search_terms'] ) ? array_values( array_filter( array_map( 'matewa_catalog_safe_text', $vertical_config['search_terms'] ) ) ) : array(); $normalized_verticals[ $vertical_key ] = array( 'label_key' => $label_key, 'chips' => matewa_catalog_normalize_chip_list( $chips ), 'dropdown' => $dropdown, 'rules' => $rules, 'search_terms' => $search_terms, ); } if ( ! isset( $normalized_verticals['general'] ) ) { $normalized_verticals['general'] = array( 'label_key' => 'vertical.general', 'chips' => array( 'intent.quote', 'intent.pricing', 'intent.availability', 'intent.ask_question', 'intent.book_job', 'intent.other', ), 'dropdown' => array(), 'rules' => array(), ); } $data['verticals'] = $normalized_verticals; $data['categories'] = matewa_catalog_normalize_categories( isset( $data['categories'] ) ? $data['categories'] : array(), $data['verticals'] ); if ( ! isset( $data['verticals'][ $data['default_vertical'] ] ) ) { if ( isset( $data['verticals']['general'] ) ) { $data['default_vertical'] = 'general'; } else { $keys = array_keys( $data['verticals'] ); $data['default_vertical'] = ! empty( $keys ) ? (string) $keys[0] : 'general'; } } return $data; } } /** * Resolve which remote payload should be used. * * Routing model: * - free => free payload * - trial_expired => free payload * - trial_active => main payload * - pro => main payload * - master => main payload * * @return array */ function matewa_resolve_vertical_source() { static $resolved_source = null; if ( is_array( $resolved_source ) ) { return $resolved_source; } // Use the effective runtime mode so Founder QA and downgrade states // resolve the same catalogue as the live widget. $mode = function_exists( 'matewa_get_mode' ) ? matewa_get_mode() : ( function_exists( 'matewa_license_status' ) ? matewa_license_status() : MATEWA_MODE_FREE ); $is_free_like = in_array( $mode, array( MATEWA_MODE_FREE, MATEWA_MODE_TRIAL_EXPIRED, ), true ); $full_catalogue_url = 'https://247trademate.pro/247chatmate/vertical-packs/full-catalogue.json'; $resolved_source = array( 'mode' => $is_free_like ? 'free' : 'main', // Catalogue data is identical for Free, Trial and Pro. Use a URL-specific // namespace so legacy 289-vertical transients can never shadow this source. 'cache_key' => 'matewa_remote_verticals_full_' . substr( md5( $full_catalogue_url ), 0, 12 ), 'url' => $full_catalogue_url, 'fallback_url' => 'https://247trademate.pro/247chatmate/verticals.json', ); return $resolved_source; } /** * Clear remote vertical transients. * Useful after changing remote catalog files. * * @return void */ if ( ! function_exists( 'matewa_clear_verticals_cache' ) ) { function matewa_clear_verticals_cache() { $full_catalogue_url = 'https://247trademate.pro/247chatmate/vertical-packs/full-catalogue.json'; $modes = array( 'matewa_remote_verticals_free', 'matewa_remote_verticals_main', 'matewa_remote_verticals_full_' . substr( md5( $full_catalogue_url ), 0, 12 ), ); foreach ( $modes as $base_cache_key ) { $version_key = $base_cache_key . '_version'; $cached_version = get_transient( $version_key ); if ( $cached_version ) { delete_transient( $base_cache_key . '_' . $cached_version ); } delete_transient( $version_key ); } } } /** * Merge optional remote vertical packs onto the canonical catalogue. * * Remote packs live beside the main catalogue under /247chatmate/vertical-packs/. * They are additive data only: product entitlement/locking remains local. * * @param array $data Canonical catalogue. * @return array */ /** * Fetch one remote vertical pack robustly. * * Tries the clean static URL first (best for Hostinger/static files), then a * cache-busted WordPress request, cURL and allow_url_fopen as fallbacks. * A validated last-known-good copy is retained locally. * * @param string $pack_url * @return array */ if ( ! function_exists( 'matewa_fetch_remote_vertical_pack_json' ) ) { function matewa_fetch_remote_vertical_pack_json( $pack_url ) { $pack_url = esc_url_raw( (string) $pack_url ); if ( '' === $pack_url || 0 !== strpos( $pack_url, 'https://' ) ) { return array(); } $cache_key = 'matewa_remote_vertical_pack_' . md5( $pack_url ); $cached = get_transient( $cache_key ); $attempts = array(); $validate = static function ( $raw ) { if ( ! is_string( $raw ) || '' === trim( $raw ) ) { return array(); } $decoded = json_decode( $raw, true ); if ( ! is_array( $decoded ) || empty( $decoded['verticals'] ) || ! is_array( $decoded['verticals'] ) ) { return array(); } return $decoded; }; $wp_args = array( 'timeout' => 10, 'redirection' => 5, 'sslverify' => true, 'user-agent' => '247ChatMate/' . ( defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : 'unknown' ), 'headers' => array( 'Accept' => 'application/json', ), ); // 1. Clean static URL first. $response = wp_remote_get( $pack_url, $wp_args ); if ( ! is_wp_error( $response ) ) { $code = (int) wp_remote_retrieve_response_code( $response ); $attempts[] = 'wp_clean:' . $code; if ( 200 === $code ) { $fresh = $validate( (string) wp_remote_retrieve_body( $response ) ); if ( ! empty( $fresh ) ) { set_transient( $cache_key, $fresh, 6 * HOUR_IN_SECONDS ); set_transient( $cache_key . '_diag', array( 'status' => 'ok', 'source' => 'wp_clean', 'count' => count( $fresh['verticals'] ), 'time' => time(), ), DAY_IN_SECONDS ); return $fresh; } $attempts[] = 'wp_clean:invalid_json'; } } else { $attempts[] = 'wp_clean:error'; } // 2. Cache-busted WP request. $busted_url = add_query_arg( array( '_matewa' => time() ), $pack_url ); $response = wp_remote_get( $busted_url, array_merge( $wp_args, array( 'headers' => array( 'Accept' => 'application/json', 'Cache-Control' => 'no-cache', 'Pragma' => 'no-cache', ), ) ) ); if ( ! is_wp_error( $response ) ) { $code = (int) wp_remote_retrieve_response_code( $response ); $attempts[] = 'wp_busted:' . $code; if ( 200 === $code ) { $fresh = $validate( (string) wp_remote_retrieve_body( $response ) ); if ( ! empty( $fresh ) ) { set_transient( $cache_key, $fresh, 6 * HOUR_IN_SECONDS ); set_transient( $cache_key . '_diag', array( 'status' => 'ok', 'source' => 'wp_busted', 'count' => count( $fresh['verticals'] ), 'time' => time(), ), DAY_IN_SECONDS ); return $fresh; } $attempts[] = 'wp_busted:invalid_json'; } } else { $attempts[] = 'wp_busted:error'; } // 3. Continue to the stream fallback below if both WordPress HTTP requests fail. // 4. allow_url_fopen fallback. if ( filter_var( ini_get( 'allow_url_fopen' ), FILTER_VALIDATE_BOOLEAN ) ) { $context = stream_context_create( array( 'http' => array( 'timeout' => 10, 'header' => "Accept: application/json\r\nUser-Agent: 247ChatMate/" . ( defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : 'unknown' ) . "\r\n", ), 'ssl' => array( 'verify_peer' => true, 'verify_peer_name' => true, ), ) ); $raw = @file_get_contents( $pack_url, false, $context ); $attempts[] = is_string( $raw ) ? 'fopen:response' : 'fopen:error'; $fresh = $validate( is_string( $raw ) ? $raw : '' ); if ( ! empty( $fresh ) ) { set_transient( $cache_key, $fresh, 6 * HOUR_IN_SECONDS ); set_transient( $cache_key . '_diag', array( 'status' => 'ok', 'source' => 'fopen', 'count' => count( $fresh['verticals'] ), 'time' => time(), ), DAY_IN_SECONDS ); return $fresh; } } if ( is_array( $cached ) && ! empty( $cached['verticals'] ) && is_array( $cached['verticals'] ) ) { set_transient( $cache_key . '_diag', array( 'status' => 'cached', 'source' => 'last_known_good', 'count' => count( $cached['verticals'] ), 'attempts' => $attempts, 'time' => time(), ), DAY_IN_SECONDS ); return $cached; } set_transient( $cache_key . '_diag', array( 'status' => 'failed', 'source' => '', 'count' => 0, 'attempts' => $attempts, 'time' => time(), ), DAY_IN_SECONDS ); return array(); } } if ( ! function_exists( 'matewa_catalog_apply_remote_vertical_packs' ) ) { function matewa_catalog_apply_remote_vertical_packs( $data ) { $data = is_array( $data ) ? $data : array(); $pack_urls = array( 'https://247trademate.pro/247chatmate/vertical-packs/high-value.json', ); foreach ( $pack_urls as $pack_url ) { $pack_url = esc_url_raw( (string) $pack_url ); if ( '' === $pack_url || 0 !== strpos( $pack_url, 'https://' ) ) { continue; } $pack = matewa_fetch_remote_vertical_pack_json( $pack_url ); if ( empty( $pack ) ) { continue; } foreach ( array( 'intent_labels', 'vertical_labels' ) as $label_group ) { if ( ! isset( $data[ $label_group ] ) || ! is_array( $data[ $label_group ] ) ) { $data[ $label_group ] = array(); } if ( isset( $pack[ $label_group ] ) && is_array( $pack[ $label_group ] ) ) { $data[ $label_group ] = array_replace( $data[ $label_group ], $pack[ $label_group ] ); } } if ( ! isset( $data['verticals'] ) || ! is_array( $data['verticals'] ) ) { $data['verticals'] = array(); } $data['verticals'] = array_replace( $data['verticals'], $pack['verticals'] ); if ( ! isset( $data['categories'] ) || ! is_array( $data['categories'] ) ) { $data['categories'] = array(); } $pack_categories = isset( $pack['categories'] ) && is_array( $pack['categories'] ) ? $pack['categories'] : array(); foreach ( $pack_categories as $category_key => $category_config ) { $category_key = matewa_catalog_safe_key( $category_key ); if ( '' === $category_key || ! is_array( $category_config ) ) { continue; } $incoming_verticals = isset( $category_config['verticals'] ) && is_array( $category_config['verticals'] ) ? array_values( array_filter( array_map( 'sanitize_key', $category_config['verticals'] ) ) ) : array(); if ( isset( $data['categories'][ $category_key ] ) && is_array( $data['categories'][ $category_key ] ) ) { $existing_verticals = isset( $data['categories'][ $category_key ]['verticals'] ) && is_array( $data['categories'][ $category_key ]['verticals'] ) ? $data['categories'][ $category_key ]['verticals'] : array(); $data['categories'][ $category_key ] = array_merge( $data['categories'][ $category_key ], $category_config ); $data['categories'][ $category_key ]['verticals'] = array_values( array_unique( array_merge( $existing_verticals, $incoming_verticals ) ) ); } else { $category_config['verticals'] = $incoming_verticals; $data['categories'][ $category_key ] = $category_config; } } } return matewa_catalog_apply_controlled_normalisation( $data ); } } /** * Get vertical definitions (remote → cached → safe fallback). * * @return array */ function matewa_get_verticals() { static $request_cache = array(); $source = matewa_resolve_vertical_source(); $base_cache_key = $source['cache_key']; $version_key = $base_cache_key . '_version'; $request_key = (string) $base_cache_key; if ( isset( $request_cache[ $request_key ] ) && is_array( $request_cache[ $request_key ] ) ) { return $request_cache[ $request_key ]; } $cached_version = get_transient( $version_key ); if ( $cached_version ) { $cached = get_transient( $base_cache_key . '_' . $cached_version ); if ( $cached && is_array( $cached ) ) { $cached = matewa_catalog_apply_controlled_normalisation( $cached ); matewa_store_verticals_diagnostic( array( 'resolver_mode' => (string) $source['mode'], 'cache_key' => (string) $base_cache_key, 'url' => (string) $source['url'], 'result' => 'cache_hit', 'reason' => 'cached_payload_returned', 'payload_version' => (string) $cached_version, 'vertical_count' => isset( $cached['verticals'] ) && is_array( $cached['verticals'] ) ? count( $cached['verticals'] ) : 0, ) ); $cached = matewa_catalog_apply_controlled_normalisation( $cached ); $request_cache[ $request_key ] = $cached; return $cached; } matewa_store_verticals_diagnostic( array( 'resolver_mode' => (string) $source['mode'], 'cache_key' => (string) $base_cache_key, 'url' => (string) $source['url'], 'result' => 'cache_miss_after_version_hit', 'reason' => 'version_transient_found_but_payload_missing', 'payload_version' => (string) $cached_version, ) ); } if ( function_exists( 'matewa_is_admin_settings_vertical_guard_context' ) && matewa_is_admin_settings_vertical_guard_context() ) { $fallback = matewa_catalog_apply_controlled_normalisation( matewa_verticals_fallback() ); $request_cache[ $request_key ] = $fallback; return $fallback; } $source_request_url = add_query_arg( array( 'plugin' => defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : 'unknown', '_matewa_verticals' => time(), ), $source['url'] ); $request_args = array( 'timeout' => 10, 'redirection' => 5, 'sslverify' => true, 'user-agent' => '247ChatMate/' . ( defined( 'MATEWA_VERSION' ) ? MATEWA_VERSION : 'unknown' ), 'headers' => array( 'Accept' => 'application/json', 'Cache-Control' => 'no-cache', 'Pragma' => 'no-cache', ), ); $response = wp_remote_get( $source_request_url, $request_args ); // Compatibility fallback only. The complete catalogue above remains canonical. if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { $fallback_url = isset( $source['fallback_url'] ) ? esc_url_raw( (string) $source['fallback_url'] ) : ''; if ( '' !== $fallback_url ) { $response = wp_remote_get( add_query_arg( array( '_matewa_verticals' => time() ), $fallback_url ), $request_args ); } } if ( is_wp_error( $response ) ) { matewa_store_verticals_diagnostic( array( 'resolver_mode' => (string) $source['mode'], 'cache_key' => (string) $base_cache_key, 'url' => (string) $source['url'], 'result' => 'fallback', 'reason' => 'wp_remote_get_error', 'response_code' => '', ) ); $fallback = matewa_catalog_apply_controlled_normalisation( matewa_verticals_fallback() ); $request_cache[ $request_key ] = $fallback; return $fallback; } $code = wp_remote_retrieve_response_code( $response ); if ( 200 !== (int) $code ) { matewa_store_verticals_diagnostic( array( 'resolver_mode' => (string) $source['mode'], 'cache_key' => (string) $base_cache_key, 'url' => (string) $source['url'], 'result' => 'fallback', 'reason' => 'non_200_response', 'response_code' => (string) $code, ) ); $fallback = matewa_catalog_apply_controlled_normalisation( matewa_verticals_fallback() ); $request_cache[ $request_key ] = $fallback; return $fallback; } $body = wp_remote_retrieve_body( $response ); $data = json_decode( $body, true ); if ( ! is_array( $data ) || empty( $data['version'] ) || empty( $data['verticals'] ) || ! is_array( $data['verticals'] ) ) { matewa_store_verticals_diagnostic( array( 'resolver_mode' => (string) $source['mode'], 'cache_key' => (string) $base_cache_key, 'url' => (string) $source['url'], 'result' => 'fallback', 'reason' => 'invalid_json_payload', 'response_code' => (string) $code, ) ); $fallback = matewa_catalog_apply_controlled_normalisation( matewa_verticals_fallback() ); $request_cache[ $request_key ] = $fallback; return $fallback; } $data = matewa_catalog_apply_controlled_normalisation( $data ); $default = isset( $data['default_vertical'] ) ? $data['default_vertical'] : 'general'; if ( ! isset( $data['verticals'][ $default ] ) ) { $default = 'general'; $data['default_vertical'] = $default; } $version = (string) $data['version']; set_transient( $base_cache_key . '_' . $version, $data, 15 * MINUTE_IN_SECONDS ); set_transient( $version_key, $version, 15 * MINUTE_IN_SECONDS ); matewa_store_verticals_diagnostic( array( 'resolver_mode' => (string) $source['mode'], 'cache_key' => (string) $base_cache_key, 'url' => (string) $source['url'], 'result' => 'remote_success', 'reason' => 'remote_payload_loaded', 'response_code' => (string) $code, 'payload_version' => $version, 'vertical_count' => count( $data['verticals'] ), ) ); $request_cache[ $request_key ] = $data; return $data; } /** * Get one vertical config by key, safely. * * @param string $vertical_key * @return array */ if ( ! function_exists( 'matewa_get_vertical_config' ) ) { function matewa_get_vertical_config( $vertical_key ) { $data = matewa_get_verticals(); $verticals = isset( $data['verticals'] ) && is_array( $data['verticals'] ) ? $data['verticals'] : array(); $vertical_key = is_string( $vertical_key ) ? trim( $vertical_key ) : ''; if ( '' !== $vertical_key && isset( $verticals[ $vertical_key ] ) && is_array( $verticals[ $vertical_key ] ) ) { return $verticals[ $vertical_key ]; } $default = isset( $data['default_vertical'] ) && is_string( $data['default_vertical'] ) ? $data['default_vertical'] : 'general'; if ( isset( $verticals[ $default ] ) && is_array( $verticals[ $default ] ) ) { return $verticals[ $default ]; } if ( isset( $verticals['general'] ) && is_array( $verticals['general'] ) ) { return $verticals['general']; } return array( 'label_key' => 'vertical.general', 'chips' => array( 'intent.quote', 'intent.pricing', 'intent.availability', 'intent.ask_question', 'intent.book_job', 'intent.other', ), 'dropdown' => array(), 'rules' => array(), ); } } /** * Resolve the active vertical safely. * * @return array */ function matewa_get_active_vertical() { $data = matewa_get_verticals(); $verticals = isset( $data['verticals'] ) && is_array( $data['verticals'] ) ? $data['verticals'] : array(); $default = isset( $data['default_vertical'] ) ? $data['default_vertical'] : 'general'; $selected = get_option( 'matewa_vertical_selected', '' ); $selected = is_string( $selected ) ? trim( $selected ) : ''; if ( '' !== $selected && isset( $verticals[ $selected ] ) ) { return $verticals[ $selected ]; } if ( isset( $verticals[ $default ] ) ) { return $verticals[ $default ]; } return isset( $verticals['general'] ) ? $verticals['general'] : array( 'label_key' => 'vertical.general', 'chips' => array( 'intent.quote', 'intent.pricing', 'intent.availability', 'intent.ask_question', 'intent.book_job', 'intent.other', ), 'dropdown' => array(), 'rules' => array(), ); } /** * Resolve the active vertical key safely. * * @return string */ if ( ! function_exists( 'matewa_get_active_vertical_key' ) ) { function matewa_get_active_vertical_key() { $data = matewa_get_verticals(); $verticals = isset( $data['verticals'] ) && is_array( $data['verticals'] ) ? $data['verticals'] : array(); $default = isset( $data['default_vertical'] ) ? $data['default_vertical'] : 'general'; $selected = get_option( 'matewa_vertical_selected', '' ); $selected = is_string( $selected ) ? trim( $selected ) : ''; if ( '' !== $selected && isset( $verticals[ $selected ] ) ) { return $selected; } if ( isset( $verticals[ $default ] ) ) { return (string) $default; } if ( isset( $verticals['general'] ) ) { return 'general'; } $keys = array_keys( $verticals ); if ( ! empty( $keys ) ) { return (string) $keys[0]; } return 'general'; } } /** * Get visible default chip labels for a vertical. * This is what the widget/admin preview/editor defaults should use. * * @param string $vertical_key * @return array */ if ( ! function_exists( 'matewa_get_vertical_chip_labels' ) ) { function matewa_get_vertical_chip_labels( $vertical_key ) { $config = matewa_get_vertical_config( $vertical_key ); $chips = isset( $config['chips'] ) && is_array( $config['chips'] ) ? $config['chips'] : array(); $labels = array(); foreach ( $chips as $intent_key ) { $labels[] = matewa_catalog_resolve_intent_label( (string) $intent_key ); } return $labels; } } if ( ! function_exists( 'matewa_get_effective_vertical_chip_labels' ) ) { function matewa_get_effective_vertical_chip_labels( $vertical_key ) { $vertical_key = matewa_catalog_safe_key( $vertical_key, 'general' ); $config = matewa_get_vertical_config( $vertical_key ); $chips = isset( $config['chips'] ) && is_array( $config['chips'] ) ? $config['chips'] : array(); $overrides = get_option( 'matewa_chip_label_overrides', array() ); $overrides = is_array( $overrides ) ? $overrides : array(); $vertical_overrides = isset( $overrides[ $vertical_key ] ) && is_array( $overrides[ $vertical_key ] ) ? $overrides[ $vertical_key ] : array(); $labels = array(); foreach ( $chips as $intent_key ) { $intent_key = is_string( $intent_key ) ? trim( $intent_key ) : ''; $override_label = ''; if ( '' !== $intent_key && isset( $vertical_overrides[ $intent_key ] ) ) { $override_label = trim( (string) $vertical_overrides[ $intent_key ] ); } if ( '' !== $override_label ) { $labels[] = $override_label; } else { $labels[] = matewa_catalog_resolve_intent_label( $intent_key ); } } return $labels; } } /** * Get visible effective chip labels for the active vertical. * * @return array */ if ( ! function_exists( 'matewa_get_active_vertical_chip_labels' ) ) { function matewa_get_active_vertical_chip_labels() { return matewa_get_effective_vertical_chip_labels( matewa_get_active_vertical_key() ); } } /** * Get visible vertical label for a vertical key. * * @param string $vertical_key * @return string */ if ( ! function_exists( 'matewa_get_vertical_display_label' ) ) { function matewa_get_vertical_display_label( $vertical_key ) { $config = matewa_get_vertical_config( $vertical_key ); $label_key = isset( $config['label_key'] ) ? (string) $config['label_key'] : ''; return matewa_catalog_resolve_vertical_label( $vertical_key, $label_key ); } } /** * Absolute safety fallback. * This must NEVER fail. * * @return array */ function matewa_verticals_fallback() { $fallback = array( 'version' => '1.2-fallback', 'default_vertical' => 'general', 'categories' => array( 'general' => array( 'label' => 'General', 'verticals' => array( 'general' ), ), ), 'verticals' => array( 'general' => array( 'label_key' => 'vertical.general', 'chips' => array( 'intent.quote', 'intent.pricing', 'intent.availability', 'intent.ask_question', 'intent.book_job', 'intent.other', ), 'dropdown' => array(), 'rules' => array(), ), ), 'rules' => array( 'max_chips' => 6, 'ensure_other_last' => true, 'other_intent_key' => 'intent.other', 'other_type' => 'service', 'other_encourage_notes' => true, ), ); return matewa_catalog_apply_controlled_normalisation( $fallback ); }