| Form Tracking | ✓ Active - pushes form_submit events with user_email and user_phone |
| Link Tracking | ✓ Active - tracks phone_click on tel: links |
| GTM Integration | is_valid_gtm_id( $gtm_id ) ): ?> ✓ Active - Container ✗ Disabled - No valid container ID configured |
| Server-Side GTM | ✓ Active - ⚠ Configured but not validated - ✗ Enabled but no host configured - Disabled (using standard GTM) |
✓ All form submissions will include this campaign data for attribution tracking.
When users visit your site with UTM parameters (e.g., ?utm_campaign=summer_sale),
the campaign data will be stored and included with all form submissions for proper attribution.
No debug logs yet. Logs will appear here when forms are submitted.
| Time | Level | Message | Context |
|---|---|---|---|
| ' . esc_html( wp_json_encode( $log['context'], JSON_PRETTY_PRINT ) ) . '' : '-'; ?> |
' . esc_html__( 'Debug logs cleared.', 'choice-universal-form-tracker' ) . '
'; } } /** * Get color for log level */ private function get_log_level_color( $level ) { switch ( $level ) { case 'error': return '#dc3545'; case 'warning': return '#ffc107'; case 'info': return '#17a2b8'; case 'debug': return '#6c757d'; default: return '#6c757d'; } } /** * Enqueue admin scripts */ public function enqueue_admin_scripts( $hook ) { $settings_hook = 'settings_page_choice-universal-form-tracker'; $click_hook = 'toplevel_page_cuft-click-tracking'; // The admin stylesheet loads on the plugin's own screens only. if ( in_array( $hook, array( $settings_hook, $click_hook ), true ) ) { wp_enqueue_style( 'cuft-admin', CUFT_URL . '/assets/cuft-admin.css', array(), CUFT_VERSION ); } // The admin script serves the settings page and the Click Tracking page. if ( $hook !== $settings_hook && $hook !== $click_hook ) { return; } wp_enqueue_script( 'cuft-admin', CUFT_URL . '/assets/cuft-admin.js', array( 'jquery' ), CUFT_VERSION, true ); CUFT_Utils::localize( 'cuft-admin', 'cuftAdmin', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'cuft_admin' ), 'register_nonce' => wp_create_nonce( 'cuft_token_register' ), 'current_version' => CUFT_VERSION, 'plugin_url' => CUFT_URL, 'admin_url' => admin_url( 'options-general.php?page=choice-universal-form-tracker' ), ) ); if ( $hook !== $settings_hook ) { return; } // Enqueue Force Update assets (Feature 009 - v3.19.0). Skipped in the // WordPress.org build, which ships neither the tab nor its handlers. if ( CUFT_Plugin::has_updater() ) { wp_enqueue_script( 'cuft-force-update', CUFT_URL . '/assets/admin/cuft-force-update.js', array( 'jquery' ), CUFT_VERSION, true ); CUFT_Utils::localize( 'cuft-force-update', 'cuftForceUpdate', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'cuft_force_update' ), ) ); wp_enqueue_style( 'cuft-force-update', CUFT_URL . '/assets/admin/cuft-force-update.css', array(), CUFT_VERSION ); } } /** * AJAX handler for testing sGTM endpoint */ public function ajax_test_sgtm() { // Verify nonce and permissions if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cuft_admin' ) || ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Security check failed' ), 403 ); } $raw_host = isset( $_POST['sgtm_host'] ) ? sanitize_text_field( wp_unslash( $_POST['sgtm_host'] ) ) : ''; $gtm_id = CUFT_SGTM::sanitize_gtm_id( (string) get_option( 'cuft_gtm_id', '' ) ); if ( '' === trim( $raw_host ) ) { wp_send_json_error( array( 'message' => 'Please enter a Server GTM host' ) ); } if ( ! $gtm_id ) { wp_send_json_error( array( 'message' => 'Please configure a GTM ID first' ) ); } $sgtm_host = CUFT_SGTM::sanitize_host( $raw_host ); if ( ! $sgtm_host ) { wp_send_json_error( array( 'message' => 'Invalid host. Enter a hostname only, for example gtm.example.com.' ) ); } // Only the saved host may change stored state; testing a host that // has not been saved yet reports the result without recording it. $is_saved_host = ( CUFT_SGTM::get_host() === $sgtm_host ); $test_results = $this->test_sgtm_endpoints( $sgtm_host, $gtm_id, $is_saved_host ); if ( $is_saved_host ) { update_option( 'cuft_sgtm_validated', (bool) $test_results['success'] ); } wp_send_json_success( array( 'valid' => (bool) $test_results['success'], 'message' => $test_results['success'] ? 'Custom server validated successfully' : $test_results['message'], 'details' => $test_results['details'], 'endpoints_tested' => array( 'gtm_js' => isset( $test_results['details']['gtm_js'] ) && 0 === strpos( $test_results['details']['gtm_js'], 'OK' ), 'ns_html' => isset( $test_results['details']['ns_html'] ) && 0 === strpos( $test_results['details']['ns_html'], 'OK' ), ), ) ); } /** * AJAX handler for manual health check */ public function ajax_manual_health_check() { // Verify nonce and permissions if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cuft_admin' ) || ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Security check failed' ) ); } // Check if custom server is enabled $enabled = get_option( 'cuft_sgtm_enabled', false ); $sgtm_host = CUFT_SGTM::get_host(); $gtm_id = CUFT_SGTM::sanitize_gtm_id( (string) get_option( 'cuft_gtm_id', '' ) ); if ( ! $enabled || '' === $sgtm_host || ! $gtm_id ) { wp_send_json_error( array( 'message' => 'Custom server not configured' ) ); } // Perform health check $test_results = $this->test_sgtm_endpoints( $sgtm_host, $gtm_id ); $health_check_passed = $test_results['success']; $timestamp = time(); $response_time = $test_results['success'] ? 200.0 : 5000.0; // Mock values // Get current counters $consecutive_success = get_option( 'cuft_sgtm_health_consecutive_success', 0 ); $consecutive_failure = get_option( 'cuft_sgtm_health_consecutive_failure', 0 ); $active_server = get_option( 'cuft_sgtm_active_server', 'fallback' ); $response_data = array( 'health_check_passed' => $health_check_passed, 'message' => $health_check_passed ? 'Health check successful' : 'Health check failed: ' . $test_results['message'], 'timestamp' => $timestamp, 'response_time' => $response_time, 'consecutive_success' => $consecutive_success, 'consecutive_failure' => $consecutive_failure, 'active_server' => $active_server ); wp_send_json_success( $response_data ); } /** * AJAX handler for getting custom server status */ public function ajax_get_sgtm_status() { // Verify nonce and permissions if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cuft_admin' ) || ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Security check failed' ) ); } // Get configuration $enabled = get_option( 'cuft_sgtm_enabled', false ); $url = CUFT_SGTM::get_host(); $validated = get_option( 'cuft_sgtm_validated', false ); // Get status $active_server = get_option( 'cuft_sgtm_active_server', 'fallback' ); $last_check_time = get_option( 'cuft_sgtm_health_last_check', 0 ); $last_check_result = get_option( 'cuft_sgtm_health_last_result', false ); $last_check_message = get_option( 'cuft_sgtm_health_last_message', '' ); $consecutive_success = get_option( 'cuft_sgtm_health_consecutive_success', 0 ); $consecutive_failure = get_option( 'cuft_sgtm_health_consecutive_failure', 0 ); $response_time = get_option( 'cuft_sgtm_health_response_time', 0.0 ); // Calculate next check time (6 hours from last check) $next_check = $last_check_time + (6 * 60 * 60); // Human readable timestamps $human_readable = array( 'last_check' => $last_check_time > 0 ? human_time_diff( $last_check_time ) . ' ago' : 'Never', 'next_check' => $next_check > time() ? human_time_diff( $next_check ) . ' from now' : 'Overdue', 'status' => $active_server === 'custom' ? 'Healthy' : 'Using Fallback' ); $response_data = array( 'configuration' => array( 'enabled' => $enabled, 'host' => $url, 'validated' => $validated ), 'status' => array( 'active_server' => $active_server, 'last_check_time' => $last_check_time, 'last_check_result' => $last_check_result, 'last_check_message' => $last_check_message, 'consecutive_success' => $consecutive_success, 'consecutive_failure' => $consecutive_failure, 'response_time' => $response_time ), 'next_check' => $next_check, 'human_readable' => $human_readable ); wp_send_json_success( $response_data ); } /** * AJAX handler for downloading GTM templates */ public function ajax_download_gtm_template() { // Verify nonce and permissions if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'cuft_admin' ) || ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => 'Security check failed' ) ); } // Get template type $template_type = isset( $_POST['template'] ) ? sanitize_text_field( wp_unslash( $_POST['template'] ) ) : ''; if ( empty( $template_type ) || ! in_array( $template_type, array( 'web', 'server' ), true ) ) { wp_send_json_error( array( 'message' => 'Invalid template type' ) ); } // Determine file path based on template type $plugin_dir = plugin_dir_path( dirname( __FILE__ ) ); if ( $template_type === 'web' ) { $file_path = $plugin_dir . 'gtm-web-client/CUFT - Web Defaults.json'; $filename = 'CUFT-Web-Defaults.json'; } else { $file_path = $plugin_dir . 'gtm-server/CUFT - Server Defaults.json'; $filename = 'CUFT-Server-Defaults.json'; } // Check if file exists if ( ! file_exists( $file_path ) ) { wp_send_json_error( array( 'message' => 'Template file not found' ) ); } // Read file content $file_content = file_get_contents( $file_path ); if ( $file_content === false ) { wp_send_json_error( array( 'message' => 'Failed to read template file' ) ); } // Return file content as base64 to avoid JSON parsing issues wp_send_json_success( array( 'content' => base64_encode( $file_content ), 'filename' => $filename ) ); } /** * Add custom cron intervals */ public static function add_cron_intervals( $schedules ) { $schedules['six_hours'] = array( 'interval' => 6 * 60 * 60, // 6 hours in seconds 'display' => 'Every 6 hours' ); return $schedules; } /** * Register the six-hour health check on every request. * * WP-Cron requests are not admin requests, so the callback and its * six_hours schedule cannot live in the constructor. The event is kept * scheduled here rather than on activation, because sites updated by * copying files or through an updater never reactivate, and on activation * the six_hours schedule did not exist yet. */ public static function register_health_check() { add_filter( 'cron_schedules', array( __CLASS__, 'add_cron_intervals' ) ); add_action( 'cuft_scheduled_health_check', array( __CLASS__, 'run_scheduled_health_check' ) ); if ( get_option( 'cuft_sgtm_enabled', false ) ) { if ( ! wp_next_scheduled( 'cuft_scheduled_health_check' ) ) { wp_schedule_event( time(), 'six_hours', 'cuft_scheduled_health_check' ); } } elseif ( wp_next_scheduled( 'cuft_scheduled_health_check' ) ) { wp_clear_scheduled_hook( 'cuft_scheduled_health_check' ); } } /** * WP-Cron entry point for the scheduled health check. */ public static function run_scheduled_health_check() { ( new self() )->scheduled_health_check(); } /** * Scheduled health check callback */ public function scheduled_health_check() { // Check if custom server is enabled $enabled = get_option( 'cuft_sgtm_enabled', false ); $sgtm_host = CUFT_SGTM::get_host(); $gtm_id = CUFT_SGTM::sanitize_gtm_id( (string) get_option( 'cuft_gtm_id', '' ) ); if ( ! $enabled || '' === $sgtm_host || ! $gtm_id ) { return; // Skip health check if not configured } // Perform health check $this->test_sgtm_endpoints( $sgtm_host, $gtm_id ); // The store_health_check_result method will handle: // - Storing health check results // - Updating consecutive counters // - Switching between custom/fallback servers // - Creating admin notice triggers } /** * Test the tagging server's gtm.js and ns.html endpoints. * * Both URLs are built by CUFT_SGTM from the validated host and container * ID; TLS certificates are always verified. * * @param string $sgtm_host Validated bare host. * @param string $gtm_id Validated container ID. * @param bool $record Store the result and update the server switch. * @return array */ private function test_sgtm_endpoints( $sgtm_host, $gtm_id, $record = true ) { $results = array( 'success' => false, 'message' => '', 'details' => array(), ); $endpoints = array( 'gtm_js' => CUFT_SGTM::script_url( $sgtm_host, $gtm_id ), 'ns_html' => CUFT_SGTM::noscript_url( $sgtm_host, $gtm_id ), ); foreach ( $endpoints as $key => $url ) { $label = 'gtm_js' === $key ? 'gtm.js' : 'ns.html'; if ( '' === $url ) { $results['message'] = 'Invalid host or GTM container ID'; break; } $response = wp_remote_get( $url, array( 'timeout' => 5 ) ); if ( is_wp_error( $response ) ) { $results['message'] = 'Failed to connect to ' . $label . ' endpoint: ' . $response->get_error_message(); $results['details'][ $key ] = 'Error: ' . $response->get_error_message(); break; } $code = (int) wp_remote_retrieve_response_code( $response ); $body = (string) wp_remote_retrieve_body( $response ); if ( 200 !== $code ) { $results['message'] = $label . ' endpoint returned status code: ' . $code; $results['details'][ $key ] = 'HTTP ' . $code; break; } $looks_right = 'gtm_js' === $key ? ( false !== strpos( $body, 'google' ) || false !== strpos( $body, 'gtm' ) ) : ( false !== strpos( $body, '<' ) ); if ( ! $looks_right ) { $results['message'] = $label . ' endpoint did not return the expected content'; $results['details'][ $key ] = 'Invalid response content'; break; } $results['details'][ $key ] = 'OK (HTTP 200)'; } if ( '' === $results['message'] ) { $results['success'] = true; $results['message'] = 'Both endpoints validated successfully'; } if ( $record ) { $this->store_health_check_result( $results ); } return $results; } /** * Store health check result in wp_options */ private function store_health_check_result( $results ) { $timestamp = time(); $success = $results['success']; $message = $results['message']; // Calculate response time (simplified - in real implementation, you'd measure actual time) $response_time = $success ? 200.0 : 5000.0; // Mock values for now // Store basic health check data update_option( 'cuft_sgtm_health_last_check', $timestamp ); update_option( 'cuft_sgtm_health_last_result', $success ); update_option( 'cuft_sgtm_health_last_message', $message ); update_option( 'cuft_sgtm_health_response_time', $response_time ); // Handle consecutive counters and server switching if ( $success ) { // Increment consecutive success, reset failure counter $consecutive_success = get_option( 'cuft_sgtm_health_consecutive_success', 0 ) + 1; update_option( 'cuft_sgtm_health_consecutive_success', $consecutive_success ); update_option( 'cuft_sgtm_health_consecutive_failure', 0 ); // Switch to custom server after 3 consecutive successes $current_server = get_option( 'cuft_sgtm_active_server', 'fallback' ); if ( $consecutive_success >= 3 ) { if ( $current_server !== 'custom' ) { update_option( 'cuft_sgtm_active_server', 'custom' ); // Trigger admin notice for server recovery add_option( 'cuft_sgtm_server_recovered', $timestamp ); } } elseif ( $current_server !== 'custom' ) { // Recovering: confirm within the hour, not over 18 hours. self::schedule_health_recheck(); } } else { // Increment consecutive failure, reset success counter $consecutive_failure = get_option( 'cuft_sgtm_health_consecutive_failure', 0 ) + 1; update_option( 'cuft_sgtm_health_consecutive_failure', $consecutive_failure ); update_option( 'cuft_sgtm_health_consecutive_success', 0 ); // Fall back on the second failure in a row. One timeout from a // busy host would otherwise take the site off its tagging server // until three passes six hours apart brought it back; a recheck // 15 minutes later settles it instead. $current_server = get_option( 'cuft_sgtm_active_server', 'fallback' ); if ( $consecutive_failure >= 2 ) { if ( $current_server === 'custom' ) { update_option( 'cuft_sgtm_active_server', 'fallback' ); // Trigger admin notice for server failure add_option( 'cuft_sgtm_server_failed', $timestamp ); } } else { self::schedule_health_recheck(); } } } /** * One-off health check 15 minutes from now, beside the six-hour run. */ private static function schedule_health_recheck() { if ( ! wp_next_scheduled( 'cuft_scheduled_health_check', array( 'recheck' ) ) ) { wp_schedule_single_event( time() + 15 * MINUTE_IN_SECONDS, 'cuft_scheduled_health_check', array( 'recheck' ) ); } } /** * Render setup progress indicator */ private function render_setup_progress() { $gtm_id = get_option( 'cuft_gtm_id', '' ); $frameworks = CUFT_Form_Detector::get_framework_status(); $detected_frameworks = array_filter( $frameworks, function($fw) { return $fw['detected']; } ); // Calculate setup completion $steps = array( 'gtm_setup' => !empty( $gtm_id ), 'framework_detected' => !empty( $detected_frameworks ) ); $completed_steps = array_filter( $steps ); $total_steps = count( $steps ); $completed_count = count( $completed_steps ); $progress_percentage = ( $completed_count / $total_steps ) * 100; // Only show if setup is not complete if ( $completed_count < $total_steps ) { ?>
Click tracking write failed.
Clicks may not be recording.
Last failure: UTC
()
Updates a click's lead status from an email link, a CRM, or any system that can request a URL.
Every request must carry this site's webhook key in the key parameter. Requests without it are rejected with HTTP 403.
Append click_id and the status parameters below. Treat this URL as a secret: it contains the key.
Parameters:
• action = cuft_webhook (required)
• key = this site's webhook key (required)
• click_id = the click ID to update (required; only existing clicks can be updated)
• qualified = 0 or 1 (optional)
• score = 0-10 (optional)
• status = qualify_lead, disqualify_lead, working_lead, close_convert_lead or close_unconvert_lead (optional)
Click any Click ID in the table below to copy it here. You need at least one click tracking record. Create one by clicking a tracked link or submitting a tracked form.
| Click ID | Campaign | Events | Qualified | Score | Date Created | Date Updated | Actions |
|---|---|---|---|---|---|---|---|
| No click tracking data found. Click data will appear here when tracking is active. | |||||||
|
click_id ); ?>
ip_hash ) ): ?>
IP: ip_hash, 0, 12 ) ); ?>… |
campaign ?: '-' ); ?> |
events ) ? CUFT_Click_Tracker::get_events( $click->click_id ) : array();
if ( ! empty( $events ) ) :
// Sort events by timestamp descending (newest first)
usort( $events, function( $a, $b ) {
return strcmp( $b['timestamp'], $a['timestamp'] );
});
// Display first 3 events, collapse rest
$visible_events = array_slice( $events, 0, 3 );
$hidden_events = array_slice( $events, 3 );
foreach ( $visible_events as $event ):
$event_type = $event['event'];
$event_time = gmdate( 'M j, g:i A', strtotime( $event['timestamp'] ) );
// Badge colors by event type
$badge_colors = array(
'phone_click' => '#3b82f6',
'email_click' => '#8b5cf6',
'form_submit' => '#10b981',
'generate_lead' => '#f59e0b',
'qualify_lead' => '#10b981',
'disqualify_lead' => '#ef4444',
'working_lead' => '#f59e0b',
'close_convert_lead' => '#059669',
'close_unconvert_lead' => '#6b7280',
'score_updated' => '#06b6d4'
);
$badge_color = isset( $badge_colors[ $event_type ] ) ? $badge_colors[ $event_type ] : '#6b7280';
?>
+ more No events |
qualified ? 'YES' : 'NO' ); ?> | score; ?>/10 |
date_created ) ) ); ?>
UTC |
date_updated !== $click->date_created ): ?>
date_updated ) ) ); ?>
UTC - |
|
Choice Universal Form Tracker: GTM container ID is missing or invalid. '; echo 'Please configure your GTM ID to enable conversion tracking.
'; echo '✅ Custom GTM server is now active
';
echo 'Your custom server has passed 3 consecutive health checks and is now being used for GTM script loading. ';
echo 'View status
⚠️ Custom GTM server unavailable, using fallback
';
echo 'Your custom server failed a health check and the system has automatically switched to Google\'s default endpoints. ';
echo 'View status