'int', 'restaurant_id' => 'int', 'booking_id' => 'int', 'settings' => 'array' ]; protected $fillable = [ 'restaurant_id', 'booking_id', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'gclid', 'fbclid', 'msclkid', 'ttclid', 'referrer_url', 'landing_page', 'user_agent', 'ip_address', 'raw_query', 'settings' ]; // Primary click IDs - covers 99% of restaurant advertising const PRIMARY_CLICK_IDS = [ 'gclid' => 'Google Ads', 'fbclid' => 'Facebook/Instagram Ads', 'msclkid' => 'Microsoft Ads (Bing)', 'ttclid' => 'TikTok Ads' ]; // Additional click IDs that can be stored in settings const SECONDARY_CLICK_IDS = [ 'twclid' => 'Twitter/X Ads', 'li_fat_id' => 'LinkedIn Ads', 'dclid' => 'Google Display & Video 360', 'sccid' => 'Snapchat Ads', 'gbraid' => 'Google iOS tracking', 'wbraid' => 'Google web-to-app tracking', 'irclickid' => 'Impact Radius', 'ScCid' => 'Snapchat Creative ID', 'yclid' => 'Yandex Click ID', 'epik' => 'Pinterest Enhanced Match' ]; public function restaurant() { return $this->belongsTo(Restaurant::class); } public function booking() { return $this->belongsTo(Booking::class); } /** * Get all tracking data as array */ public function toArray() { return [ 'id' => $this->id, 'restaurant_id' => $this->restaurant_id, 'booking_id' => $this->booking_id, 'utm_source' => $this->utm_source, 'utm_medium' => $this->utm_medium, 'utm_campaign' => $this->utm_campaign, 'utm_term' => $this->utm_term, 'utm_content' => $this->utm_content, 'utm_id' => $this->utm_id, 'gclid' => $this->gclid, 'fbclid' => $this->fbclid, 'msclkid' => $this->msclkid, 'ttclid' => $this->ttclid, 'referrer_url' => $this->referrer_url, 'landing_page' => $this->landing_page, 'raw_query' => $this->raw_query, 'platform' => $this->getPlatform(), 'additional_click_ids' => $this->getAdditionalClickIds(), 'additional_params' => $this->getAdditionalParams(), 'date_created' => $this->date_created ]; } /** * Create tracking from request data */ public static function createFromRequest($booking_id, $restaurant_id) { $tracking = new self(); $tracking->booking_id = $booking_id; $tracking->restaurant_id = $restaurant_id; // Store raw query string $tracking->raw_query = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : null; // UTM parameters (standard) $tracking->utm_source = isset($_GET['utm_source']) ? sanitize_text_field($_GET['utm_source']) : null; $tracking->utm_medium = isset($_GET['utm_medium']) ? sanitize_text_field($_GET['utm_medium']) : null; $tracking->utm_campaign = isset($_GET['utm_campaign']) ? sanitize_text_field($_GET['utm_campaign']) : null; $tracking->utm_term = isset($_GET['utm_term']) ? sanitize_text_field($_GET['utm_term']) : null; $tracking->utm_content = isset($_GET['utm_content']) ? sanitize_text_field($_GET['utm_content']) : null; $tracking->utm_id = isset($_GET['utm_id']) ? sanitize_text_field($_GET['utm_id']) : null; // Primary Click IDs (most common in restaurant advertising) $tracking->gclid = isset($_GET['gclid']) ? sanitize_text_field($_GET['gclid']) : null; $tracking->fbclid = isset($_GET['fbclid']) ? sanitize_text_field($_GET['fbclid']) : null; $tracking->msclkid = isset($_GET['msclkid']) ? sanitize_text_field($_GET['msclkid']) : null; $tracking->ttclid = isset($_GET['ttclid']) ? sanitize_text_field($_GET['ttclid']) : null; // Referrer and landing page $tracking->referrer_url = isset($_SERVER['HTTP_REFERER']) ? esc_url_raw($_SERVER['HTTP_REFERER']) : null; $tracking->landing_page = isset($_SERVER['REQUEST_URI']) ? esc_url_raw($_SERVER['REQUEST_URI']) : null; // User agent and IP $tracking->user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field($_SERVER['HTTP_USER_AGENT']) : null; $tracking->ip_address = self::getClientIp(); // Store secondary click IDs and additional params in settings $tracking->storeAdditionalClickIds($_GET); $tracking->storeAdditionalParams($_GET); $tracking->save(); return $tracking; } /** * Get the advertising platform from click IDs */ public function getPlatform() { // Check primary click IDs first foreach (self::PRIMARY_CLICK_IDS as $clid => $platform) { if (!empty($this->{$clid})) { return $platform; } } // Check secondary click IDs in settings $additionalClickIds = $this->getAdditionalClickIds(); foreach (self::SECONDARY_CLICK_IDS as $clid => $platform) { if (isset($additionalClickIds[$clid]) && !empty($additionalClickIds[$clid])) { return $platform; } } // Fallback to UTM source if no CLID if (!empty($this->utm_source)) { return ucfirst($this->utm_source); } return 'Direct/Unknown'; } /** * Check if tracking has any click ID (primary or secondary) */ public function hasClickId() { // Check primary foreach (array_keys(self::PRIMARY_CLICK_IDS) as $clid) { if (!empty($this->{$clid})) { return true; } } // Check secondary $additionalClickIds = $this->getAdditionalClickIds(); return !empty($additionalClickIds); } /** * Get all click IDs present in this tracking (primary + secondary) */ public function getAllClickIds() { $clickIds = []; // Primary click IDs foreach (self::PRIMARY_CLICK_IDS as $clid => $platform) { if (!empty($this->{$clid})) { $clickIds[$clid] = [ 'value' => $this->{$clid}, 'platform' => $platform, 'type' => 'primary' ]; } } // Secondary click IDs from settings $additionalClickIds = $this->getAdditionalClickIds(); foreach ($additionalClickIds as $clid => $value) { if (!empty($value)) { $platform = isset(self::SECONDARY_CLICK_IDS[$clid]) ? self::SECONDARY_CLICK_IDS[$clid] : ucfirst($clid); $clickIds[$clid] = [ 'value' => $value, 'platform' => $platform, 'type' => 'secondary' ]; } } return $clickIds; } /** * Store secondary click IDs in settings */ protected function storeAdditionalClickIds($params) { $additionalClickIds = []; foreach (array_keys(self::SECONDARY_CLICK_IDS) as $clid) { if (isset($params[$clid]) && !empty($params[$clid])) { $additionalClickIds[$clid] = sanitize_text_field($params[$clid]); } } // Also capture any other *clid parameter not in our lists foreach ($params as $key => $value) { if ((stripos($key, 'clid') !== false || stripos($key, 'click') !== false) && !isset(self::PRIMARY_CLICK_IDS[$key]) && !isset(self::SECONDARY_CLICK_IDS[$key])) { $additionalClickIds[$key] = sanitize_text_field($value); } } if (!empty($additionalClickIds)) { $this->set_setting('additional_click_ids', $additionalClickIds); } } /** * Get secondary click IDs from settings */ public function getAdditionalClickIds() { return $this->get_setting('additional_click_ids', []); } /** * Store additional tracking parameters not in standard fields */ protected function storeAdditionalParams($params) { $standardParams = array_merge( ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id'], array_keys(self::PRIMARY_CLICK_IDS), array_keys(self::SECONDARY_CLICK_IDS) ); $additionalParams = []; foreach ($params as $key => $value) { // Store any utm_* parameter not in standard list if (strpos($key, 'utm_') === 0 && !in_array($key, $standardParams)) { $additionalParams[$key] = sanitize_text_field($value); } // Store any other tracking-related parameter elseif (!in_array($key, $standardParams) && (stripos($key, 'ad') !== false || stripos($key, 'campaign') !== false || stripos($key, 'source') !== false)) { $additionalParams[$key] = sanitize_text_field($value); } } if (!empty($additionalParams)) { $this->set_setting('additional_params', $additionalParams); } } /** * Get additional tracking parameters */ public function getAdditionalParams() { return $this->get_setting('additional_params', []); } /** * Get a specific additional parameter */ public function getAdditionalParam($key, $default = null) { $params = $this->getAdditionalParams(); return isset($params[$key]) ? $params[$key] : $default; } /** * Parse raw query to get all parameters */ public function parseRawQuery() { if (!$this->raw_query) { return []; } parse_str($this->raw_query, $params); return $params; } /** * Get client IP address */ protected static function getClientIp() { $ip = ''; if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; } elseif (isset($_SERVER['REMOTE_ADDR'])) { $ip = $_SERVER['REMOTE_ADDR']; } return sanitize_text_field($ip); } /** * Get tracking statistics grouped by source */ public static function getStatsBySource($restaurant_id, $date_from = null, $date_to = null) { $query = self::where('restaurant_id', $restaurant_id); if ($date_from) { $query->where('date_created', '>=', $date_from); } if ($date_to) { $query->where('date_created', '<=', $date_to); } return $query->groupBy('utm_source') ->selectRaw('utm_source, COUNT(*) as total') ->orderBy('total', 'DESC') ->get(); } /** * Get tracking statistics grouped by medium */ public static function getStatsByMedium($restaurant_id, $date_from = null, $date_to = null) { $query = self::where('restaurant_id', $restaurant_id); if ($date_from) { $query->where('date_created', '>=', $date_from); } if ($date_to) { $query->where('date_created', '<=', $date_to); } return $query->groupBy('utm_medium') ->selectRaw('utm_medium, COUNT(*) as total') ->orderBy('total', 'DESC') ->get(); } /** * Get tracking statistics grouped by campaign */ public static function getStatsByCampaign($restaurant_id, $date_from = null, $date_to = null) { $query = self::where('restaurant_id', $restaurant_id); if ($date_from) { $query->where('date_created', '>=', $date_from); } if ($date_to) { $query->where('date_created', '<=', $date_to); } return $query->groupBy('utm_campaign') ->selectRaw('utm_campaign, COUNT(*) as total') ->orderBy('total', 'DESC') ->get(); } /** * Get tracking statistics grouped by platform (from click IDs) */ public static function getStatsByPlatform($restaurant_id, $date_from = null, $date_to = null) { $query = self::where('restaurant_id', $restaurant_id); if ($date_from) { $query->where('date_created', '>=', $date_from); } if ($date_to) { $query->where('date_created', '<=', $date_to); } $trackings = $query->get(); $stats = []; foreach ($trackings as $tracking) { $platform = $tracking->getPlatform(); if (!isset($stats[$platform])) { $stats[$platform] = 0; } $stats[$platform]++; } arsort($stats); return $stats; } /** * Get conversion rate by platform */ public static function getConversionRateByPlatform($restaurant_id, $date_from = null, $date_to = null) { $stats = self::getStatsByPlatform($restaurant_id, $date_from, $date_to); // Get total bookings for the period $totalBookings = Booking::where('restaurant_id', $restaurant_id); if ($date_from) { $totalBookings->where('date_created', '>=', $date_from); } if ($date_to) { $totalBookings->where('date_created', '<=', $date_to); } $total = $totalBookings->count(); $result = []; foreach ($stats as $platform => $count) { $result[$platform] = [ 'count' => $count, 'percentage' => $total > 0 ? round(($count / $total) * 100, 2) : 0 ]; } return $result; } /** * Get full tracking report with all dimensions */ public static function getFullReport($restaurant_id, $date_from = null, $date_to = null) { $query = self::where('restaurant_id', $restaurant_id) ->with('booking'); if ($date_from) { $query->where('date_created', '>=', $date_from); } if ($date_to) { $query->where('date_created', '<=', $date_to); } return $query->orderBy('date_created', 'DESC')->get(); } }