—';
return;
}
echo esc_html( implode( ', ', $parts ) );
}
public static function format_device_type( $device_type ) {
$device_type = sanitize_key( (string) $device_type );
if ( '' === $device_type ) {
return '—';
}
return ucfirst( $device_type );
}
public static function format_entry_source( $entry_source ) {
$entry_source = sanitize_key( (string) $entry_source );
if ( '' === $entry_source ) {
return '—';
}
$labels = array(
'google_cpc' => __( 'Google Ads', 'adct-tracking-template' ),
'google_organic' => __( 'Google Organic', 'adct-tracking-template' ),
'direct' => __( 'Direct', 'adct-tracking-template' ),
'referral' => __( 'Referral', 'adct-tracking-template' ),
'youtube' => __( 'YouTube', 'adct-tracking-template' ),
'facebook' => __( 'Facebook', 'adct-tracking-template' ),
'instagram' => __( 'Instagram', 'adct-tracking-template' ),
);
return $labels[ $entry_source ] ?? ucwords( str_replace( '_', ' ', $entry_source ) );
}
public static function format_landing_path_short( $landing_path, $max_length = 42 ) {
$landing_path = trim( (string) $landing_path );
if ( '' === $landing_path ) {
return '';
}
if ( preg_match( '#^https?://#i', $landing_path ) ) {
$parsed = wp_parse_url( $landing_path );
if ( ! empty( $parsed['path'] ) ) {
$landing_path = $parsed['path'];
}
if ( ! empty( $parsed['query'] ) ) {
$landing_path .= '?' . $parsed['query'];
}
} elseif ( false !== strpos( $landing_path, '/' ) && '/' !== $landing_path[0] ) {
$first_slash = strpos( $landing_path, '/' );
$landing_path = substr( $landing_path, $first_slash );
}
$landing_path = preg_replace( '#/+#', '/', $landing_path );
if ( strlen( $landing_path ) <= $max_length ) {
return $landing_path;
}
return substr( $landing_path, 0, $max_length - 1 ) . '…';
}
public static function format_duration( $seconds ) {
$seconds = absint( $seconds );
if ( 0 === $seconds ) {
return '—';
}
if ( $seconds < 60 ) {
/* translators: %d: number of seconds (abbreviated as "s") */
return sprintf( __( '%ds', 'adct-tracking-template' ), $seconds );
}
$minutes = (int) floor( $seconds / 60 );
$remain = $seconds % 60;
if ( $minutes < 60 ) {
if ( $remain ) {
/* translators: 1: number of minutes (abbreviated as "m"), 2: number of seconds (abbreviated as "s") */
return sprintf( __( '%1$dm %2$ds', 'adct-tracking-template' ), $minutes, $remain );
}
/* translators: %d: number of minutes (abbreviated as "m") */
return sprintf( __( '%dm', 'adct-tracking-template' ), $minutes );
}
$hours = (int) floor( $minutes / 60 );
$mins = $minutes % 60;
if ( $mins ) {
/* translators: 1: number of hours (abbreviated as "h"), 2: number of minutes (abbreviated as "m") */
return sprintf( __( '%1$dh %2$dm', 'adct-tracking-template' ), $hours, $mins );
}
/* translators: %d: number of hours (abbreviated as "h") */
return sprintf( __( '%dh', 'adct-tracking-template' ), $hours );
}
public static function has_attribution_data( $row ) {
$fields = array(
'entry_source',
'landing_url',
'landing_path',
'utm_campaign',
'utm_source',
'utm_medium',
'gclid',
'landing_referrer',
);
foreach ( $fields as $field ) {
if ( ! empty( $row->$field ) ) {
return true;
}
}
return ! empty( $row->duration_seconds );
}
public static function render_product_image( $url, $title ) {
if ( empty( $url ) ) {
echo '—';
return;
}
printf(
'
',
esc_url( $url ),
esc_attr( $title )
);
}
public static function render_pagination( $total_items, $per_page, $paged, $query_args ) {
$total_pages = max( 1, (int) ceil( $total_items / $per_page ) );
if ( $total_pages <= 1 ) {
return;
}
$query_args['per_page'] = $per_page;
unset( $query_args['paged'] );
$links = paginate_links(
array(
'base' => add_query_arg( array_merge( $query_args, array( 'paged' => '%#%' ) ), admin_url( 'admin.php' ) ),
'format' => '',
'current' => $paged,
'total' => $total_pages,
'prev_text' => '« ' . __( 'Previous', 'adct-tracking-template' ),
'next_text' => __( 'Next', 'adct-tracking-template' ) . ' »',
'type' => 'plain',
)
);
if ( ! $links ) {
return;
}
echo '
';
}
public static function format_contact_type_label( $contact_type ) {
$labels = array(
'whatsapp' => __( 'WhatsApp', 'adct-tracking-template' ),
'phone' => __( 'Phone', 'adct-tracking-template' ),
'showroom_landline' => __( 'Showroom', 'adct-tracking-template' ),
'floating_whatsapp' => __( 'Floating WhatsApp', 'adct-tracking-template' ),
'floating_phone' => __( 'Floating Call', 'adct-tracking-template' ),
'elfsight_call' => __( 'Elfsight Call', 'adct-tracking-template' ),
'footer_landline' => __( 'Footer Call', 'adct-tracking-template' ),
);
return $labels[ $contact_type ] ?? ucwords( str_replace( '_', ' ', (string) $contact_type ) );
}
public static function contact_type_badge_class( $contact_type ) {
$map = array(
'whatsapp' => 'is-whatsapp',
'phone' => 'is-phone',
'showroom_landline' => 'is-showroom',
'floating_whatsapp' => 'is-floating',
'floating_phone' => 'is-floating',
'elfsight_call' => 'is-elfsight',
'footer_landline' => 'is-showroom',
);
return $map[ $contact_type ] ?? '';
}
public static function render_meta_box( $title, $items ) {
?>
entry_source ) ) {
$items[] = array(
'label' => __( 'Entry source', 'adct-tracking-template' ),
'value' => self::format_entry_source( $row->entry_source ),
);
}
if ( ! empty( $row->landing_url ) ) {
$items[] = array(
'label' => __( 'Landing URL', 'adct-tracking-template' ),
'value' => $row->landing_url,
'url' => $row->landing_url,
);
} elseif ( ! empty( $row->landing_path ) ) {
$items[] = array(
'label' => __( 'Landing page', 'adct-tracking-template' ),
'value' => $row->landing_path,
);
}
if ( ! empty( $row->session_duration ) || ! empty( $row->duration_seconds ) ) {
$items[] = array(
'label' => __( 'Session time', 'adct-tracking-template' ),
'value' => self::format_duration( $row->session_duration ?? $row->duration_seconds ?? 0 ),
);
}
if ( ! empty( $row->utm_campaign ) ) {
$items[] = array(
'label' => __( 'Campaign name', 'adct-tracking-template' ),
'value' => $row->utm_campaign,
);
}
if ( ! empty( $row->utm_id ) && $row->utm_id !== ( $row->utm_campaign ?? '' ) ) {
$items[] = array(
'label' => __( 'Campaign ID', 'adct-tracking-template' ),
'value' => $row->utm_id,
);
}
if ( ! empty( $row->utm_source ) || ! empty( $row->utm_medium ) ) {
$items[] = array(
'label' => __( 'UTM source / medium', 'adct-tracking-template' ),
'value' => trim( ( $row->utm_source ?? '' ) . ' / ' . ( $row->utm_medium ?? '' ), ' /' ),
);
}
if ( ! empty( $row->utm_term ) ) {
$items[] = array(
'label' => __( 'Keyword', 'adct-tracking-template' ),
'value' => $row->utm_term,
);
}
if ( ! empty( $row->landing_referrer ) ) {
$items[] = array(
'label' => __( 'Referrer', 'adct-tracking-template' ),
'value' => $row->landing_referrer,
);
}
return $items;
}
public static function render_product_block( $row ) {
$title = $row->product_title ?? '';
$url = $row->product_url ?? '';
$image = $row->product_image_url ?? '';
?>
product_price ) ) : ?>
product_price ); ?>
product_mileage ) ) : ?>
product_mileage ); ?>
visitor_country ?? '', $row->visitor_region ?? '' ) ) ) );
$badge = self::contact_type_badge_class( $row->contact_type ?? '' );
?>
__( 'Salesman', 'adct-tracking-template' ),
'value' => $row->agent_name ?: '—',
),
array(
'label' => __( 'Clicked', 'adct-tracking-template' ),
'value' => $row->clicked_value ?: '—',
),
array(
'label' => __( 'Source', 'adct-tracking-template' ),
'value' => $row->source ?: '—',
),
)
);
self::render_meta_box(
__( 'Visitor', 'adct-tracking-template' ),
array(
array(
'label' => __( 'Device', 'adct-tracking-template' ),
'value' => self::format_device_type( $row->device_type ?? '' ),
),
array(
'label' => __( 'Browser', 'adct-tracking-template' ),
'value' => $row->browser_name ?: '—',
),
array(
'label' => __( 'Location', 'adct-tracking-template' ),
'value' => $location ?: '—',
),
)
);
if ( self::has_attribution_data( $row ) ) {
self::render_meta_box( __( 'Marketing', 'adct-tracking-template' ), self::build_marketing_items( $row ) );
}
?>
contact_type ?? '' );
?>
__( 'Salesman', 'adct-tracking-template' ),
'value' => $row->agent_name ?: '—',
),
array(
'label' => __( 'Clicked', 'adct-tracking-template' ),
'value' => $row->clicked_value ?: '—',
),
array(
'label' => __( 'Page clicked', 'adct-tracking-template' ),
'value' => $row->product_url ?: '—',
'url' => $row->product_url ?: '',
),
)
);
?>
visitor_country ?? '', $session->visitor_region ?? '' ) ) ) );
$entry_source = sanitize_key( (string) ( $session->entry_source ?? '' ) );
$source_class = $entry_source ? 'is-source-' . $entry_source : 'is-source-direct';
$click_types = array();
foreach ( $clicks as $click ) {
$type = $click->contact_type ?? '';
if ( $type && ! in_array( $type, $click_types, true ) ) {
$click_types[] = $type;
}
}
?>
>
›
session_started ) ); ?>
→
session_ended ) ); ?>
utm_campaign ) ) : ?>
utm_campaign ); ?>
landing_path ) ) : ?>
landing_path ); ?>
click_count ) )
);
?>
session_duration ?? 0 ) ); ?>
entry_source ) ) : ?>
entry_source ) ); ?>
';
self::render_meta_box(
__( 'Visitor', 'adct-tracking-template' ),
array(
array(
'label' => __( 'Device', 'adct-tracking-template' ),
'value' => self::format_device_type( $session->device_type ?? '' ),
),
array(
'label' => __( 'Browser', 'adct-tracking-template' ),
'value' => $session->browser_name ?: '—',
),
array(
'label' => __( 'Location', 'adct-tracking-template' ),
'value' => $location ?: '—',
),
)
);
self::render_meta_box( __( 'Marketing', 'adct-tracking-template' ), $marketing_items );
echo '
';
}
?>
contact_type ?? '' );
?>
clicks ) )
);
?>
last_clicked_at ) ) : ?>
last_clicked_at )
);
?>
class_exists( 'WooCommerce' ),
'tracker_js' => file_exists( $tracker_path ),
'entry_js' => file_exists( $entry_path ),
'wp_rocket' => defined( 'WP_ROCKET_VERSION' ),
);
}
public static function render_sidebar( array $snapshot, $export_url, $show_setup ) {
$status = self::get_system_status();
$can_manage = ADCT_Settings::user_can_manage();
$allowed_roles = ADCT_Settings::get_allowed_roles();
$available_roles = ADCT_Settings::get_available_roles();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display filter, not a state-changing action
$access_saved = isset( $_GET['adct_access'] ) && 'saved' === $_GET['adct_access'];
?>
$snapshot,
'show_setup' => $show_setup,
);
}
public static function render_overview_page() {
if ( ! ADCT_Settings::user_can_view() ) {
return;
}
$period = ADCT_Analytics::get_period_from_request();
$overview = ADCT_Analytics::get_overview_data( $period );
$page = self::get_page_context();
$snapshot = $page['snapshot'];
$show_setup = $page['show_setup'];
$sessions_url = admin_url( 'admin.php?page=adct-contact-click-tracker-sessions' );
$leads_url = admin_url( 'admin.php?page=adct-contact-click-tracker-leads' );
$has_data = ! empty( $overview['totals']['clicks'] );
$has_marketing = ! empty( $overview['source_breakdown'] ) || ! empty( $overview['campaign_breakdown'] ) || ! empty( $overview['landing_breakdown'] );
$top_kpi_label = __( 'Top campaign', 'adct-tracking-template' );
$top_kpi_value = $overview['totals']['top_campaign'];
if ( '—' === $top_kpi_value ) {
$top_kpi_label = __( 'Top source', 'adct-tracking-template' );
$top_kpi_value = $overview['totals']['top_source'];
}
wp_localize_script(
'adct-overview',
'adctOverviewConfig',
array(
'contactLabels' => $overview['charts']['contact_labels'],
'contactCounts' => $overview['charts']['contact_counts'],
'contactColors' => $overview['charts']['contact_colors'],
'sourceLabels' => $overview['charts']['source_labels'],
'sourceCounts' => $overview['charts']['source_counts'],
'sourceColors' => $overview['charts']['source_colors'],
'campaignLabels' => $overview['charts']['campaign_labels'],
'campaignCounts' => $overview['charts']['campaign_counts'],
'campaignColors' => $overview['charts']['campaign_colors'],
'dayLabels' => $overview['charts']['labels'],
'clickSeries' => $overview['charts']['clicks'],
'sessionSeries' => $overview['charts']['sessions'],
)
);
?>
-
clicked_at ?? '' ) ); ?>
product_title ?: __( 'Site-wide contact', 'adct-tracking-template' ) ); ?>
contact_type ?? '' ),
! empty( $lead->entry_source ) ? self::format_entry_source( $lead->entry_source ) : '',
ADCT_Leads::is_salesman_attributed_click( $lead->contact_type ?? '', $lead->agent_name ?? '' ) ? $lead->agent_name : '',
)
)
)
)
);
?>
contact_type ?? '' ) ); ?>
' . esc_html__( 'No data yet.', 'adct-tracking-template' ) . '';
return;
}
?>
$row ) : ?>
-
{$label_key} ?? '' ); ?>
{$count_key} ?? 0 ) ) ); ?>
' . esc_html__( 'No attributed traffic yet. Once visitors arrive from Google, ads, or referrals and click contact, channels will appear here.', 'adct-tracking-template' ) . '';
return;
}
?>
$total_pages ) {
$paged = $total_pages;
$offset = ( $paged - 1 ) * $per_page;
}
$lead_rows = ADCT_Database::get_clicks( $filters, $per_page, $offset );
$session_context = ADCT_Database::get_lead_session_context( $lead_rows );
$channel_counts = ADCT_Leads::get_channel_counts( $tab_filters );
$agents = ADCT_Database::get_distinct_values( 'agent_name' );
$entry_sources = ADCT_Database::get_distinct_values( 'entry_source' );
$campaigns = ADCT_Database::get_distinct_values( 'utm_campaign' );
$page_context = self::get_page_context();
$snapshot = $page_context['snapshot'];
$show_setup = $page_context['show_setup'];
$base_url = admin_url( 'admin.php?page=adct-contact-click-tracker-leads' );
$query_args = array_merge(
$tab_filters,
array(
'page' => 'adct-contact-click-tracker-leads',
'lead_channel' => $channel,
'per_page' => $per_page,
)
);
$tab_query = array_merge(
$tab_filters,
array(
'page' => 'adct-contact-click-tracker-leads',
'per_page' => $per_page,
)
);
$from_item = $list_total ? ( $offset + 1 ) : 0;
$to_item = min( $offset + $per_page, $list_total );
$export_url = wp_nonce_url(
add_query_arg(
array_merge(
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display filter, not a state-changing action
$_GET,
array(
'adct_export' => 'csv',
'page' => 'adct-contact-click-tracker-leads',
)
),
$base_url
),
'adct_export_csv'
);
?>
' . esc_html__( 'Likely engaged', 'adct-tracking-template' ) . '' );
?>
contact_type ?? '' );
$status = ADCT_Leads::get_lead_status_label( $row->contact_type ?? '' );
$has_product = ! empty( $row->product_title ) || ! empty( $row->product_id );
$date_parts = ADCT_Leads::format_lead_datetime_parts( $row->clicked_at ?? '' );
$source_text = ! empty( $row->entry_source ) ? self::format_entry_source( $row->entry_source ) : '—';
$source_hint = self::format_landing_path_short( $row->landing_path ?? '' );
?>
|
|
|
contact_type ?? '' ) ); ?>
|
product_image_url ) ) : ?>
product_title ?: __( 'Site-wide contact', 'adct-tracking-template' ) ); ?>
product_price ) || ! empty( $row->product_mileage ) ) : ?>
product_price ?? '',
$row->product_mileage ?? '',
)
)
)
)
);
?>
product_url ) ) : ?>
|
|
utm_campaign ?: '—' ); ?> |
contact_type ?? '', $row->agent_name ?? '' ) ); ?> |
id ?? 0 );
$position = $positions[ $row_id ] ?? null;
$summary = ( ! empty( $session_key ) && isset( $summaries[ $session_key ] ) ) ? $summaries[ $session_key ] : null;
$session_url = ADCT_Leads::build_session_view_url( $session_key );
if ( '' === $session_key || ! $session_url ) {
echo '' . esc_html__( 'No session', 'adct-tracking-template' ) . '';
return;
}
$session_code = ADCT_Leads::format_session_code( $session_key );
$position_txt = $position
? sprintf(
/* translators: 1: click number in session, 2: total clicks in session */
__( 'Click %1$d of %2$d', 'adct-tracking-template' ),
(int) $position['position'],
(int) $position['total']
)
: '';
?>
-
- click_count ) ); ?>
- session_started ?? '' ) ); ?>
entry_source ) ) : ?>
- entry_source ) ); ?>
device_type ) ) : ?>
- device_type ) ); ?>
- #
session_id ?? '',
$row->clicked_at ?? '',
$row->product_title,
$row->product_url,
$row->product_price ?? '',
$row->product_mileage ?? '',
$row->product_image_url ?? '',
$row->agent_name,
$row->contact_type,
$row->clicked_value ?? '',
$row->source ?? '',
$row->device_type ?? '',
$row->browser_name ?? '',
$row->visitor_country ?? '',
$row->visitor_region ?? '',
$row->entry_source ?? '',
$row->landing_url ?? '',
$row->landing_path ?? '',
$row->landing_referrer ?? '',
$row->utm_source ?? '',
$row->utm_medium ?? '',
$row->utm_campaign ?? '',
$row->utm_term ?? '',
$row->utm_content ?? '',
$row->gclid ?? '',
$row->duration_seconds ?? 0,
)
);
}
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- streaming CSV directly to php://output for browser download, not a real file; WP_Filesystem has no stream-output equivalent
fclose( $output );
exit;
}
public static function render_reports_page() {
if ( ! ADCT_Settings::user_can_view() ) {
return;
}
$filters = ADCT_Database::get_filters_from_request();
$pagination = ADCT_Database::get_pagination_args();
$per_page = $pagination['per_page'];
$paged = $pagination['paged'];
$offset = $pagination['offset'];
$list_total = ADCT_Database::count_sessions( $filters );
$total_pages = max( 1, (int) ceil( $list_total / $per_page ) );
if ( $paged > $total_pages ) {
$paged = $total_pages;
$offset = ( $paged - 1 ) * $per_page;
}
$session_rows = ADCT_Database::get_sessions( $filters, $per_page, $offset );
$session_keys = array_map(
static function ( $session ) {
return $session->session_key;
},
$session_rows
);
$clicks_by_session = ADCT_Database::get_clicks_for_session_keys( $session_keys, $filters );
$total_clicks = ADCT_Database::count_clicks( $filters );
$agents = ADCT_Database::get_distinct_values( 'agent_name' );
$types = ADCT_Database::get_distinct_values( 'contact_type' );
$devices = ADCT_Database::get_distinct_values( 'device_type' );
$countries = ADCT_Database::get_distinct_values( 'visitor_country' );
$entry_sources = ADCT_Database::get_distinct_values( 'entry_source' );
$campaigns = ADCT_Database::get_distinct_values( 'utm_campaign' );
$landing_paths = ADCT_Database::get_distinct_values( 'landing_path' );
$page_context = self::get_page_context();
$snapshot = $page_context['snapshot'];
$show_setup = $page_context['show_setup'];
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display filter, not a state-changing action
$focus_session = ! empty( $filters['session_id'] ) ? sanitize_text_field( $filters['session_id'] ) : '';
$base_url = admin_url( 'admin.php?page=adct-contact-click-tracker-sessions' );
$query_args = array_merge(
$filters,
array(
'page' => 'adct-contact-click-tracker-sessions',
'per_page' => $per_page,
)
);
$from_item = $list_total ? ( $offset + 1 ) : 0;
$to_item = min( $offset + $per_page, $list_total );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display filter, not a state-changing action
$export_url = wp_nonce_url( add_query_arg( array_merge( $_GET, array( 'adct_export' => 'csv' ) ), $base_url ), 'adct_export_csv' );
?>
session_key ] ?? array();
$is_focused = $focus_session && $focus_session === (string) $session->session_key;
self::render_session_card( $session, $session_clicks, $is_focused );
?>