' . esc_html__( 'Settings', 'pixelsync' ) . '' ); return $links; } public function add_sidebar_link() { // Top-level item, but with no fixed position (null): WordPress places it after the core items // instead of competing with them near the top of the menu. add_menu_page( 'PixelSync', 'PixelSync', 'manage_options', 'pxsync-bridge', [ $this, 'render_ui' ], 'dashicons-chart-line', null ); } public function register_pxsync_settings() { register_setting( 'pxsync_bridge_options', 'pxsync_pixel_id', 'sanitize_text_field' ); register_setting( 'pxsync_bridge_options', 'pxsync_enable_am', 'absint' ); register_setting( 'pxsync_bridge_options', 'pxsync_capi_token', 'sanitize_text_field' ); register_setting( 'pxsync_bridge_options', 'pxsync_debug_mode', 'absint' ); register_setting( 'pxsync_bridge_options', 'pxsync_test_event_code', 'sanitize_text_field' ); // Event toggle keys $events = [ 'pxsync_evt_pageview', 'pxsync_evt_viewcontent', 'pxsync_evt_addtocart', 'pxsync_evt_viewcart', 'pxsync_evt_initiatecheckout', 'pxsync_evt_addpayment', 'pxsync_evt_purchase', 'pxsync_evt_search', 'pxsync_evt_registration', ]; foreach ( $events as $event ) { register_setting( 'pxsync_bridge_options', $event, 'absint' ); } } public function enqueue_pxsync_styles( $hook ) { // The page's screen id starts with the parent menu's name, which can differ between sites, // so only the ending (the page slug) is checked. $suffix = '_page_pxsync-bridge'; if ( substr( (string) $hook, -strlen( $suffix ) ) !== $suffix ) return; wp_enqueue_style( 'pxsync-bridge-admin-css', PXSYNC_URL . 'assets/css/admin-style.css', [], defined( 'PXSYNC_VERSION' ) ? PXSYNC_VERSION : '1.0.0' ); wp_enqueue_script( 'pxsync-bridge-admin-js', PXSYNC_URL . 'assets/js/admin-script.js', ['jquery'], defined('PXSYNC_VERSION') ? PXSYNC_VERSION : '1.0.0', true ); // Localize the admin-specific nonce wp_localize_script( 'pxsync-bridge-admin-js', 'pxsync_admin_vars', [ 'nonce' => wp_create_nonce( 'pxsync_admin_action' ), 'ajax_url' => admin_url( 'admin-ajax.php' ) ]); } public function handle_manual_feed_generation() { check_ajax_referer( 'pxsync_admin_action', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_send_json_error( 'Unauthorized' ); if ( class_exists( 'PXSYNC_Feed' ) ) { try { $feed = new PXSYNC_Feed(); if ( ! $feed->generate_xml_file() ) { wp_send_json_error( 'Could not write the feed file. Check that the folder wp-content/uploads is writable by the server.' ); } } catch ( \Throwable $e ) { // Administrators only (checked above): show what went wrong instead of a blank server error. wp_send_json_error( 'Feed error: ' . wp_strip_all_tags( $e->getMessage() ) . ' (' . basename( $e->getFile() ) . ':' . (int) $e->getLine() . ')' ); } wp_send_json_success( [ 'time' => get_option( 'pxsync_feed_last_updated', 'Just now' ) ] ); } wp_send_json_error( 'Feed engine class missing.' ); } public function pxsync_get_live_logs() { check_ajax_referer( 'pxsync_admin_action', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); global $wpdb; $table_name = $wpdb->prefix . 'pxsync_logs'; // Plugin's own log table; %i safely quotes the table name (WordPress 6.2+). // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $logs = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY created_at DESC LIMIT 50', $table_name ) ); if (empty($logs)) { wp_send_json_success('Waiting for events...'); } $html = ''; foreach ($logs as $log) { $name = esc_html($log->event_name); $source = esc_html($log->source); $status = esc_html($log->status); $time = esc_html( wp_date( 'M j, H:i:s', strtotime( $log->created_at ) ) ); $platform = esc_html($log->source_platform ?: 'Direct'); $is_deduplicated = (strpos($source, 'Browser') !== false && strpos($source, 'Server') !== false); // 1. Event ID (eID) $clean_eid = preg_replace('/_err_.*$/', '', $log->event_id); $display_eid = (strlen($clean_eid) > 20) ? substr($clean_eid, 0, 17) . '...' : $clean_eid; $eid_html = 'eID: ' . esc_html($display_eid) . ''; // 2. Extract Device and User ID $details_data = json_decode($log->details, true); if ( ! is_array( $details_data ) ) { $details_data = [ 'info' => $log->details ]; } // Stored data may come from visitors: force plain strings so odd values can never break this screen. $user_id = isset( $details_data['user_id'] ) && is_scalar( $details_data['user_id'] ) ? (string) $details_data['user_id'] : 'Guest'; $device = isset( $details_data['device'] ) && is_scalar( $details_data['device'] ) ? (string) $details_data['device'] : 'Desktop'; $user_html = ($user_id !== 'Guest') ? '#' . esc_html($user_id) . '' : 'Guest'; $device_icon = '💻'; if ( strtolower($device) === 'mobile' ) $device_icon = '📱'; if ( strtolower($device) === 'tablet' ) $device_icon = '📱'; $device_html = '' . $device_icon . ' ' . esc_html($device) . ''; // 3. Metadata (pID & Value) $meta_html = ''; if ( is_array($details_data) ) { $parts = []; if ( ! empty( $details_data['id'] ) && is_scalar( $details_data['id'] ) ) { $pid = (string) $details_data['id']; $short_pid = (strlen($pid) > 12) ? substr($pid, 0, 10) . '..' : $pid; $parts[] = 'ID: '.esc_html($short_pid).''; } if ( ! empty( $details_data['value'] ) && is_scalar( $details_data['value'] ) ) { $currency = isset( $details_data['currency'] ) && is_scalar( $details_data['currency'] ) ? (string) $details_data['currency'] : ''; $parts[] = 'Val: '.esc_html( (string) $details_data['value'] ).' '.esc_html( $currency ).''; } $meta_html = implode(' | ', $parts); } // 4. Deduplication Status if ($is_deduplicated) { $dedup_html = '✔ Deduplicated'; } else { $dedup_html = '⚠ Single Source'; } $status_color = $is_deduplicated ? '#25F4EE' : '#ff9800'; $status_bg = $is_deduplicated ? 'rgba(37, 244, 238, 0.1)' : 'rgba(255, 152, 0, 0.1)'; $p_style = 'color: #888; font-size: 11px;'; if ($platform === 'TikTok') $p_style = 'color: #25F4EE; font-weight: bold;'; if ($platform === 'Facebook') $p_style = 'color: #1877F2; font-weight: bold;'; if ($platform === 'Google Ads') $p_style = 'color: #4285F4; font-weight: bold;'; if ($platform === 'Instagram')$p_style = 'color: #E1306C; font-weight: bold;'; $html .= ''; $html .= '' . $name . ''; $html .= '' . $source . ''; $html .= '' . $platform . ''; $html .= '' . $user_html . ''; $html .= '' . $device_html . ''; $html .= '' . $status . ''; $html .= '' . $time . ''; $html .= ''; $html .= ''; $html .= '' . $eid_html . ''; $html .= '' . $meta_html . ''; $html .= '' . $dedup_html . ''; $html .= ''; $html .= ''; } wp_send_json_success($html); } public function ajax_clear_logs() { wp_create_nonce( 'pxsync_admin_action' ); check_ajax_referer( 'pxsync_admin_action', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange $wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %i', $wpdb->prefix . 'pxsync_logs' ) ); wp_send_json_success(); } public function ajax_toggle_debug() { check_ajax_referer( 'pxsync_admin_action', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) wp_die(); $enabled = isset( $_POST['enabled'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['enabled'] ) ) ? 1 : 0; update_option( 'pxsync_debug_mode', $enabled ); wp_send_json_success([ 'status' => $enabled ]); } public function render_ui() { $pixel_id = get_option('pxsync_pixel_id', ''); $enable_am = get_option('pxsync_enable_am', 0); $capi_token = get_option('pxsync_capi_token', ''); $test_code = get_option('pxsync_test_event_code', ''); $debug_enabled = get_option('pxsync_debug_mode', 0); $feed_url = add_query_arg( 'pxsync_feed', class_exists('PXSYNC_Feed') ? PXSYNC_Feed::get_secret_key() : 'error_missing_class', home_url( '/' ) ); // Load Event Toggles (Default to 1 / ON if not set yet) $evt_pv = get_option('pxsync_evt_pageview', 1); $evt_vc = get_option('pxsync_evt_viewcontent', 1); $evt_atc = get_option('pxsync_evt_addtocart', 1); $evt_vcart = get_option('pxsync_evt_viewcart', 1); $evt_ic = get_option('pxsync_evt_initiatecheckout', 1); $evt_api = get_option('pxsync_evt_addpayment', 1); $evt_pur = get_option('pxsync_evt_purchase', 1); $evt_src = get_option('pxsync_evt_search', 1); $evt_reg = get_option('pxsync_evt_registration', 1); // Check if settings were just saved so we can keep the menu open // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display-only flag set by WordPress after saving $is_settings_saved = ( isset( $_GET['settings-updated'] ) && '' !== sanitize_text_field( wp_unslash( $_GET['settings-updated'] ) ) ) ? 'open' : ''; ?>

