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' ); ?>