PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.8.4
Independent Analytics – WordPress Analytics Plugin v2.8.4
2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / IAWP / View.php
independent-analytics / IAWP Last commit date
AJAX 1 year ago Admin_Page 1 year ago Custom_WordPress_Columns 2 years ago Data_Pruning 1 year ago Date_Picker 1 year ago Date_Range 1 year ago Ecommerce 1 year ago Email_Reports 1 year ago Filter_Lists 2 years ago Form_Submissions 1 year ago Interval 2 years ago Menu_Bar_Stats 1 year ago Migrations 1 year ago Models 1 year ago Public_API 2 years ago Rows 1 year ago Statistics 1 year ago Tables 1 year ago Utils 1 year ago Campaign_Builder.php 2 years ago Capability_Manager.php 1 year ago Chart.php 1 year ago Chart_Geo.php 1 year ago Cron_Job.php 1 year ago Cron_Job_Autoloader.php 1 year ago Cron_Manager.php 2 years ago Current_Traffic_Finder.php 2 years ago Dashboard_Options.php 2 years ago Dashboard_Widget.php 2 years ago Database.php 2 years ago Database_Manager.php 2 years ago Empty_Report_Option.php 2 years ago Env.php 1 year ago Filters.php 1 year ago Geo_Database_Background_Job.php 2 years ago Geo_Database_Manager.php 1 year ago Geoposition.php 2 years ago Icon_Directory.php 2 years ago Icon_Directory_Factory.php 2 years ago Illuminate_Builder.php 1 year ago Independent_Analytics.php 1 year ago Interrupt.php 1 year ago Known_Referrers.php 2 years ago Patch.php 1 year ago Plugin_Conflict_Detector.php 1 year ago Plugin_Group.php 1 year ago Plugin_Group_Option.php 2 years ago Query.php 1 year ago Query_Taps.php 1 year ago Quick_Stats.php 2 years ago REST_API.php 1 year ago Real_Time.php 1 year ago Report.php 2 years ago Report_Finder.php 2 years ago Report_Options_Parser.php 1 year ago Resource_Identifier.php 1 year ago Settings.php 1 year ago Sort_Configuration.php 2 years ago Track_Resource_Changes.php 2 years ago View.php 2 years ago View_Counter.php 1 year ago Visitors_Over_Time_Finder.php 2 years ago WP_Option_Cache_Bust.php 2 years ago
View.php
312 lines
1 <?php
2
3 namespace IAWP;
4
5 use IAWP\Custom_WordPress_Columns\Views_Column;
6 use IAWP\Models\Page;
7 use IAWP\Models\Visitor;
8 use IAWP\Utils\Device;
9 use IAWP\Utils\String_Util;
10 use IAWP\Utils\URL;
11 use IAWPSCOPED\Illuminate\Database\Query\JoinClause;
12 /** @internal */
13 class View
14 {
15 private $payload;
16 private $referrer_url;
17 private $visitor;
18 private $campaign_fields;
19 private $viewed_at;
20 private $resource;
21 private $session;
22 /**
23 * @param array $payload
24 * @param string|null $referrer_url
25 * @param Visitor $visitor
26 * @param array $campaign_fields
27 * @param \DateTime|null $viewed_at
28 */
29 public function __construct(array $payload, ?string $referrer_url, Visitor $visitor, array $campaign_fields, ?\DateTime $viewed_at = null)
30 {
31 $this->payload = $payload;
32 $this->referrer_url = \is_null($referrer_url) ? '' : \trim($referrer_url);
33 $this->visitor = $visitor;
34 $this->campaign_fields = $campaign_fields;
35 $this->viewed_at = $viewed_at instanceof \DateTime ? $viewed_at : new \DateTime();
36 $this->resource = $this->fetch_or_create_resource();
37 // If a resource can't be found or created, a view cannot be recorded
38 if (\is_null($this->resource)) {
39 return;
40 }
41 $this->session = $this->fetch_or_create_session();
42 $view_id = $this->create_view();
43 $this->link_with_previous_view($view_id);
44 $this->set_session_total_views();
45 $this->set_sessions_initial_view($view_id);
46 $this->set_sessions_final_view($view_id);
47 $this->update_postmeta($this->resource);
48 }
49 /**
50 * @return int ID of newly created session
51 */
52 public function create_session() : int
53 {
54 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
55 return \IAWP\Illuminate_Builder::get_builder()->from($sessions_table)->insertGetId(['visitor_id' => $this->visitor->id(), 'referrer_id' => $this->fetch_or_create_referrer(), 'country_id' => $this->fetch_or_create_country(), 'city_id' => $this->fetch_or_create_city(), 'campaign_id' => $this->get_campaign(), 'device_type_id' => Device::getInstance()->type_id(), 'device_os_id' => Device::getInstance()->os_id(), 'device_browser_id' => Device::getInstance()->browser_id(), 'created_at' => $this->viewed_at()]);
56 }
57 public function fetch_or_create_country() : ?int
58 {
59 if (!$this->visitor->geoposition()->valid_location()) {
60 return null;
61 }
62 $countries_table = \IAWP\Query::get_table_name(\IAWP\Query::COUNTRIES);
63 $country_id = \IAWP\Illuminate_Builder::get_builder()->from($countries_table)->where('country_code', '=', $this->visitor->geoposition()->country_code())->where('country', '=', $this->visitor->geoposition()->country())->where('continent', '=', $this->visitor->geoposition()->continent())->value('country_id');
64 if (!\is_null($country_id)) {
65 return $country_id;
66 }
67 \IAWP\Illuminate_Builder::get_builder()->from($countries_table)->insertOrIgnore(['country_code' => $this->visitor->geoposition()->country_code(), 'country' => $this->visitor->geoposition()->country(), 'continent' => $this->visitor->geoposition()->continent()]);
68 return \IAWP\Illuminate_Builder::get_builder()->from($countries_table)->where('country_code', '=', $this->visitor->geoposition()->country_code())->where('country', '=', $this->visitor->geoposition()->country())->where('continent', '=', $this->visitor->geoposition()->continent())->value('country_id');
69 }
70 public function fetch_or_create_city() : ?int
71 {
72 if (!$this->visitor->geoposition()->valid_location()) {
73 return null;
74 }
75 $country_id = $this->fetch_or_create_country();
76 $cities_table = \IAWP\Query::get_table_name(\IAWP\Query::CITIES);
77 $city_id = \IAWP\Illuminate_Builder::get_builder()->from($cities_table)->where('country_id', $country_id)->where('subdivision', '=', $this->visitor->geoposition()->subdivision())->where('city', '=', $this->visitor->geoposition()->city())->value('city_id');
78 if (!\is_null($city_id)) {
79 return $city_id;
80 }
81 \IAWP\Illuminate_Builder::get_builder()->from($cities_table)->insertOrIgnore(['country_id' => $country_id, 'subdivision' => $this->visitor->geoposition()->subdivision(), 'city' => $this->visitor->geoposition()->city()]);
82 return \IAWP\Illuminate_Builder::get_builder()->from($cities_table)->where('country_id', $country_id)->where('subdivision', '=', $this->visitor->geoposition()->subdivision())->where('city', '=', $this->visitor->geoposition()->city())->value('city_id');
83 }
84 /**
85 * Fetch the last view, if any.
86 *
87 * @return int|null
88 */
89 private function fetch_last_viewed_resource() : ?int
90 {
91 global $wpdb;
92 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
93 $session = $this->fetch_current_session();
94 if (\is_null($session)) {
95 return null;
96 }
97 $view = $wpdb->get_row($wpdb->prepare("\n SELECT * FROM {$views_table} WHERE session_id = %d ORDER BY viewed_at DESC LIMIT 1\n ", $session->session_id));
98 if (\is_null($view)) {
99 return null;
100 }
101 return $view->resource_id;
102 }
103 private function viewed_at() : string
104 {
105 return $this->viewed_at->format('Y-m-d\\TH:i:s');
106 }
107 private function link_with_previous_view($view_id) : void
108 {
109 global $wpdb;
110 $views_tables = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
111 $sessions_tables = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
112 $session = \IAWP\Illuminate_Builder::get_builder()->from($sessions_tables)->where('session_id', '=', $this->session)->first();
113 if (\is_null($session)) {
114 return;
115 }
116 $final_view_id = $session->final_view_id;
117 $initial_view_id = $session->initial_view_id;
118 if (!\is_null($final_view_id)) {
119 $wpdb->update($views_tables, ['next_view_id' => $view_id, 'next_viewed_at' => $this->viewed_at()], ['id' => $final_view_id]);
120 } elseif (!\is_null($initial_view_id)) {
121 $wpdb->update($views_tables, ['next_view_id' => $view_id, 'next_viewed_at' => $this->viewed_at()], ['id' => $initial_view_id]);
122 }
123 }
124 private function set_session_total_views()
125 {
126 global $wpdb;
127 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
128 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
129 $wpdb->query($wpdb->prepare("\n UPDATE {$sessions_table} AS sessions\n LEFT JOIN (\n SELECT\n session_id,\n COUNT(*) AS view_count\n FROM\n {$views_table} AS views\n WHERE\n views.session_id = %d\n GROUP BY\n session_id) AS view_counts ON sessions.session_id = view_counts.session_id\n SET\n sessions.total_views = COALESCE(view_counts.view_count, 0)\n WHERE sessions.session_id = %d\n ", $this->session, $this->session));
130 }
131 private function set_sessions_initial_view(int $view_id)
132 {
133 global $wpdb;
134 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
135 $wpdb->query($wpdb->prepare("UPDATE {$sessions_table} SET initial_view_id = %d WHERE session_id = %d AND initial_view_id IS NULL", $view_id, $this->session));
136 }
137 private function set_sessions_final_view(int $view_id)
138 {
139 global $wpdb;
140 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
141 $wpdb->query($wpdb->prepare("\n UPDATE {$sessions_table} AS sessions\n SET\n sessions.final_view_id = %d,\n sessions.ended_at = %s\n WHERE sessions.session_id = %d AND sessions.initial_view_id IS NOT NULL AND sessions.initial_view_id != %d\n ", $view_id, $this->viewed_at(), $this->session, $view_id));
142 }
143 private function create_view() : int
144 {
145 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
146 return \IAWP\Illuminate_Builder::get_builder()->from($views_table)->insertGetId(['resource_id' => $this->resource->id(), 'viewed_at' => $this->viewed_at(), 'page' => $this->payload['page'], 'session_id' => $this->session]);
147 }
148 private function fetch_resource()
149 {
150 global $wpdb;
151 $resources_table = \IAWP\Query::get_table_name(\IAWP\Query::RESOURCES);
152 $query = '';
153 $payload_copy = \array_merge($this->payload);
154 unset($payload_copy['page']);
155 switch ($payload_copy['resource']) {
156 case 'singular':
157 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND singular_id = %d", $payload_copy['resource'], $payload_copy['singular_id']);
158 break;
159 case 'author_archive':
160 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND author_id = %d", $payload_copy['resource'], $payload_copy['author_id']);
161 break;
162 case 'date_archive':
163 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND date_archive = %s", $payload_copy['resource'], $payload_copy['date_archive']);
164 break;
165 case 'post_type_archive':
166 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND post_type = %s", $payload_copy['resource'], $payload_copy['post_type']);
167 break;
168 case 'term_archive':
169 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND term_id = %s", $payload_copy['resource'], $payload_copy['term_id']);
170 break;
171 case 'search':
172 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND search_query = %s", $payload_copy['resource'], $payload_copy['search_query']);
173 break;
174 case 'home':
175 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s ", $payload_copy['resource']);
176 break;
177 case '404':
178 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND not_found_url = %s", $payload_copy['resource'], $payload_copy['not_found_url']);
179 break;
180 case 'virtual_page':
181 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND virtual_page_id = %s", $payload_copy['resource'], $payload_copy['virtual_page_id']);
182 break;
183 }
184 $resource = $wpdb->get_row($query);
185 if (\is_null($resource)) {
186 return null;
187 }
188 return $resource;
189 }
190 private function fetch_or_create_resource() : ?Page
191 {
192 global $wpdb;
193 $resources_table = \IAWP\Query::get_table_name(\IAWP\Query::RESOURCES);
194 $resource = $this->fetch_resource();
195 if (\is_null($resource)) {
196 $payload_copy = \array_merge($this->payload);
197 unset($payload_copy['page']);
198 $wpdb->insert($resources_table, $payload_copy);
199 $resource = $this->fetch_resource();
200 }
201 if (\is_null($resource)) {
202 return null;
203 }
204 $page = Page::from_row($resource);
205 $page->update_cache();
206 return $page;
207 }
208 /**
209 * @return int|null ID of the session that should be used for this view
210 */
211 private function fetch_or_create_session() : ?int
212 {
213 $session = $this->fetch_current_session();
214 if (\is_null($session)) {
215 return $this->create_session();
216 }
217 $is_same_referrer = $this->fetch_or_create_referrer() === $session->referrer_id;
218 $is_same_resource = \intval($this->fetch_resource()->id) === $this->fetch_last_viewed_resource();
219 $same_as_previous_view = $is_same_referrer && $is_same_resource;
220 // The goal here is to prevent opening multiple tabs to the site from creating multiple sessions
221 if ($is_same_referrer) {
222 return $session->session_id;
223 }
224 // The goal here is to prevent a page refresh from creating another session
225 if ($this->is_internal_referrer($this->referrer_url) || $same_as_previous_view) {
226 return $session->session_id;
227 }
228 return $this->create_session();
229 }
230 /**
231 * @param string|null $referrer_url
232 *
233 * @return bool
234 */
235 private function is_internal_referrer(?string $referrer_url) : bool
236 {
237 return !empty($referrer_url) && String_Util::str_starts_with(\strtolower($referrer_url), \strtolower(\site_url()));
238 }
239 private function fetch_referrer(array $referrer) : int
240 {
241 $referrers_table = \IAWP\Query::get_table_name(\IAWP\Query::REFERRERS);
242 $id = \IAWP\Illuminate_Builder::get_builder()->select('id')->from($referrers_table)->where('domain', '=', $referrer['domain'])->value('id');
243 if (\is_null($id)) {
244 $id = \IAWP\Illuminate_Builder::get_builder()->from($referrers_table)->insertGetId(['domain' => $referrer['domain'], 'type' => $referrer['type'], 'referrer' => $referrer['referrer']]);
245 }
246 return $id;
247 }
248 private function fetch_or_create_referrer() : int
249 {
250 $url = new URL($this->referrer_url);
251 if (!$url->is_valid_url() || $this->is_internal_referrer($this->referrer_url)) {
252 return $this->fetch_referrer(['domain' => '', 'type' => 'Direct', 'referrer' => 'Direct']);
253 } elseif (!\is_null(\IAWP\Known_Referrers::get_group_for($url->get_domain()))) {
254 $group = \IAWP\Known_Referrers::get_group_for($url->get_domain());
255 return $this->fetch_referrer(['domain' => $group['domain'], 'type' => $group['type'], 'referrer' => $group['name']]);
256 } else {
257 return $this->fetch_referrer(['domain' => $url->get_domain(), 'type' => 'Referrer', 'referrer' => $this->strip_www($url->get_domain())]);
258 }
259 }
260 private function strip_www(string $string) : string
261 {
262 if (\strpos($string, "www.") !== 0) {
263 return $string;
264 }
265 return \substr($string, 4);
266 }
267 private function get_campaign() : ?int
268 {
269 global $wpdb;
270 $required_fields = ['utm_source', 'utm_medium', 'utm_campaign'];
271 $valid = \true;
272 foreach ($required_fields as $field) {
273 if (!isset($this->campaign_fields[$field])) {
274 $valid = \false;
275 }
276 }
277 if (!$valid) {
278 return null;
279 }
280 $campaigns_table = \IAWP\Query::get_table_name(\IAWP\Query::CAMPAIGNS);
281 $campaign = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$campaigns_table} WHERE landing_page_title = %s AND utm_source = %s AND utm_medium = %s AND utm_campaign = %s AND (utm_term = %s OR (%d = 0 AND utm_term IS NULL)) AND (utm_content = %s OR (%d = 0 AND utm_content IS NULL))", $this->resource->title(), $this->campaign_fields['utm_source'], $this->campaign_fields['utm_medium'], $this->campaign_fields['utm_campaign'], $this->campaign_fields['utm_term'], isset($this->campaign_fields['utm_term']) ? 1 : 0, $this->campaign_fields['utm_content'], isset($this->campaign_fields['utm_content']) ? 1 : 0));
282 if (!\is_null($campaign)) {
283 return $campaign->campaign_id;
284 }
285 $wpdb->insert($campaigns_table, ['landing_page_title' => $this->resource->title(), 'utm_source' => $this->campaign_fields['utm_source'], 'utm_medium' => $this->campaign_fields['utm_medium'], 'utm_campaign' => $this->campaign_fields['utm_campaign'], 'utm_term' => $this->campaign_fields['utm_term'], 'utm_content' => $this->campaign_fields['utm_content']]);
286 $campaign = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$campaigns_table} WHERE landing_page_title = %s AND utm_source = %s AND utm_medium = %s AND utm_campaign = %s AND (utm_term = %s OR (%d = 0 AND utm_term IS NULL)) AND (utm_content = %s OR (%d = 0 AND utm_content IS NULL))", $this->resource->title(), $this->campaign_fields['utm_source'], $this->campaign_fields['utm_medium'], $this->campaign_fields['utm_campaign'], $this->campaign_fields['utm_term'], isset($this->campaign_fields['utm_term']) ? 1 : 0, $this->campaign_fields['utm_content'], isset($this->campaign_fields['utm_content']) ? 1 : 0));
287 if (!\is_null($campaign)) {
288 return $campaign->campaign_id;
289 }
290 return null;
291 }
292 private function fetch_current_session() : ?object
293 {
294 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
295 $session = \IAWP\Illuminate_Builder::get_builder()->from($sessions_table, 'sessions')->selectRaw('IFNULL(ended_at, created_at) AS latest_view_at')->selectRaw('sessions.*')->where('visitor_id', '=', $this->visitor->id())->havingRaw('latest_view_at > DATE_SUB(UTC_TIMESTAMP(), INTERVAL 30 MINUTE)')->orderBy('latest_view_at', 'DESC')->first();
296 return $session;
297 }
298 private function update_postmeta(Page $resource) : void
299 {
300 $singular_id = $resource->get_singular_id();
301 if ($singular_id === null) {
302 return;
303 }
304 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
305 $resources_table = \IAWP\Query::get_table_name(\IAWP\Query::RESOURCES);
306 $total_views = \IAWP\Illuminate_Builder::get_builder()->selectRaw('COUNT(*) AS views')->from("{$resources_table} as resources")->join("{$views_table} AS views", function (JoinClause $join) {
307 $join->on('resources.id', '=', 'views.resource_id');
308 })->where('singular_id', '=', $singular_id)->value('views');
309 \update_post_meta($singular_id, Views_Column::$meta_key, $total_views);
310 }
311 }
312