PixelSync for Tiktok

📡 Connection Settings

Sends secure, SHA-256 hashed customer data to TikTok to maximize ad attribution.

Secured Data: Email Phone City
🎛️ Event Management

Select which events should be tracked. Disabling an event turns off both the Browser Pixel and Server CAPI for that action.

['PageView', $evt_pv, 'Fires on every page load.'], 'pxsync_evt_viewcontent' => ['ViewContent', $evt_vc, 'Fires on product pages.'], 'pxsync_evt_addtocart' => ['AddToCart', $evt_atc, 'Fires on adding to cart.'], 'pxsync_evt_viewcart' => ['ViewCart', $evt_vcart, 'Fires on the cart page.'], 'pxsync_evt_initiatecheckout' => ['InitiateCheckout', $evt_ic, 'Fires when checkout begins.'], 'pxsync_evt_addpayment' => ['AddPaymentInfo', $evt_api, 'Fires on payment method select.'], 'pxsync_evt_purchase' => ['Purchase', $evt_pur, 'Fires on successful orders.'], 'pxsync_evt_search' => ['Search', $evt_src, 'Fires on store searches.'], 'pxsync_evt_registration' => ['CompleteRegistration', $evt_reg, 'Fires on account creation.'] ]; foreach ($event_ui_map as $key => $data) { $label = $data[0]; $val = $data[1]; $desc = $data[2]; ?>

🛍️ Product Catalog Feed

Use this URL in TikTok Business Center to sync your WooCommerce products.

Live Diagnostic Feed BETA

Real-time view of events firing. Helps verify deduplication.

⚠️ Experimental Feature: The Live Logger UI is currently in active development. Due to aggressive mobile browser caching and connection drops, some server events may occasionally fail to display here. Rest assured, your actual TikTok Ads Manager data and CAPI delivery remain 100% accurate and unaffected.

Event Source Platform User ID Device Status Time