' . 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('
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 .= 'Use this URL in TikTok Business Center to sync your WooCommerce products.
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 |
|---|