Plugin Directory

Changeset 3300778


Ignore:
Timestamp:
05/26/2025 01:54:41 PM (10 months ago)
Author:
metriondev
Message:

Version 1.4.0

Location:
metrion
Files:
82 added
6 edited

Legend:

Unmodified
Added
Removed
  • metrion/trunk/includes/event_capture.php

    r3296257 r3300778  
    3737    $allow_pii = isset($metrion_consent_cookie_value['allow_pii']) && $metrion_consent_cookie_value['allow_pii'] === "1";
    3838    $allow_marketing = isset($metrion_consent_cookie_value['allow_marketing']) && $metrion_consent_cookie_value['allow_marketing'] === "1";
     39    $blocking_detected = isset($metrion_consent_cookie_value['b']) && $metrion_consent_cookie_value['b'] === "1";
    3940
    4041    // Construct the base event object
    4142    $event_data = metrion_get_event_data($order_id, $triggered_by_hook, $user_cookie_value, $session_cookie_value, $order, $metrion_consent_cookie_value);
     43    // Add consent-related flags to the consent object in event_data
     44    if (isset($event_data['consent']) && is_array($event_data['consent'])) {
     45        $event_data['consent']['allow_uid'] = (int) $allow_uid;
     46        $event_data['consent']['allow_sid'] = (int) $allow_sid;
     47        $event_data['consent']['allow_pii'] = (int) $allow_pii;
     48        $event_data['consent']['allow_marketing'] = (int) $allow_marketing;
     49        $event_data['consent']['b'] = (int) $blocking_detected;
     50    } else {
     51        $event_data['consent'] = [
     52            'allow_uid' => (int) $allow_uid,
     53            'allow_sid' => (int) $allow_sid,
     54            'allow_pii' => (int) $allow_pii,
     55            'allow_marketing' => (int) $allow_marketing,
     56            'b' => (int) $blocking_detected
     57        ];
     58    }
    4259
    4360    // Enforce consent on the payload of the event object
     
    136153    $billing_email      = !empty($billing_data['email']) ? hash('sha256', sanitize_email($billing_data['email'])) : '';
    137154    $billing_phone      = !empty($billing_data['phone']) ? hash('sha256', sanitize_text_field($billing_data['phone'])) : '';
    138     $street_address      = !empty($billing_data['street_address']) ? hash('sha256', sanitize_text_field($billing_data['street_address'])) : '';
     155    $street_address      = !empty($billing_data['address_1']) ? hash('sha256', sanitize_text_field($billing_data['address_1'])) : '';
    139156
    140157    // IP Address Validation
     
    156173    $shipping_tax = floatval($order->get_shipping_tax());
    157174    $shipping_incl_tax = $shipping_excl_tax + $shipping_tax;
    158 
    159175
    160176    return array(
     
    196212    return array(
    197213        // Core configuration settings
    198         'version' => '1.3.0',
     214        'version' => GLOBAL_METRION_PLUGIN_VERSION,
    199215        'installation_id' => get_option('metrion_installation_id', ''),
    200216        'webhook_url' => get_option('metrion_webhook_destination', 'https://hub.getmetrion.com/stream/wordpress'),
     
    209225       
    210226        // Consent information
    211         'allow_marketing' => get_option('metrion_allow_marketing', true),
    212         'allow_pii' => get_option('metrion_allow_pii', true),
     227        'allow_marketing' => get_option('metrion_allow_marketing', false),
     228        'allow_pii' => get_option('metrion_allow_pii', "false"),
    213229        'allow_uid' => get_option('metrion_allow_uid', true),
    214230        'allow_sid' => get_option('metrion_allow_sid', true),
     
    323339
    324340    // Get the file modification time of this script for cache busting
    325     $script_version = file_exists(__FILE__) ? filemtime(__FILE__) : '1.0.0';
     341    $script_version = file_exists(__FILE__) ? filemtime(__FILE__) : '1.1.0';
    326342
    327343    // Register a dummy script to ensure script execution
  • metrion/trunk/includes/initial.php

    r3296257 r3300778  
    1515});
    1616
    17 /**
    18  * Security: Ensure the request comes from the same website origin
    19  */
     17// Security: Ensure the request comes from the same website origin
    2018function metrion_check_request_origin(WP_REST_Request $request) {
    2119    $allowed_origin = get_site_url(); // Get the current site's base URL
     
    4947
    5048    $excluded_forms = array_filter(
    51         array_map('trim', preg_split('/\r\n|\r|\n/', get_option('metrion_excluded_forms', ''))),
     49        array_map('trim', preg_split('/\r\n|\r|\n/', get_option('metrion_elementor_excluded_forms', ''))),
    5250        function($item) {
    5351            return !empty($item);
     
    5755    return [
    5856        // Metrion settings
    59         'version' => '1.3.0',
     57        'version' => GLOBAL_METRION_PLUGIN_VERSION,
    6058        'installation_id' => get_option('metrion_installation_id', ''),
    6159        'webhook_url' => get_option('metrion_webhook_destination', 'https://hub.getmetrion.com/stream/wordpress'),
     
    6866        'api_path_part_1' => get_option('metrion_api_path_part_1', 'metrion'),
    6967        'api_path_part_2' => get_option('metrion_api_path_part_2', 'event'),
    70         'debug_mode' => get_option('metrion_debug_mode', '0'),
     68        'debug_mode' => get_option('metrion_debug_mode', 0),
    7169
    7270        // Consent manager settings
    73         'allow_marketing' => get_option('metrion_allow_marketing', true),
    74         'allow_pii' => get_option('metrion_allow_pii', true),
    75         'allow_uid' => get_option('metrion_allow_uid', true),
    76         'allow_sid' => get_option('metrion_allow_sid', true),
     71        'allow_marketing' => get_option('metrion_allow_marketing', 0),
     72        'allow_pii' => get_option('metrion_allow_pii', 0),
     73        'allow_uid' => get_option('metrion_allow_uid', 1),
     74        'allow_sid' => get_option('metrion_allow_sid', 1),
    7775        'cmp_selection' => get_option('metrion_cmp_selection', 'none'),
    7876        'consent_cookie_name' => get_option('metrion_consent_cookie_name', 'mtrn_consent'),
    7977        'floodgate_name' => get_option('metrion_floodgate_name', 'mtrn_floodgate'),
    8078        'click_ids_cookie_name' => get_option('metrion_click_ids_cookie_name', 'mtrn_cids'),
    81         'allow_cookie_placement_before_explicit_consent' => get_option('metrion_allow_cookie_placement_before_explicit_consent', true),
     79        'allow_cookie_placement_before_explicit_consent' => get_option('metrion_allow_cookie_placement_before_explicit_consent', 0),
    8280
    8381        // Google settings
    84         'google_enable_tracking' => get_option('metrion_google_enable_tracking', true),
    85         'google_enforce_consent_mode' => get_option('metrion_enforce_google_consent_mode', false),
     82        'google_enable_tracking' => get_option('metrion_google_enable_tracking', 0),
     83        'google_enforce_consent_mode' => get_option('metrion_enforce_google_consent_mode', 0),
    8684        'metrion_google_ads_purchase_enhanced_conversion_label' => get_option('metrion_google_ads_purchase_enhanced_conversion_label', ''),
    8785        'metrion_google_ads_tag_id' => get_option('metrion_google_ads_tag_id', ''),
    8886        'google_ads_account_id' => get_option('metrion_google_ads_account_id', ''),
    89         'google_enable_enhanced_conversions' => get_option('metrion_enable_google_ads_enhanced_conversions', false),
    90         'google_ads_enable_dynamic_remarketing' => get_option('metrion_enable_google_ads_dynamic_remarketing', false),
     87        'google_enable_enhanced_conversions' => get_option('metrion_enable_google_ads_enhanced_conversions', 0),
     88        'google_ads_enable_dynamic_remarketing' => get_option('metrion_enable_google_ads_dynamic_remarketing', 0),
    9189
    9290        // Meta settings
    93         'meta_enable_tracking' => get_option('metrion_meta_enable_tracking', false),
     91        'meta_enable_tracking' => get_option('metrion_meta_enable_tracking', 0),
    9492        'meta_pixel_id' => get_option('metrion_meta_pixel_id', ''),
    9593        'meta_capi_token' => get_option('metrion_meta_capi_token', ''),
    9694        'meta_test_event_code' => get_option('metrion_meta_test_event_code', ''),
    9795
    98         'form_tracking' => get_option('metrion_form_tracking', 1),
    99         'excluded_forms' => $excluded_forms,
     96        'elementor_form_tracking' => get_option('metrion_enable_elementor_form_tracking', 0),
     97        'elementor_excluded_forms' => $excluded_forms,
    10098    ];
    10199}
     
    168166}
    169167
    170 
    171 /**
    172  * Retrieves the user's IP address in a sanitized and secure way.
    173  *
    174  * @return string The sanitized user IP address or 'Unknown IP' if not found.
    175  */
     168// Helper function to get user ip (needs update)
    176169function metrion_get_user_ip() {
    177170    $ip_sources = [
     
    206199    return 'Unknown IP';
    207200}
    208 
    209 
    210201
    211202// Map and enrich the Metrion event data
     
    218209    $mapped_data['metrion_session_id'] = $data[$session_cookie_name] ?? null;
    219210    $mapped_data['metrion_event_id'] = $data[$event_id_name] ?? null;
    220 
    221    
    222211
    223212    // Map the events coming from the front-end JS
     
    327316}
    328317
    329 // 2 IDENTIFICATION HANDLING
    330 
     318// Helper function to generate UUID
    331319function metrion_generateUUID() {
    332320    $data = random_bytes(16);
     
    336324}
    337325
    338 /**
    339  * Retrieves the top-level domain (TLD) plus one subdomain (TLD+1).
    340  *
    341  * @return string The extracted TLD+1 or an empty string if unavailable.
    342  */
     326// Helper function to get tld plus one TLD+1 (top level domain)
    343327function metrion_get_tld_plus_one() {
    344328    // List of known public suffixes (expandable)
     
    381365}
    382366
    383 
     367// Helper function to set cookie
    384368function metrion_set_cookie($name, $value, $expiration_days, $http_only = false, $secure_only = true) {
    385369    static $cookie_domain = null; // Cache the domain
     
    421405}
    422406
     407// Helper function to refresh user id cookie
    423408function metrion_refresh_cookie($cookie_name, $cookie_expiration) {
    424409    $uuid = metrion_generateUUID();
     
    429414}
    430415
     416// Helper function to extend user id cookie
    431417function metrion_extend_cookie($cookie_name, $uuid, $cookie_expiration) {
    432418    $expiration_timestamp = (time() + 86400 * $cookie_expiration) * 1000;
     
    436422}
    437423
     424// Helper function to update identification (user cookie) values
    438425function metrion_update_identification_cookies(){
    439426    $user_cookie_name = sanitize_key( get_option('metrion_user_cookie_name', 'mtrn_uid') );
     
    465452}
    466453
     454// Function to start initialisation of Metrion identification
    467455function metrion_initial_config() {
    468456    $allow_cookie_placement_before_explicit_consent = get_option('metrion_allow_cookie_placement_before_explicit_consent', 1);
     
    489477add_action('init', 'metrion_initial_config', 10);
    490478
    491 
     479// Function to load the main Metrion js bundle
    492480function enqueue_metrion_js() {
    493     wp_enqueue_script(
    494         'metrion_event_js',
    495         plugins_url('js/events.js', __DIR__), // Cleaner way to get the script URL
    496         [], // No dependencies
    497         filemtime(plugin_dir_path(__DIR__) . 'js/events.js'), // Dynamic versioning using file modification time
    498         true // Load in the footer
    499     );
     481    $path_part_1 = get_option('metrion_api_path_part_1', 'metrion');
     482    $path_part_2 = get_option('metrion_api_path_part_2', 'event');
     483    $metrion_use_api_endpoints_to_load_js = get_option('metrion_use_api_endpoints_to_load_js', false);
     484
     485    // Destination Logic
     486    $consent_cookie_name = sanitize_key(get_option('metrion_consent_cookie_name', 'mtrn_consent'));
     487    $allow_marketing = False;
     488    // Get the pre-consent bundle by default
     489    $bundle_to_enqueue = "pre-bundle";
     490
     491    // Check if the consent cookie exists and is not empty
     492    if (!empty($_COOKIE[$consent_cookie_name])) {
     493        // Decode the cookie value safely
     494        $cookie_raw = isset($_COOKIE[$consent_cookie_name]) ? sanitize_text_field(wp_unslash($_COOKIE[$consent_cookie_name])) : '';
     495        $cookie_json = urldecode($cookie_raw);          // Decode URL encoding
     496        $cookie_json = stripslashes($cookie_json);      // Handle double-encoding
     497        $cookie_data = json_decode($cookie_json, true); // JSON decoding
     498
     499        // Validate JSON structure
     500        if (json_last_error() !== JSON_ERROR_NONE || !is_array($cookie_data)) {
     501            return;
     502        }
     503
     504        // Check if marketing tracking is allowed
     505        $allow_marketing = isset($cookie_data['allow_marketing'])
     506            ? filter_var($cookie_data['allow_marketing'], FILTER_VALIDATE_BOOLEAN)
     507            : false;
     508
     509        // If marketing is allowed, get the full bundle
     510        if($allow_marketing){
     511            // Get the normal bundle name
     512            $bundle_to_enqueue = "bundle";
     513        }
     514    }
     515
     516    // Load the JS through the custom api endpoint
     517    if($metrion_use_api_endpoints_to_load_js){
     518        wp_enqueue_script(
     519            'metrion_event_js',
     520            site_url('/wp-json/' . $path_part_1 .'/' . $path_part_2 . '/' . $bundle_to_enqueue),
     521            [], // No dependencies
     522            GLOBAL_METRION_PLUGIN_VERSION,
     523            true // Load in the footer
     524        );
     525    }
     526   
     527    // Load the JS through the Metrion directory structure
     528    else{
     529        $directory_js_bundle_url = plugins_url('js/bundles/'. $bundle_to_enqueue . '.js', __DIR__);
     530        wp_enqueue_script(
     531            'metrion_event_js',
     532            $directory_js_bundle_url,
     533            [], // No dependencies
     534            GLOBAL_METRION_PLUGIN_VERSION,
     535            true // Load in the footer
     536        );       
     537    }
    500538   
    501539    wp_localize_script('metrion_event_js', 'metrion_api', [
     
    529567        'meta_pixel_id' => get_option('metrion_meta_pixel_id', ''),
    530568        'meta_test_event_code' => get_option('metrion_meta_test_event_code', ''),
    531         'purchase_only_tracking' => get_option('metrion_purchase_only_tracking', 1),
     569        'purchase_only_tracking' => get_option('metrion_purchase_only_tracking', 0),
    532570        'microsoft_ads_enable_tracking' => get_option('metrion_microsoft_ads_enable_tracking', 0),
    533571        'microsoft_ads_enforce_consent_mode' => get_option('metrion_microsoft_ads_enforce_consent_mode', 0),
    534572        'microsoft_ads_enable_dynamic_remarketing' => get_option('metrion_microsoft_ads_enable_dynamic_remarketing', 0),
    535573        'microsoft_ads_tag_id' => get_option('metrion_microsoft_ads_tag_id', ''),
    536         'form_tracking' => get_option('metrion_form_tracking', ''),
    537         'excluded_forms' => get_option('metrion_excluded_forms', ''),
    538         'allow_pings' => get_option('metrion_allow_pre_consent_pings', 1),
    539         'enable_detection' => get_option('metrion_enable_block_detection', 0)
     574        'elementor_form_tracking' => get_option('metrion_enable_elementor_form_tracking', ''),
     575        'elementor_excluded_forms' => get_option('metrion_elementor_excluded_forms', ''),
     576        'allow_pings' => get_option('metrion_allow_pre_consent_pings', 0),
     577        'enable_detection' => get_option('metrion_enable_block_detection', 1)
    540578    ]);
    541 
    542     // After loading the main script. Load the other destination JS files
    543     metrion_enqueue_destination_scripts();
    544579}
    545580
    546581add_action('wp_enqueue_scripts', 'enqueue_metrion_js', 11); // Ensure JS loads after cookies
    547582
     583// Hook to enqueue destination scripts based on user consent
     584add_action('wp_enqueue_scripts', 'metrion_enqueue_detect_script', 25);
     585
     586// Enqueues tracking scripts based on user consent stored in a cookie.
     587function metrion_enqueue_detect_script() {
     588    // Exit if detection is not required
     589    if(!get_option('metrion_enable_block_detection', false)){
     590        return;
     591    }
     592
     593    // Destination Logic
     594    $consent_cookie_name = sanitize_key(get_option('metrion_consent_cookie_name', 'mtrn_consent'));
     595
     596    // Check if the consent cookie exists and is not empty
     597    if (empty($_COOKIE[$consent_cookie_name])) {
     598        return;
     599    }
     600
     601    // Decode the cookie value safely
     602    $cookie_raw = isset($_COOKIE[$consent_cookie_name]) ? sanitize_text_field(wp_unslash($_COOKIE[$consent_cookie_name])) : '';
     603    $cookie_json = urldecode($cookie_raw);          // Decode URL encoding
     604    $cookie_json = stripslashes($cookie_json);      // Handle double-encoding
     605    $cookie_data = json_decode($cookie_json, true); // JSON decoding
     606
     607    // Validate JSON structure
     608    if (json_last_error() !== JSON_ERROR_NONE || !is_array($cookie_data)) {
     609        return;
     610    }
     611
     612    // Check if marketing tracking is allowed
     613    $allow_marketing = isset($cookie_data['allow_marketing'])
     614        ? filter_var($cookie_data['allow_marketing'], FILTER_VALIDATE_BOOLEAN)
     615        : false;
     616
     617    if (!$allow_marketing) {
     618        return;
     619    }
     620
     621    // Check if Metrion detect script is not already fired before in user/session history
     622    $detection_finished = isset($cookie_data['b'])
     623    ? filter_var($cookie_data['b'], FILTER_VALIDATE_BOOLEAN)
     624    : false;
     625
     626    // Only if detection is not finished, load the detection JS
     627    if(!$detection_finished){
     628
     629        // Load the JS through the custom api endpoint
     630        $metrion_use_api_endpoints_to_load_js = get_option('metrion_use_api_endpoints_to_load_js', false);
     631        $path_part_1 = get_option('metrion_api_path_part_1', 'metrion');
     632        $path_part_2 = get_option('metrion_api_path_part_2', 'event');
     633        if($metrion_use_api_endpoints_to_load_js){
     634            wp_enqueue_script(
     635                'metrion_detect_js',
     636                site_url('/wp-json/' . $path_part_1 .'/' . $path_part_2 . '/detect-bundle'),
     637                [], // No dependencies
     638                GLOBAL_METRION_PLUGIN_VERSION,
     639                true // Load in the footer
     640            );
     641        }
     642        // Load through the plugin directory
     643        else{           
     644            wp_enqueue_script(
     645                'metrion_detect_js',
     646                plugins_url('js/detect/detect.js', __DIR__),
     647                [], // No dependencies
     648                GLOBAL_METRION_PLUGIN_VERSION,
     649                true // Load in the footer
     650            );
     651        }
     652    }
     653}
     654
     655
     656// Register the Metrion Ping API route
    548657add_action('rest_api_init', function () {
    549 
    550     $path_part_1 = get_option('metrion_api_path_part_1', 'metrion');
    551     $path_part_2 = get_option('metrion_api_path_part_2', 'event');
    552     register_rest_route($path_part_1, '/' . $path_part_2 . '/cookies', [
    553         'methods'  => 'POST',
    554         'callback' => 'metrion_update_identification_cookies_api',
    555         'permission_callback' => 'metrion_check_request_origin' // Allows public access (modify if needed)
     658    $namespace = get_option('metrion_api_path_part_1', 'metrion');
     659    $route     = get_option('metrion_api_path_part_2', 'event') . '/ping';
     660
     661    register_rest_route($namespace, '/' . $route, [
     662        'methods'             => 'POST',
     663        'callback'            => 'handle_metrion_ping',
     664        'permission_callback' => 'metrion_check_request_origin',
    556665    ]);
    557666});
    558667
    559 function metrion_update_identification_cookies_api(WP_REST_Request $request) {
    560     // Sanitize and fetch cookie settings
    561     $cookie_name = sanitize_key(get_option('metrion_user_cookie_name', 'mtrn_uid'));
    562     $cookie_expiration_days = absint(get_option('metrion_user_cookie_lifetime', 365));
    563 
    564     // If cookie is not set or is empty, refresh it
    565     if (empty($_COOKIE[$cookie_name])) {
    566         metrion_refresh_cookie($cookie_name, $cookie_expiration_days);
    567         return new WP_REST_Response(['status' => 'cookie set'], 200);
    568     }
    569 
    570     // Sanitize cookie value
    571     $user_cookie_value = sanitize_text_field(wp_unslash($_COOKIE[$cookie_name]));
    572 
    573     // Split into parts and validate structure
    574     $user_cookie_parts = explode('--', $user_cookie_value);
    575     if (count($user_cookie_parts) !== 2) {
    576         metrion_refresh_cookie($cookie_name, $cookie_expiration_days);
    577         return new WP_REST_Response(['status' => 'cookie refreshed'], 200);
    578     }
    579 
    580     // Extract and sanitize UUID and expiration timestamp
    581     list($uuid, $expiration_timestamp) = array_map('sanitize_text_field', array_pad($user_cookie_parts, 2, ''));
    582 
    583     // Ensure timestamp is a valid integer
    584     $expiration_timestamp = absint($expiration_timestamp);
    585 
    586     // Validate expiration time (convert to milliseconds for consistency)
    587     if ((time() * 1000) >= $expiration_timestamp) {
    588         metrion_refresh_cookie($cookie_name, $cookie_expiration_days);
    589         return new WP_REST_Response(['status' => 'cookie refreshed'], 200);
    590     }
    591 
    592     // Extend the cookie if still valid
    593     metrion_extend_cookie($cookie_name, $uuid, $cookie_expiration_days);
    594     return new WP_REST_Response(['status' => 'cookie extended'], 200);
    595 }
    596 
    597 function metrion_get_script_urls() {
     668// Ping API handler
     669function handle_metrion_ping(WP_REST_Request $request) {
     670    $body = json_decode($request->get_body(), true);
     671
     672    if (!is_array($body)) {
     673        return new WP_Error('invalid_json', 'Invalid JSON body', ['status' => 400]);
     674    }
     675
     676    $data = [
     677        'event'    => isset($body['event']) ? sanitize_text_field($body['event']) : '',
     678        'id'       => isset($body['id']) ? sanitize_text_field($body['id']) : '',
     679        'origin'   => isset($body['origin']) ? esc_url_raw($body['origin']) : '',
     680        'referrer' => isset($body['referrer']) ? esc_url_raw($body['referrer']) : '',
     681    ];
     682
     683    $ping_endpoint = 'https://stream.getmetrion.com/wp/ping/';
     684
     685    $response = wp_remote_post($ping_endpoint, [
     686        'body'    => wp_json_encode($data),
     687        'headers' => ['Content-Type' => 'application/json'],
     688        'timeout' => 20,
     689    ]);
     690
     691    return is_wp_error($response)
     692        ? new WP_Error('ping_failed', 'Failed to forward ping', ['status' => 500])
     693        : ['success' => true];
     694}
     695
     696
     697// Get dynamic destination JS script urls based on consent
     698function metrion_get_destination_bundle_url() {
    598699    $consent_cookie_name = sanitize_key( get_option('metrion_consent_cookie_name', 'mtrn_consent') );
    599700    // Decode the cookie value safely
     
    619720    }
    620721
    621 
    622722    // If marketing is allowed start enqueings scripts
    623723    $scripts = [];
    624724
    625     // Meta Ads script
    626     if (get_option('metrion_meta_enable_tracking', false) && get_option('metrion_meta_allow_front_end_pixel_tracking', false)) {
    627         $meta_script_path = plugin_dir_path(__DIR__) . 'js/meta/events.js';
    628         if (file_exists($meta_script_path)) {
    629             $meta_script_version = filemtime($meta_script_path);
    630             $scripts[] = plugins_url('js/meta/events.js', __DIR__) . '?ver=' . $meta_script_version;
    631         }
    632     }
    633 
    634     // Google Ads script
    635     if (get_option('metrion_google_enable_tracking', false)) {
    636         $google_script_path = plugin_dir_path(__DIR__) . 'js/google_ads/events.js';
    637         if (file_exists($google_script_path)) {
    638             $google_script_version = filemtime($google_script_path);
    639             $scripts[] = plugins_url('js/google_ads/events.js', __DIR__) . '?ver=' . $google_script_version;
    640         }
    641     }
    642 
    643     // Microsoft Ads script
    644     if (get_option('metrion_microsoft_ads_enable_tracking', false)) {
    645         $microsoft_ads_script_path = plugin_dir_path(__DIR__) . 'js/microsoft_ads/events.js';
    646         if (file_exists($microsoft_ads_script_path)) {
    647             $microsoft_ads_script_version = filemtime($microsoft_ads_script_path);
    648             $scripts[] = plugins_url('js/microsoft_ads/events.js', __DIR__) . '?ver=' . $microsoft_ads_script_version;
    649         }
    650     }
    651 
     725    // Load the JS through the custom api endpoint or through directory structure
     726    $metrion_use_api_endpoints_to_load_js = get_option('metrion_use_api_endpoints_to_load_js', false);
     727    $path_part_1 = get_option('metrion_api_path_part_1', 'metrion');
     728    $path_part_2 = get_option('metrion_api_path_part_2', 'event');
     729    if($metrion_use_api_endpoints_to_load_js){
     730        $scripts[] = site_url('/wp-json/' . $path_part_1 .'/' . $path_part_2 . '/destination-bundle') . '?ver=' . GLOBAL_METRION_PLUGIN_VERSION;
     731    }
     732    else{
     733        $directory_js_bundle = plugins_url('/js/bundles/destination-bundle.js', __DIR__);
     734        $scripts[] = $directory_js_bundle . '?ver=' . GLOBAL_METRION_PLUGIN_VERSION;
     735    }
    652736    return $scripts;
    653737}
    654738
    655 
    656 // REST API Callback Function
     739// Dynamic destination API Callback Function
    657740function metrion_load_destination_js_api(WP_REST_Request $request) {
    658     $scripts = metrion_get_script_urls(); // Get script URLs
    659 
     741    $scripts = metrion_get_destination_bundle_url(); // Get script URLs
    660742    return new WP_REST_Response(['status' => 'destination js loaded', 'scripts' => $scripts], 200);
    661743}
    662744
    663 
    664 // Register REST API Endpoint
     745// Register dynamic destination REST API Endpoint
    665746add_action('rest_api_init', function () {
    666747    $path_part_1 = get_option('metrion_api_path_part_1', 'metrion');
     
    673754    ]);
    674755});
    675 
    676 
    677 // Hook to enqueue destination scripts based on user consent
    678 add_action('wp_enqueue_scripts', 'metrion_enqueue_destination_scripts', 25);
    679 
    680 
    681 /**
    682  * Enqueues tracking scripts based on user consent stored in a cookie.
    683  */
    684 function metrion_enqueue_destination_scripts() {
    685     // Consent mode logic
    686     $consent_scripts = [];
    687 
    688     // Consent mode should load at all times
    689     // Google Ads consent mode
    690     if (get_option('metrion_enforce_google_consent_mode', false) == "1") {
    691         $google_ads_consent_mode_script_path = plugin_dir_path(__DIR__) . 'js/google_ads/consent_mode.js';
    692         $google_ads_consent_mode_script_version = file_exists($google_ads_consent_mode_script_path) ? filemtime($google_ads_consent_mode_script_path) : '1.0.0';
    693 
    694         if (file_exists($google_ads_consent_mode_script_path)) {
    695             $consent_scripts['metrion-google-consent-mode'] = plugins_url('js/google_ads/consent_mode.js', __DIR__) . '?ver=' . $google_ads_consent_mode_script_version;
    696         }
    697     }
    698     // Microsoft Ads consent mode
    699     if (get_option('metrion_microsoft_ads_enforce_consent_mode', false)) {
    700         $microsoft_ads_consent_mode_script_path = plugin_dir_path(__DIR__) . 'js/microsoft_ads/consent_mode.js';
    701         $microsoft_ads_consent_mode_script_version = file_exists($microsoft_ads_consent_mode_script_path) ? filemtime($microsoft_ads_consent_mode_script_path) : '1.0.0';
    702 
    703         if (file_exists($microsoft_ads_consent_mode_script_path)) {
    704             $consent_scripts['metrion-microsoft-consent-mode'] = plugins_url('js/microsoft_ads/consent_mode.js', __DIR__) . '?ver=' . $microsoft_ads_consent_mode_script_version;
    705         }
    706     }     
    707 
    708     // If no scripts need to be loaded, exit early
    709     if (!empty($consent_scripts)) {
    710         // Enqueue each script properly
    711         foreach ($consent_scripts as $handle => $src) {
    712             wp_enqueue_script($handle, esc_url($src), [], null, true);
    713         }
    714     }
    715 
    716     // Destination Logic
    717     $consent_cookie_name = sanitize_key(get_option('metrion_consent_cookie_name', 'mtrn_consent'));
    718 
    719     // Check if the consent cookie exists and is not empty
    720     if (empty($_COOKIE[$consent_cookie_name])) {
    721         return;
    722     }
    723 
    724     // Decode the cookie value safely
    725     $cookie_raw = isset($_COOKIE[$consent_cookie_name]) ? sanitize_text_field(wp_unslash($_COOKIE[$consent_cookie_name])) : '';
    726     $cookie_json = urldecode($cookie_raw); // Decode URL encoding
    727     $cookie_json = stripslashes($cookie_json); // Handle double-encoding
    728 
    729     // Decode JSON
    730     $cookie_data = json_decode($cookie_json, true);
    731 
    732     // Validate JSON structure
    733     if (json_last_error() !== JSON_ERROR_NONE || !is_array($cookie_data)) {
    734         return;
    735     }
    736 
    737     // Check if marketing tracking is allowed
    738     $allow_marketing = isset($cookie_data['allow_marketing'])
    739         ? filter_var($cookie_data['allow_marketing'], FILTER_VALIDATE_BOOLEAN)
    740         : false;
    741 
    742     if (!$allow_marketing) {
    743         return;
    744     }
    745 
    746     // Check if Metrion block detection is enabled
    747     if(get_option('metrion_enable_block_detection', false)){
    748         // Check if Metrion detect script is not already fired before in user/session history
    749         $detection_finished = isset($cookie_data['b'])
    750         ? filter_var($cookie_data['b'], FILTER_VALIDATE_BOOLEAN)
    751         : false;
    752        
    753         // Only if detection is not finished, load the detection JS
    754         if(!$detection_finished){
    755             $detect_scripts = [];
    756             $detect_script_path = plugin_dir_path(__DIR__) . 'js/detect.js';
    757             $detect_script_version = file_exists($detect_script_path) ? filemtime($detect_script_path) : '1.0.0';
    758             if (file_exists($detect_script_path)) {
    759                 $detect_scripts['metrion-detect'] = plugins_url('js/detect.js', __DIR__) . '?ver=' . $detect_script_version;
    760             }
    761             // Enqueue detect script properly
    762             foreach ($detect_scripts as $handle => $src) {
    763                 wp_enqueue_script($handle, esc_url($src), [], null, true);
    764             }
    765         }
    766     }
    767 
    768     // List of scripts to enqueue
    769     $scripts = [];
    770 
    771     // Google Ads
    772     if (get_option('metrion_google_enable_tracking', false)) {
    773         $google_script_path = plugin_dir_path(__DIR__) . 'js/google_ads/events.js';
    774         $google_script_version = file_exists($google_script_path) ? filemtime($google_script_path) : '1.0.0';
    775 
    776         if (file_exists($google_script_path)) {
    777             $scripts['metrion-google-ads'] = plugins_url('js/google_ads/events.js', __DIR__) . '?ver=' . $google_script_version;
    778         }
    779     }
    780 
    781     // Meta Ads
    782     if (get_option('metrion_meta_enable_tracking', false) && get_option('metrion_meta_allow_front_end_pixel_tracking', false)) {
    783         $meta_script_path = plugin_dir_path(__DIR__) . 'js/meta/events.js';
    784         $meta_script_version = file_exists($meta_script_path) ? filemtime($meta_script_path) : '1.0.0';
    785 
    786         if (file_exists($meta_script_path)) {
    787             $scripts['metrion-meta-ads'] = plugins_url('js/meta/events.js', __DIR__) . '?ver=' . $meta_script_version;
    788         }
    789     }
    790 
    791     // Microsoft  Ads
    792     if (get_option('metrion_microsoft_ads_enable_tracking', false)) {
    793         $microsoft_ads_script_path = plugin_dir_path(__DIR__) . 'js/microsoft_ads/events.js';
    794         $microsoft_ads_script_version = file_exists($microsoft_ads_script_path) ? filemtime($microsoft_ads_script_path) : '1.0.0';
    795 
    796         if (file_exists($microsoft_ads_script_path)) {
    797             $scripts['metrion-microsoft-ads'] = plugins_url('js/microsoft_ads/events.js', __DIR__) . '?ver=' . $microsoft_ads_script_version;
    798         }
    799     }
    800 
    801 
    802     // If no scripts need to be loaded, exit early
    803     if (empty($scripts)) {
    804         return;
    805     }
    806 
    807     // Enqueue each script properly
    808     foreach ($scripts as $handle => $src) {
    809         wp_enqueue_script($handle, esc_url($src), [], null, true);
    810     }
    811 }
    812 
    813 
    814 // Register the Metrion Ping API route
    815 add_action('rest_api_init', function () {
    816     $namespace = get_option('metrion_api_path_part_1', 'metrion');
    817     $route     = get_option('metrion_api_path_part_2', 'event') . '/ping';
    818 
    819     register_rest_route($namespace, '/' . $route, [
    820         'methods'             => 'POST',
    821         'callback'            => 'handle_metrion_ping',
    822         'permission_callback' => 'metrion_check_request_origin',
    823     ]);
    824 });
    825 
    826 /**
    827  * Handles the Metrion Ping API request.
    828  *
    829  * @param WP_REST_Request $request
    830  * @return array|WP_Error
    831  */
    832 function handle_metrion_ping(WP_REST_Request $request) {
    833     $body = json_decode($request->get_body(), true);
    834 
    835     if (!is_array($body)) {
    836         return new WP_Error('invalid_json', 'Invalid JSON body', ['status' => 400]);
    837     }
    838 
    839     $data = [
    840         'event'    => isset($body['event']) ? sanitize_text_field($body['event']) : '',
    841         'id'       => isset($body['id']) ? sanitize_text_field($body['id']) : '',
    842         'origin'   => isset($body['origin']) ? esc_url_raw($body['origin']) : '',
    843         'referrer' => isset($body['referrer']) ? esc_url_raw($body['referrer']) : '',
    844     ];
    845 
    846     $ping_endpoint = 'https://stream.getmetrion.com/wp/ping/';
    847 
    848     $response = wp_remote_post($ping_endpoint, [
    849         'body'    => wp_json_encode($data),
    850         'headers' => ['Content-Type' => 'application/json'],
    851         'timeout' => 20,
    852     ]);
    853 
    854     return is_wp_error($response)
    855         ? new WP_Error('ping_failed', 'Failed to forward ping', ['status' => 500])
    856         : ['success' => true];
    857 }
  • metrion/trunk/js/settings/settings.js

    r3290502 r3300778  
    11document.addEventListener('DOMContentLoaded', function() {
    2     var toggleButton = document.getElementById('optional-settings-toggle');
    3     var optionalSettings = document.getElementById('optional-settings');
     2    // Advanced options
     3    var advanced_button = document.getElementById('optional-settings-toggle');
     4    var advanced_settings = document.getElementById('optional-settings');
    45
    5     toggleButton.addEventListener('click', function() {
    6         const isHidden = optionalSettings.style.display === 'none';
    7         optionalSettings.style.display = isHidden ? 'block' : 'none';
    8         toggleButton.textContent = `Optional Advanced Configuration Settings ${isHidden ? '▲' : '▼'}`;
     6    advanced_button.addEventListener('click', function() {
     7        const isHidden = advanced_settings.style.display === 'none';
     8        advanced_settings.style.display = isHidden ? 'block' : 'none';
     9        advanced_button.textContent = `Optional Advanced Configuration Settings ${isHidden ? '▲' : '▼'}`;
     10    });
     11
     12    // Consent options
     13    var consent_button = document.getElementById('consent-settings-toggle');
     14    var consent_settings = document.getElementById('consent-settings');
     15
     16    consent_button.addEventListener('click', function() {
     17        const isHiddenconsent = consent_settings.style.display === 'none';
     18        consent_settings.style.display = isHiddenconsent ? 'block' : 'none';
     19        consent_button.textContent = `Consent Configuration Settings ${isHiddenconsent ? '▲' : '▼'}`;
     20    });
     21
     22    // Google Ads options
     23    var google_ads_button = document.getElementById('google-ads-settings-toggle');
     24    var google_ads_settings = document.getElementById('google-ads-settings');
     25
     26    google_ads_button.addEventListener('click', function() {
     27        const isHiddenGoogleAds = google_ads_settings.style.display === 'none';
     28        google_ads_settings.style.display = isHiddenGoogleAds ? 'block' : 'none';
     29        google_ads_button.textContent = `Google Ads Configuration Settings ${isHiddenGoogleAds ? '▲' : '▼'}`;
     30    });
     31
     32    // Microsoft Ads options
     33    var microsoft_ads_button = document.getElementById('microsoft-ads-settings-toggle');
     34    var microsoft_ads_settings = document.getElementById('microsoft-ads-settings');
     35
     36    microsoft_ads_button.addEventListener('click', function() {
     37        const isHiddenMicrosoftAds = microsoft_ads_settings.style.display === 'none';
     38        microsoft_ads_settings.style.display = isHiddenMicrosoftAds ? 'block' : 'none';
     39        microsoft_ads_button.textContent = `Microsoft Ads Configuration Settings ${isHiddenMicrosoftAds ? '▲' : '▼'}`;
     40    });
     41
     42    // Meta Ads options
     43    var meta_ads_button = document.getElementById('meta-ads-settings-toggle');
     44    var meta_ads_settings = document.getElementById('meta-ads-settings');
     45
     46    meta_ads_button.addEventListener('click', function() {
     47        const isHiddenMetaAds = meta_ads_settings.style.display === 'none';
     48        meta_ads_settings.style.display = isHiddenMetaAds ? 'block' : 'none';
     49        meta_ads_button.textContent = `Meta Ads Configuration Settings ${isHiddenMetaAds ? '▲' : '▼'}`;
    950    });
    1051});
  • metrion/trunk/readme.txt

    r3296257 r3300778  
    44Requires at least: 3.8
    55Tested up to: 6.8
    6 Stable tag: 1.3.0
     6Stable tag: 1.4.0
    77Requires PHP: 7.1
    88License: GPLv3 or later
     
    3838* Microsoft Ads consent mode
    3939* Microsoft Ads dynamic remarketing list functionality
    40 * Pre-consent event counting
    4140
    4241== Installation ==
     
    6160- Checkout start
    6261- Purchase
     62- Form submits
    6363When a user is identified with data like an email address, phone number, this data can be included with each interaction based on the consent configurations within Metrion.
    6464This data is temporarily stored for conversion attribution purposes. Read more about our [terms of use](https://getmetrion.com/en/terms-and-conditions) and [privacy policy](https://getmetrion.com/en/privacy-policy).
     
    8686
    8787== Changelog ==
     88
     89= 1.4.0 =
     90- Added support for dynamic JS bundles which improves Metrion performance
     91- Added support for loading dynamic JS bundles over API endpoints instead of the plugin directory paths
     92- Updated Complianz CMP logic
    8893
    8994= 1.3.0 =
  • metrion/trunk/uninstall.php

    r3296257 r3300778  
    1919    'metrion_user_cookie_lifetime',
    2020    'metrion_session_cookie_lifetime',
    21     'metrion_excluded_forms',
    22     'metrion_form_tracking',
     21    'metrion_elementor_excluded_forms',
     22    'metrion_enable_elementor_form_tracking',
    2323    'metrion_event_id_name',
    2424    'metrion_google_ads_account_id',
     
    5050    'metrion_microsoft_ads_tag_id',
    5151    'metrion_allow_pre_consent_pings',
    52     'metrion_enable_block_detection'   
     52    'metrion_enable_block_detection',
     53    'metrion_use_api_endpoints_to_load_js',
     54    'metrion_enable_woocommerce_tracking'   
    5355];
    5456
  • metrion/trunk/views/settings.php

    r3296257 r3300778  
    11<?php
    2 
    32
    43?>
     
    2524                <th scope="row"><label for="metrion_api_key">Metrion Key</label></th>
    2625                <td>
    27                     <input type="text" id="metrion_api_key" name="metrion_api_key" placeholder="xxx" value="<?php echo esc_attr(get_option('metrion_api_key', 'xxx')); ?>"/></td>
    28 
     26                    <input type="text" id="metrion_api_key" name="metrion_api_key" placeholder="xxx" value="<?php echo esc_attr(get_option('metrion_api_key', 'xxx')); ?>"/>
     27                </td>
    2928            </tr>
    3029            <tr valign="top">
     
    5251        <p>
    5352            <a href="javascript:void(0);" id="optional-settings-toggle">
    54                 Optional Advanced Configuration Settings ▼
     53                Advanced Configuration Settings ▼
    5554            </a>
    5655        </p>
    5756
    5857        <div id="optional-settings" style="display: none;">
    59             <h2>Optional settings</h2>
    60             <p>Updating the settings in this section will impact your identification and ability to stitch data before and after changes. Please use with caution.<p>
     58            <h2>Advanced settings</h2>
     59            <p>Updating the settings in this section will impact your identification and ability to data-collection stream data. Data before and after changes cannot be stitched back together!</p>
    6160            <table class="metrion-settings-advanced">
    6261                <!-- Webhook Destination URL (readonly) -->
     
    6766                               placeholder="https://hub.getmetrion.com/stream/wordpress"
    6867                               value="<?php echo esc_attr(get_option('metrion_webhook_destination', 'https://hub.getmetrion.com/stream/wordpress')); ?>"
    69                                readonly />
     68                                />
    7069                    </td>
    7170                </tr>
     
    125124                    </td>
    126125                </tr>
     126
     127                <!-- JS bundling to hide plugin directory in front-end (Checkbox) -->
     128                <tr valign="top">
     129                    <th scope="row"><label for="metrion_use_api_endpoints_to_load_js">Use custom API endpoints to load JS</label></th>
     130                    <td>
     131                        <label class="switch">
     132                            <input type="checkbox" id="metrion_use_api_endpoints_to_load_js" name="metrion_use_api_endpoints_to_load_js" value="1" <?php checked(1, get_option('metrion_use_api_endpoints_to_load_js', 0)); ?>>
     133                            <span class="slider round"></span>
     134                        </label>
     135                    </td>
     136                </tr>
     137
     138                <!-- Tracking modules checkboxes -->
     139                <tr valign="top">
     140                    <th scope="row"><label for="metrion_enable_woocommerce_tracking">Woocommerce tracking</label></th>
     141                    <td>
     142                        <!-- Keep the checkbox, just hide it with CSS and use the switch class for the toggle effect -->
     143                        <label class="switch">
     144                            <input type="checkbox" id="metrion_enable_woocommerce_tracking"
     145                                name="metrion_enable_woocommerce_tracking" value="1" <?php checked(1, get_option('metrion_enable_woocommerce_tracking', 1)); ?>>
     146                            <span class="slider round"></span>
     147                        </label>
     148                    </td>
     149                </tr>
     150
     151
    127152                <tr valign="top">
    128153                    <th scope="row"><label for="metrion_purchase_only_tracking">Purchase-only tracking</label></th>
     
    136161                    </td>
    137162                </tr>
    138                 <tr valign="top">
    139                     <th scope="row"><label for="metrion_form_tracking">Elementor Form Submits tracking</label></th>
    140                     <td>
    141                         <label class="switch">
    142                             <input type="checkbox" id="metrion_form_tracking" name="metrion_form_tracking" value="1"
    143                                 <?php checked(1, get_option('metrion_form_tracking', 0)); ?>>
     163                <!-- Elementor checkboxes -->
     164                <tr valign="top">
     165                    <th scope="row"><label for="metrion_enable_elementor_form_tracking">Elementor Form Submits tracking</label></th>
     166                    <td>
     167                        <label class="switch">
     168                            <input type="checkbox" id="metrion_enable_elementor_form_tracking" name="metrion_enable_elementor_form_tracking" value="1"
     169                                <?php checked(1, get_option('metrion_enable_elementor_form_tracking', 0)); ?>>
    144170                            <span class="slider round"></span>
    145171                        </label>
     
    148174                <tr valign="top">
    149175                    <th scope="row">
    150                         <label for="metrion_excluded_forms">Excluded Elementor Form IDs</label>
     176                        <label for="metrion_elementor_excluded_forms">Excluded Elementor Form IDs</label>
    151177                    </th>
    152178                    <td>
    153                         <textarea id="metrion_excluded_forms" name="metrion_excluded_forms" rows="5"
    154                             cols="50"><?php echo esc_textarea(get_option('metrion_excluded_forms', '')); ?></textarea>
     179                        <textarea id="metrion_elementor_excluded_forms" name="metrion_elementor_excluded_forms" rows="5"
     180                            cols="50"><?php echo esc_textarea(get_option('metrion_elementor_excluded_forms', '')); ?></textarea>
    155181                        <p class="description">Enter one form ID per line to exclude from tracking (e.g.,
    156182                            adminbarsearch).</p>
     
    158184                </tr>
    159185            </table>
     186        </div>
     187        <p>
     188            <a href="javascript:void(0);" id="consent-settings-toggle">
     189                Consent Configuration Settings ▼
     190            </a>
     191        </p>
     192        <div id="consent-settings" style="display: none;">
    160193            <!-- Consent management settings -->
    161194            <h2>Consent settings</h2>
    162195            <p>
    163                 If no overwriting consent enforcements are in place, the settings below are fallback used by Metrion.
     196                Configure the consent settings below to match your setup.
    164197                If a CMP is selected, the settings of the CMP will overwrite the settings below
    165198            </p>
    166             <table class="metrion-settings-advanced">
     199            <table class="metrion-settings-consent metrion-settings-advanced">
    167200                <tr valign="top">
    168201                    <th scope="row"><label for="metrion_cmp_selection">I have a consent management platform</label></th>
     
    210243                    <td>
    211244                        <label class="switch">
    212                             <input type="checkbox" id="metrion_allow_pii" name="metrion_allow_pii" value="1" <?php checked(1, get_option('metrion_allow_pii', 1)); ?>>
     245                            <input type="checkbox" id="metrion_allow_pii" name="metrion_allow_pii" value="1" <?php checked(1, get_option('metrion_allow_pii', 0)); ?>>
    213246                            <span class="slider round"></span>
    214247                        </label>
     
    219252                    <td>
    220253                        <label class="switch">
    221                             <input type="checkbox" id="metrion_allow_marketing" name="metrion_allow_marketing" value="1" <?php checked(1, get_option('metrion_allow_marketing', 1)); ?>>
     254                            <input type="checkbox" id="metrion_allow_marketing" name="metrion_allow_marketing" value="1" <?php checked(1, get_option('metrion_allow_marketing', 0)); ?>>
    222255                            <span class="slider round"></span>
    223256                        </label>
     
    227260                    <th scope="row">
    228261                        <label for="metrion_allow_cookie_placement_before_explicit_consent">
    229                             Allow placement of cookies and data in browser storage before explicit consent. Note! This does not mean tracking data is send before explicit consent! No data wil be send before explicit consent.
     262                            Allow placement of cookies and data in browser storage before explicit consent. Note! This does not mean tracking data is sent before explicit consent! No data will be send before explicit consent.
    230263                        </label></th>
    231264                    <td>
    232265                        <label class="switch">
    233                             <input type="checkbox" id="metrion_allow_cookie_placement_before_explicit_consent" name="metrion_allow_cookie_placement_before_explicit_consent" value="1" <?php checked(1, get_option('metrion_allow_cookie_placement_before_explicit_consent', 1)); ?>>
     266                            <input type="checkbox" id="metrion_allow_cookie_placement_before_explicit_consent" name="metrion_allow_cookie_placement_before_explicit_consent" value="1" <?php checked(1, get_option('metrion_allow_cookie_placement_before_explicit_consent', 0)); ?>>
    234267                            <span class="slider round"></span>
    235268                        </label>
     
    256289                    <td>
    257290                        <label class="switch">
    258                             <input type="checkbox" id="metrion_enable_block_detection" name="metrion_enable_block_detection" value="1" <?php checked(1, get_option('metrion_enable_block_detection', 0)); ?>>
     291                            <input type="checkbox" id="metrion_enable_block_detection" name="metrion_enable_block_detection" value="1" <?php checked(1, get_option('metrion_enable_block_detection', 1)); ?>>
    259292                            <span class="slider round"></span>
    260293                        </label>
     
    262295                </tr>
    263296            </table>
     297        </div>
     298        <p>
     299            <a href="javascript:void(0);" id="google-ads-settings-toggle">
     300                Google Ads Configuration Settings ▼
     301            </a>
     302        </p>
     303        <div id="google-ads-settings" style="display: none;">
    264304            <!-- Google Ads settings -->
    265             <h2>Google specific settings</h2>
    266             <p>The settings below are optional and Google specific.</p>
    267             <table class="metrion-settings-advanced">
     305            <h2>Google Ads settings</h2>
     306            <p>Configure the fields below to match your Google Ads tracking needs.</p>
     307            <table class="metrion-settings-google-ads metrion-settings-advanced">
    268308                <tr valign="top">
    269309                    <th scope="row"><label for="metrion_google_enable_tracking">Enable Google tracking</label></th>
     
    322362                </tr>
    323363            </table>
    324             <h2>Meta specific settings</h2>
    325             <p>The settings below are optional and Meta specific.</p>
    326             <table class="metrion-settings-advanced">
     364        </div>
     365        <p>
     366            <a href="javascript:void(0);" id="meta-ads-settings-toggle">
     367                Meta Ads Configuration Settings ▼
     368            </a>
     369        </p>
     370        <div id="meta-ads-settings" style="display: none;">
     371            <h2>Meta Ads settings</h2>
     372            <p>Configure the fields below to match your Meta Ads tracking needs.</p>
     373            <table class="metrion-settings-meta-ads metrion-settings-advanced">
    327374                <tr valign="top">
    328375                    <th scope="row"><label for="metrion_meta_enable_tracking">Enable Meta tracking</label></th>
     
    365412                </tr>
    366413            </table>
    367             <h2>Microsoft Ads specific settings</h2>
    368             <p>The settings below are optional and Microsoft Ads specific.</p>
    369             <table class="metrion-settings-advanced">
     414        </div>
     415        <p>
     416            <a href="javascript:void(0);" id="microsoft-ads-settings-toggle">
     417                Microsoft Ads Configuration Settings ▼
     418            </a>
     419        </p>
     420        <div id="microsoft-ads-settings" style="display: none;">
     421            <h2>Microsoft Ads settings</h2>
     422            <p>Configure the fields below to match your Microsoft Ads tracking needs.</p>
     423            <table class="metrion-settings-microsoft-ads metrion-settings-advanced">
    370424                <tr valign="top">
    371425                    <th scope="row"><label for="metrion_microsoft_ads_enable_tracking">Enable Microsoft Ads tracking</label></th>
Note: See TracChangeset for help on using the changeset viewer.