db = RT_WA_Analytics_DB::instance(); $this->settings = RT_WA_Analytics_Settings::instance(); $this->reports_admin = new RT_WA_Analytics_Reports_Admin(); $this->leads_admin = new RT_WA_Analytics_Leads_Admin(); $this->settings_admin = new RT_WA_Analytics_Settings_Admin(); add_action( 'admin_menu', array( $this, 'add_admin_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_menu_icon_css' ) ); add_action( 'admin_init', array( $this, 'redirect_legacy_slug' ) ); add_action( 'admin_init', array( $this, 'handle_csv_export' ) ); add_filter( 'admin_title', array( $this, 'filter_admin_title' ), 10, 2 ); add_filter( 'admin_page_title', array( $this, 'filter_admin_page_title' ) ); } /** * Contextual browser/admin tab title for every plugin page. * * @param string $admin_title Current admin title. * @param string $title Raw page title. * @return string */ public function filter_admin_title( $admin_title, $title ) { $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $labels = array( 'zaaed-wa-dashboard' => __( 'Dashboard', 'zaaed-click2chat' ), 'zaaed-wa-signal' => __( 'WhatsApp Clicks', 'zaaed-click2chat' ), 'zaaed-wa-leads' => __( 'Leads', 'zaaed-click2chat' ), 'zaaed-wa-conversions' => __( 'Conversion Outcomes', 'zaaed-click2chat' ), 'zaaed-wa-analytics-page' => __( 'Attribution Insights', 'zaaed-click2chat' ), 'zaaed-wa-settings' => __( 'Settings', 'zaaed-click2chat' ), ); if ( ! empty( $page ) && isset( $labels[ $page ] ) ) { return __( 'Zaaed Click2Chat', 'zaaed-click2chat' ) . ' — ' . $labels[ $page ]; } return $admin_title; } /** * In-page heading title for the plugin pages. * * @param string $title Raw page title. * @return string */ public function filter_admin_page_title( $title ) { $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $page ) && in_array( $page, array( 'zaaed-wa-dashboard', 'zaaed-wa-signal', 'zaaed-wa-leads', 'zaaed-wa-conversions', 'zaaed-wa-analytics-page', 'zaaed-wa-settings' ), true ) ) { return __( 'Zaaed Click2Chat', 'zaaed-click2chat' ); } return $title; } /** * Redirect bookmarked requests for legacy rt-wa-* admin slugs to the current zaaed-wa-* ones. */ public function redirect_legacy_slug() { if ( ! isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } $page = sanitize_key( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin render context; capability-gated by add_menu_page. $legacy_map = array( 'zaaed-click2chat' => 'zaaed-wa-dashboard', 'rt-wa-leads' => 'zaaed-wa-leads', 'rt-wa-conversions' => 'zaaed-wa-conversions', 'rt-wa-analytics-page' => 'zaaed-wa-analytics-page', 'rt-wa-settings' => 'zaaed-wa-settings', ); if ( ! isset( $legacy_map[ $page ] ) || $page === $legacy_map[ $page ] ) { return; } $params = $_GET; // phpcs:ignore WordPress.Security.NonceVerification.Recommended unset( $params['page'] ); $params['page'] = $legacy_map[ $page ]; wp_safe_redirect( add_query_arg( $params, admin_url( 'admin.php' ) ) ); exit; } public function add_admin_menu() { $capability = 'manage_options'; add_menu_page( __( 'Zaaed Click2Chat', 'zaaed-click2chat' ), __( 'Zaaed Click2Chat', 'zaaed-click2chat' ), $capability, 'zaaed-wa-dashboard', array( $this, 'render_page' ), RT_WA_ANALYTICS_URL . 'assets/images/zaaed-menu-icon.png', 30 ); add_submenu_page( 'zaaed-wa-dashboard', __( 'Dashboard', 'zaaed-click2chat' ), __( 'Dashboard', 'zaaed-click2chat' ), $capability, 'zaaed-wa-dashboard', array( $this, 'render_page' ) ); add_submenu_page( 'zaaed-wa-dashboard', __( 'WhatsApp Clicks', 'zaaed-click2chat' ), __( 'WhatsApp Clicks', 'zaaed-click2chat' ), $capability, 'zaaed-wa-signal', array( $this, 'render_page' ) ); add_submenu_page( 'zaaed-wa-dashboard', __( 'UTM Links', 'zaaed-click2chat' ), __( 'UTM Links', 'zaaed-click2chat' ), $capability, 'zaaed-wa-utm-links', array( $this, 'render_page' ) ); add_submenu_page( 'zaaed-wa-dashboard', __( 'Leads', 'zaaed-click2chat' ), __( 'Leads', 'zaaed-click2chat' ), $capability, 'zaaed-wa-leads', array( $this, 'render_page' ) ); add_submenu_page( 'zaaed-wa-dashboard', __( 'Conversion Outcomes', 'zaaed-click2chat' ), __( 'Conversion Outcomes', 'zaaed-click2chat' ), $capability, 'zaaed-wa-conversions', array( $this, 'render_page' ) ); add_submenu_page( 'zaaed-wa-dashboard', __( 'Attribution Insights', 'zaaed-click2chat' ), __( 'Attribution Insights', 'zaaed-click2chat' ), $capability, 'zaaed-wa-analytics-page', array( $this, 'render_page' ) ); add_submenu_page( 'zaaed-wa-dashboard', __( 'Settings', 'zaaed-click2chat' ), __( 'Settings', 'zaaed-click2chat' ), $capability, 'zaaed-wa-settings', array( $this, 'render_page' ) ); } public function enqueue_admin_assets( $hook ) { if ( strpos( $hook, 'zaaed-wa-' ) === false && strpos( $hook, 'rt-wa-' ) === false ) { return; } wp_enqueue_style( 'rt-wa-analytics-admin', RT_WA_ANALYTICS_URL . 'assets/css/admin.css', array(), RT_WA_ANALYTICS_VERSION ); // Suppress generic WordPress notices inside plugin screens (scoped to // .rt-wa-wrap only), delivered through the proper styles API. wp_add_inline_style( 'rt-wa-analytics-admin', '.rt-wa-wrap .notice, .rt-wa-wrap .updated, .rt-wa-wrap .error, .rt-wa-wrap #wpbody-content > .notice, .rt-wa-wrap #wpbody-content > .updated, .rt-wa-wrap #wpbody-content > .error { display: none; }' ); // Note: no external font CDN. The admin stylesheet relies on system // font stacks (WordPress.org guidelines disallow third-party CDNs). wp_enqueue_script( 'rt-wa-analytics-admin', RT_WA_ANALYTICS_URL . 'assets/js/admin.js', array( 'jquery', 'chart-js' ), RT_WA_ANALYTICS_VERSION, true ); wp_localize_script( 'rt-wa-analytics-admin', 'rtWaAnalytics', array( 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'rt_wa_analytics_nonce' ), 'i18n' => array( 'confirmDelete' => __( 'Are you sure?', 'zaaed-click2chat' ), 'saving' => __( 'Saving...', 'zaaed-click2chat' ), 'saved' => __( 'Saved successfully!', 'zaaed-click2chat' ), 'error' => __( 'An error occurred.', 'zaaed-click2chat' ), 'apiKeyRevoked' => __( 'API Key revoked.', 'zaaed-click2chat' ), 'dataReset' => __( 'Data reset successfully.', 'zaaed-click2chat' ), 'noData' => __( 'No data available.', 'zaaed-click2chat' ), 'cancel' => __( 'Cancel', 'zaaed-click2chat' ), 'addAudience' => __( 'Add Audience', 'zaaed-click2chat' ), 'added' => __( 'Added to Audience', 'zaaed-click2chat' ), 'show' => __( 'Show', 'zaaed-click2chat' ), 'hide' => __( 'Hide', 'zaaed-click2chat' ), // translators: %s: number of WhatsApp signals. 'lastWaClicks' => __( 'Last %s Signals', 'zaaed-click2chat' ), 'addLead' => __( 'Add Lead', 'zaaed-click2chat' ), 'editLead' => __( 'Edit Lead', 'zaaed-click2chat' ), 'save' => __( 'Save', 'zaaed-click2chat' ), 'leadName' => __( 'Name', 'zaaed-click2chat' ), 'leadPhone' => __( 'Phone', 'zaaed-click2chat' ), 'leadWaPhone' => __( 'WhatsApp Number', 'zaaed-click2chat' ), 'leadEmail' => __( 'Email', 'zaaed-click2chat' ), 'leadStatus' => __( 'Status', 'zaaed-click2chat' ), 'leadValue' => __( 'Opportunity Value', 'zaaed-click2chat' ), 'leadCurrency' => __( 'Currency', 'zaaed-click2chat' ), 'leadSource' => __( 'Source', 'zaaed-click2chat' ), 'leadMedium' => __( 'Medium', 'zaaed-click2chat' ), 'leadCampaign' => __( 'Campaign', 'zaaed-click2chat' ), 'leadLandingPage' => __( 'Signal Entry Page URL', 'zaaed-click2chat' ), 'leadNotes' => __( 'Notes', 'zaaed-click2chat' ), 'confirmStatusChange' => __( 'Move this lead in the pipeline?', 'zaaed-click2chat' ), 'confirmDeleteLead' => __( 'Delete this lead? This will also remove its status history and cannot be undone.', 'zaaed-click2chat' ), 'utmBreakdown' => __( 'UTM Breakdown', 'zaaed-click2chat' ), 'clickDetails' => __( 'Click Details', 'zaaed-click2chat' ), 'trafficSource' => __( 'Traffic Source', 'zaaed-click2chat' ), 'referrer' => __( 'Referrer', 'zaaed-click2chat' ), 'close' => __( 'Close', 'zaaed-click2chat' ), 'noUtmData' => __( 'No UTM parameters captured for this click.', 'zaaed-click2chat' ), 'timestamp' => __( 'Timestamp', 'zaaed-click2chat' ), 'whatsappNumber' => __( 'WhatsApp Number', 'zaaed-click2chat' ), 'sessionId' => __( 'Session', 'zaaed-click2chat' ), 'country' => __( 'Country', 'zaaed-click2chat' ), 'term' => __( 'Term', 'zaaed-click2chat' ), 'content' => __( 'Content', 'zaaed-click2chat' ), 'waSignals' => __( 'WA Signals', 'zaaed-click2chat' ), 'conversations' => __( 'Conversations', 'zaaed-click2chat' ), 'noSignals' => __( 'No WhatsApp signals yet.', 'zaaed-click2chat' ), 'copyLink' => __( 'Copy', 'zaaed-click2chat' ), 'copied' => __( 'Copied!', 'zaaed-click2chat' ), 'copyFailed' => __( 'Could not copy the link.', 'zaaed-click2chat' ), 'savingLink' => __( 'Saving link...', 'zaaed-click2chat' ), 'savedLink' => __( 'Link saved.', 'zaaed-click2chat' ), 'duplicateLink' => __( 'A saved link with this exact URL already exists.', 'zaaed-click2chat' ), 'requiredUrl' => __( 'Please enter a valid http(s) URL.', 'zaaed-click2chat' ), 'requiredFields' => __( 'Name and destination are required.', 'zaaed-click2chat' ), 'confirmDeleteLink' => __( 'Delete this UTM link?', 'zaaed-click2chat' ), 'deletedLink' => __( 'Link deleted.', 'zaaed-click2chat' ), 'previewLabel' => __( 'Generated URL preview', 'zaaed-click2chat' ), 'generated' => __( 'Link generated — review it below, then save.', 'zaaed-click2chat' ), 'savingDefaults' => __( 'Saving defaults...', 'zaaed-click2chat' ), 'defaultsSaved' => __( 'UTM defaults saved.', 'zaaed-click2chat' ), 'detectionFailed' => __( 'Detection failed', 'zaaed-click2chat' ), 'requestFailed' => __( 'Request failed. Please try again.', 'zaaed-click2chat' ), 'deleteSelected' => __( 'Delete Selected', 'zaaed-click2chat' ), 'deleteSelectedBlocked' => __( 'Delete Selected (revoke active keys first)', 'zaaed-click2chat' ), 'confirmTitle' => __( 'Are you sure?', 'zaaed-click2chat' ), 'confirmMessage' => __( 'This action cannot be undone.', 'zaaed-click2chat' ), 'confirm' => __( 'Confirm', 'zaaed-click2chat' ), 'trafficFlow' => __( 'Traffic Flow', 'zaaed-click2chat' ), /* translators: %d: number of visitors. */ 'visitorsSection' => __( 'Visitors (%d)', 'zaaed-click2chat' ), /* translators: %d: number of events. */ 'eventsSection' => __( 'Events (%d)', 'zaaed-click2chat' ), /* translators: %s: search query. */ 'noResultsFor' => __( 'No results found for "%s"', 'zaaed-click2chat' ), /* translators: 1: number of detected integrations, 2: total integrations. */ 'integrationsDetected' => __( '%1$d of %2$d integrations detected', 'zaaed-click2chat' ), 'detected' => __( 'Detected', 'zaaed-click2chat' ), 'notDetected' => __( 'Not Detected', 'zaaed-click2chat' ), 'deleteApiKeyTitle' => __( 'Delete API Key', 'zaaed-click2chat' ), 'deleteKeyMessage' => __( 'Are you sure you want to permanently delete this revoked API key? This action cannot be undone.', 'zaaed-click2chat' ), 'deleteSelectedTitle' => __( 'Delete Selected Keys', 'zaaed-click2chat' ), /* translators: %d: number of API keys. */ 'deleteKeysMessage' => __( 'Are you sure you want to permanently delete %d selected revoked API key(s)? This action cannot be undone.', 'zaaed-click2chat' ), /* translators: %d: number of API keys. */ 'deleteN' => __( 'Delete (%d)', 'zaaed-click2chat' ), 'revokeKeyTitle' => __( 'Revoke API Key', 'zaaed-click2chat' ), 'revokeKeyMessage' => __( 'Are you sure you want to revoke this API key? Any services or applications using this key will immediately lose access to the API. This action cannot be undone.', 'zaaed-click2chat' ), 'deleteAllRevokedTitle' => __( 'Delete All Revoked Keys', 'zaaed-click2chat' ), 'deleteAllMessage' => __( 'Are you sure you want to permanently delete all revoked API keys? This action cannot be undone.', 'zaaed-click2chat' ), 'deleteAll' => __( 'Delete All', 'zaaed-click2chat' ), ), 'leadStatuses' => $this->get_lead_statuses_for_js(), ) ); wp_enqueue_script( 'chart-js', RT_WA_ANALYTICS_URL . 'assets/js/chart.umd.min.js', array(), '4.5.1', true ); } /** * Load the Zaaed menu icon styles on every admin page so the sidebar * icon renders centered and undistorted regardless of the current screen. */ public function enqueue_menu_icon_css() { wp_enqueue_style( 'rt-wa-menu-icon', RT_WA_ANALYTICS_URL . 'assets/css/admin-menu-icon.css', array(), RT_WA_ANALYTICS_VERSION ); } private function get_lead_statuses_for_js() { $statuses = RT_WA_Analytics_Leads::instance()->get_statuses(); $items = array(); foreach ( $statuses as $key => $label ) { $items[] = array( 'key' => $key, 'label' => $label, ); } return $items; } public function render_page() { $this->render_shell(); } private function render_shell() { $this->resolve_date_range(); $date_from = $this->date_from; $date_to = $this->date_to; $date_label = $this->date_label; $earliest_date = $this->db->get_earliest_record_date(); $earliest_year = (int) gmdate( 'Y', strtotime( $earliest_date ) ); $current_year = (int) current_time( 'Y' ); ?>
reports_admin->render_dashboard_content(); break; case 'zaaed-wa-signal': $this->reports_admin->render_whatsapp_clicks_content(); break; case 'zaaed-wa-utm-links': $this->reports_admin->render_utm_links_content(); break; case 'zaaed-wa-leads': $this->leads_admin->render_leads_content(); break; case 'zaaed-wa-conversions': $this->leads_admin->render_conversions_content(); break; case 'zaaed-wa-analytics-page': $this->reports_admin->render_analytics_content(); break; case 'zaaed-wa-settings': $this->settings_admin->render_settings_content(); break; default: $this->reports_admin->render_dashboard_content(); break; } ?>
resolve_date_range(); $this->export_csv( $type ); } private function sanitize_csv_field( $value ) { $value = (string) $value; if ( $value !== '' && preg_match( '/^[=+@\t\r-]/', $value ) ) { return "'" . $value; } return $value; } private function export_csv( $type ) { global $wpdb; set_time_limit( 0 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged $filename = 'zaaed-click2chat-' . gmdate( 'Y-m-d' ) . '.csv'; header( 'Content-Type: text/csv; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'X-Content-Type-Options: nosniff' ); header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' ); header( 'Pragma: no-cache' ); header( 'Expires: 0' ); $output = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen if ( false === $output ) { exit; } // UTF-8 byte-order mark so spreadsheet apps detect the encoding. fwrite( $output, "\xEF\xBB\xBF" ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite $leads = RT_WA_Analytics_Leads::instance(); switch ( $type ) { case 'leads': fputcsv( $output, array( 'ID', 'Name', 'Phone', 'WA Phone', 'Email', 'Status', 'Source', 'Medium', 'Campaign', 'Term', 'Content', 'Landing Page', 'Value', 'Currency', 'Created', 'Updated' ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fputcsv $rows = $leads->get_export_rows(); foreach ( $rows as $row ) { $row->phone = $leads->reveal_value( $row->phone ); $row->wa_phone = $leads->reveal_value( $row->wa_phone ); fputcsv( $output, array_map( array( $this, 'sanitize_csv_field' ), get_object_vars( $row ) ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fputcsv } break; case 'whatsapp-clicks': fputcsv( $output, array( 'ID', 'Time', 'Visitor', 'Page', 'Source', 'Medium', 'Campaign', 'Term', 'Content', 'Referrer', 'WhatsApp Number', 'Device', 'Browser', 'OS', 'Country' ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fputcsv $filters = array( 'date_from' => $this->date_from, 'date_to' => $this->date_to, 'source' => isset( $_GET['source'] ) ? sanitize_text_field( wp_unslash( $_GET['source'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended 'utm_medium' => isset( $_GET['utm_medium'] ) ? sanitize_text_field( wp_unslash( $_GET['utm_medium'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended 'campaign' => isset( $_GET['campaign'] ) ? sanitize_text_field( wp_unslash( $_GET['campaign'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended 'page' => isset( $_GET['landing'] ) ? sanitize_text_field( wp_unslash( $_GET['landing'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended 'visitor' => isset( $_GET['visitor'] ) ? sanitize_text_field( wp_unslash( $_GET['visitor'] ) ) : '', // phpcs:ignore WordPress.Security.NonceVerification.Recommended ); $rows = $this->db->get_whatsapp_export_rows( $filters, 10000 ); foreach ( $rows as $row ) { $row->whatsapp_number = $leads->reveal_value( $row->whatsapp_number ); fputcsv( $output, array_map( array( $this, 'sanitize_csv_field' ), get_object_vars( $row ) ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fputcsv } break; default: fputcsv( $output, array( 'No data' ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fputcsv } fclose( $output ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose exit; } }