RealTime.php
129 lines
| 1 | <?php |
| 2 | |
| 3 | namespace IAWP\RealTime; |
| 4 | |
| 5 | use IAWPSCOPED\Carbon\CarbonImmutable; |
| 6 | use IAWP\Click_Tracking\Click_Processing_Job; |
| 7 | use IAWP\Date_Range\Exact_Date_Range; |
| 8 | use IAWP\Examiner_Config; |
| 9 | use IAWP\Icon_Directory_Factory; |
| 10 | use IAWP\Map_Data; |
| 11 | use IAWP\Rows\Countries; |
| 12 | use IAWP\Tables\Groups\Group; |
| 13 | use IAWP\Tables\Table_Campaigns; |
| 14 | use IAWP\Tables\Table_Devices; |
| 15 | use IAWP\Tables\Table_Geo; |
| 16 | use IAWP\Tables\Table_Pages; |
| 17 | use IAWP\Tables\Table_Referrers; |
| 18 | use IAWP\Utils\Singleton; |
| 19 | use IAWP\Utils\Timezone; |
| 20 | use IAWPSCOPED\Illuminate\Support\Collection; |
| 21 | /** @internal */ |
| 22 | class RealTime |
| 23 | { |
| 24 | use Singleton; |
| 25 | public const CHART_MINUTES = [5, 30]; |
| 26 | private ?CarbonImmutable $starting_at; |
| 27 | private ?Examiner_Config $examiner_config; |
| 28 | public function __construct(?CarbonImmutable $starting_at = null, ?Examiner_Config $examiner_config = null) |
| 29 | { |
| 30 | $this->starting_at = $starting_at ?? CarbonImmutable::now(Timezone::utc_timezone()); |
| 31 | $this->examiner_config = $examiner_config; |
| 32 | } |
| 33 | public function get_real_time_analytics() : array |
| 34 | { |
| 35 | // Clicks need to be processed before showing real-time data |
| 36 | (new Click_Processing_Job())->handle(); |
| 37 | $five_minutes_ago = $this->starting_at->subMinutes(5); |
| 38 | $range = new Exact_Date_Range($five_minutes_ago->toDateTime(), $this->starting_at->toDateTime(), \false); |
| 39 | $chart_minutes = $this->get_chart_minutes_preference(); |
| 40 | $chart_interval_seconds = $chart_minutes === 30 ? 60 : 10; |
| 41 | $chart_end = $chart_interval_seconds === 10 ? $this->starting_at->ceilSeconds($chart_interval_seconds) : $this->starting_at; |
| 42 | $chart_start = $chart_interval_seconds === 60 ? $chart_end->subMinutes($chart_minutes)->startOfMinute() : $chart_end->subMinutes($chart_minutes)->subSeconds($chart_interval_seconds); |
| 43 | $chart_range = new Exact_Date_Range($chart_start->toDateTime(), $chart_end->toDateTime(), \false); |
| 44 | $recent_traffic = new \IAWP\RealTime\RecentTraffic($chart_range, $this->examiner_config, $chart_interval_seconds); |
| 45 | $recent_visitors = new \IAWP\RealTime\RecentVisitors($range, $this->examiner_config); |
| 46 | $visitors = $recent_visitors->visitors(); |
| 47 | $chart_data = ['short_labels' => $recent_traffic->short_labels(), 'long_labels' => $recent_traffic->long_labels(), 'timestamps' => $recent_traffic->timestamps(), 'views' => $recent_traffic->views(), 'orders' => $recent_traffic->orders(), 'clicks' => $recent_traffic->clicks(), 'form_submissions' => $recent_traffic->form_submissions()]; |
| 48 | $geo_table = new Table_Geo(); |
| 49 | $countries = new Countries($range, $geo_table->sanitize_sort_parameters('visitors')); |
| 50 | if ($this->examiner_config) { |
| 51 | if ($geo_table->id() === $this->examiner_config->type()) { |
| 52 | $countries->limit_to($this->examiner_config->id()); |
| 53 | } elseif ($geo_table->id() !== $this->examiner_config->type()) { |
| 54 | $countries->for_examiner($this->examiner_config); |
| 55 | } |
| 56 | } |
| 57 | $map_data = new Map_Data($countries->rows()); |
| 58 | $minimum_items_per_list = 10; |
| 59 | $lists = []; |
| 60 | $tables = [new Table_Pages($this->get_group_preference('views')), new Table_Referrers($this->get_group_preference('referrers')), new Table_Devices($this->get_group_preference('devices')), new Table_Campaigns($this->get_group_preference('campaigns', 'utm_campaign')), new Table_Geo($this->get_group_preference('geo'))]; |
| 61 | foreach ($tables as $table) { |
| 62 | $group = $table->group(); |
| 63 | $rows_class = $group->rows_class(); |
| 64 | $rows = new $rows_class($range, $table->sanitize_sort_parameters('visitors')); |
| 65 | $list_items = []; |
| 66 | if ($this->examiner_config) { |
| 67 | if ($table->id() === $this->examiner_config->type()) { |
| 68 | $rows->limit_to($this->examiner_config->id()); |
| 69 | } elseif ($table->id() !== $this->examiner_config->type()) { |
| 70 | $rows->for_examiner($this->examiner_config); |
| 71 | } |
| 72 | } |
| 73 | foreach ($rows->rows() as $index => $row) { |
| 74 | $title_column = $group->title_column(); |
| 75 | $icon = null; |
| 76 | if ($table->id() === 'geo') { |
| 77 | // Can this be on a row insteade |
| 78 | $icon = Icon_Directory_Factory::flags()->find($row->country_code()); |
| 79 | } |
| 80 | $list_items[] = ['id' => $row->id(), 'position' => $index + 1, 'group' => $group->singular(), 'title' => $row->{$title_column}(), 'visitors' => $row->visitors(), 'icon' => $icon]; |
| 81 | } |
| 82 | $groups = Collection::make($table->groups()->groups())->filter(function (Group $group) { |
| 83 | if ($group->id() === 'campaign') { |
| 84 | return \false; |
| 85 | } |
| 86 | return \true; |
| 87 | })->map(function (Group $group) { |
| 88 | return [$group->id(), $group->singular()]; |
| 89 | })->all(); |
| 90 | $lists[$table->id()] = ['id' => $table->id(), 'title' => $table->name(), 'group_id' => $group->id(), 'group_title' => $group->singular(), 'groups' => $groups, 'count' => \number_format_i18n(\count($list_items)), 'column' => \__('Visitors', 'independent-analytics'), 'entries' => $this->fill_rows_with_blanks($list_items, $minimum_items_per_list), 'icon' => \IAWPSCOPED\iawp_render('icons.' . $table->id()), 'empty' => \__('No data found', 'independent-analytics')]; |
| 91 | } |
| 92 | return ['visitors' => $visitors, 'starting_at' => $this->starting_at->format('Uv'), 'country_data' => $map_data->get_country_data(), 'chart_data' => $chart_data, 'chart_minutes' => $chart_minutes, 'lists' => $lists]; |
| 93 | } |
| 94 | public function get_group_preference(string $table_id, ?string $default = null) : ?string |
| 95 | { |
| 96 | $groups = \get_user_meta(\get_current_user_id(), 'iawp_real_time_groups', \true); |
| 97 | if (!\is_array($groups) || !\array_key_exists($table_id, $groups)) { |
| 98 | return $default; |
| 99 | } |
| 100 | return $groups[$table_id]; |
| 101 | } |
| 102 | public function render_real_time_analytics() |
| 103 | { |
| 104 | echo \IAWPSCOPED\iawp_render('real-time', $this->get_real_time_analytics()); |
| 105 | } |
| 106 | private function get_chart_minutes_preference() : int |
| 107 | { |
| 108 | $chart_minutes = (int) \get_user_meta(\get_current_user_id(), 'iawp_real_time_chart_minutes', \true); |
| 109 | if (!\in_array($chart_minutes, self::CHART_MINUTES, \true)) { |
| 110 | return 5; |
| 111 | } |
| 112 | return $chart_minutes; |
| 113 | } |
| 114 | private function fill_rows_with_blanks(array $rows, $items_per_list) : array |
| 115 | { |
| 116 | $number_of_rows = \count($rows); |
| 117 | $blanks_to_add = $items_per_list - $number_of_rows; |
| 118 | // Nothing to add |
| 119 | if ($blanks_to_add < 1) { |
| 120 | return $rows; |
| 121 | } |
| 122 | for ($i = 0; $i < $blanks_to_add; $i++) { |
| 123 | $position = $number_of_rows + $i + 1; |
| 124 | $rows[] = ['id' => 'blank-' . $position, 'title' => '', 'position' => $position, 'visitors' => '', 'blank' => \true]; |
| 125 | } |
| 126 | return $rows; |
| 127 | } |
| 128 | } |
| 129 |