PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.10.4
Independent Analytics – WordPress Analytics Plugin v2.10.4
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 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 Click_Tracking 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 1 year ago Form_Submissions 1 year ago Interval 1 year ago Menu_Bar_Stats 1 year ago Migrations 1 year ago Models 1 year ago Public_API 1 year ago Rows 1 year ago Statistics 1 year ago Tables 1 year ago Utils 1 year ago Campaign_Builder.php 1 year ago Capability_Manager.php 1 year ago Chart.php 1 year ago Chart_Geo.php 1 year ago Click_Tracking.php 1 year ago Cron_Job.php 1 year ago Cron_Manager.php 1 year ago Current_Traffic_Finder.php 1 year ago Dashboard_Options.php 1 year ago Dashboard_Widget.php 2 years ago Database.php 1 year 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 MainWP.php 1 year ago Patch.php 1 year ago Payload_Validator.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 1 year ago REST_API.php 1 year ago Real_Time.php 1 year ago Report.php 1 year ago Report_Finder.php 1 year ago Report_Options_Parser.php 2 years ago Resource_Identifier.php 1 year ago Settings.php 1 year ago Sort_Configuration.php 1 year ago Tables.php 1 year ago Track_Resource_Changes.php 1 year ago View.php 1 year ago View_Counter.php 1 year ago Views_Over_Time_Finder.php 1 year ago WP_Option_Cache_Bust.php 2 years ago
View.php
339 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\Page_Home;
8 use IAWP\Models\Page_Singular;
9 use IAWP\Models\Visitor;
10 use IAWP\Utils\Device;
11 use IAWP\Utils\String_Util;
12 use IAWP\Utils\URL;
13 use IAWPSCOPED\Illuminate\Database\Query\JoinClause;
14 /** @internal */
15 class View
16 {
17 private $payload;
18 private $referrer_url;
19 private $visitor;
20 private $campaign_fields;
21 private $viewed_at;
22 private $resource;
23 private $session;
24 /**
25 * @param array $payload
26 * @param string|null $referrer_url
27 * @param Visitor $visitor
28 * @param array $campaign_fields
29 * @param \DateTime|null $viewed_at
30 */
31 public function __construct(array $payload, ?string $referrer_url, Visitor $visitor, array $campaign_fields, ?\DateTime $viewed_at = null)
32 {
33 $this->payload = $payload;
34 $this->referrer_url = \is_null($referrer_url) ? '' : \trim($referrer_url);
35 $this->visitor = $visitor;
36 $this->campaign_fields = $campaign_fields;
37 $this->viewed_at = $viewed_at instanceof \DateTime ? $viewed_at : new \DateTime();
38 $this->resource = $this->fetch_or_create_resource();
39 // If a resource can't be found or created, a view cannot be recorded
40 if (\is_null($this->resource)) {
41 return;
42 }
43 $this->session = $this->fetch_or_create_session();
44 $view_id = $this->create_view();
45 $this->link_with_previous_view($view_id);
46 $this->set_session_total_views();
47 $this->set_sessions_initial_view($view_id);
48 $this->set_sessions_final_view($view_id);
49 $this->set_views_postmeta($this->resource);
50 }
51 /**
52 * @return int ID of newly created session
53 */
54 public function create_session() : int
55 {
56 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
57 return \IAWP\Illuminate_Builder::new()->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()]);
58 }
59 public function fetch_or_create_country() : ?int
60 {
61 if (!$this->visitor->geoposition()->valid_location()) {
62 return null;
63 }
64 $countries_table = \IAWP\Query::get_table_name(\IAWP\Query::COUNTRIES);
65 $country_id = \IAWP\Illuminate_Builder::new()->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');
66 if (!\is_null($country_id)) {
67 return $country_id;
68 }
69 \IAWP\Illuminate_Builder::new()->from($countries_table)->insertOrIgnore(['country_code' => $this->visitor->geoposition()->country_code(), 'country' => $this->visitor->geoposition()->country(), 'continent' => $this->visitor->geoposition()->continent()]);
70 return \IAWP\Illuminate_Builder::new()->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');
71 }
72 public function fetch_or_create_city() : ?int
73 {
74 if (!$this->visitor->geoposition()->valid_location()) {
75 return null;
76 }
77 $country_id = $this->fetch_or_create_country();
78 $cities_table = \IAWP\Query::get_table_name(\IAWP\Query::CITIES);
79 $city_id = \IAWP\Illuminate_Builder::new()->from($cities_table)->where('country_id', $country_id)->where('subdivision', '=', $this->visitor->geoposition()->subdivision())->where('city', '=', $this->visitor->geoposition()->city())->value('city_id');
80 if (!\is_null($city_id)) {
81 return $city_id;
82 }
83 \IAWP\Illuminate_Builder::new()->from($cities_table)->insertOrIgnore(['country_id' => $country_id, 'subdivision' => $this->visitor->geoposition()->subdivision(), 'city' => $this->visitor->geoposition()->city()]);
84 return \IAWP\Illuminate_Builder::new()->from($cities_table)->where('country_id', $country_id)->where('subdivision', '=', $this->visitor->geoposition()->subdivision())->where('city', '=', $this->visitor->geoposition()->city())->value('city_id');
85 }
86 /**
87 * Fetch the last view, if any.
88 *
89 * @return int|null
90 */
91 private function fetch_last_viewed_resource() : ?int
92 {
93 global $wpdb;
94 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
95 $session = $this->fetch_current_session();
96 if (\is_null($session)) {
97 return null;
98 }
99 $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));
100 if (\is_null($view)) {
101 return null;
102 }
103 return $view->resource_id;
104 }
105 private function viewed_at() : string
106 {
107 return $this->viewed_at->format('Y-m-d\\TH:i:s');
108 }
109 private function link_with_previous_view($view_id) : void
110 {
111 global $wpdb;
112 $views_tables = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
113 $sessions_tables = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
114 $session = \IAWP\Illuminate_Builder::new()->from($sessions_tables)->where('session_id', '=', $this->session)->first();
115 if (\is_null($session)) {
116 return;
117 }
118 $final_view_id = $session->final_view_id;
119 $initial_view_id = $session->initial_view_id;
120 if (!\is_null($final_view_id)) {
121 $wpdb->update($views_tables, ['next_view_id' => $view_id, 'next_viewed_at' => $this->viewed_at()], ['id' => $final_view_id]);
122 } elseif (!\is_null($initial_view_id)) {
123 $wpdb->update($views_tables, ['next_view_id' => $view_id, 'next_viewed_at' => $this->viewed_at()], ['id' => $initial_view_id]);
124 }
125 }
126 private function set_session_total_views()
127 {
128 global $wpdb;
129 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
130 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
131 $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));
132 }
133 private function set_sessions_initial_view(int $view_id)
134 {
135 global $wpdb;
136 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
137 $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));
138 }
139 private function set_sessions_final_view(int $view_id)
140 {
141 global $wpdb;
142 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
143 $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));
144 }
145 private function create_view() : int
146 {
147 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
148 return \IAWP\Illuminate_Builder::new()->from($views_table)->insertGetId(['resource_id' => $this->resource->id(), 'viewed_at' => $this->viewed_at(), 'page' => $this->payload['page'], 'session_id' => $this->session]);
149 }
150 private function fetch_resource()
151 {
152 global $wpdb;
153 $resources_table = \IAWP\Query::get_table_name(\IAWP\Query::RESOURCES);
154 $query = '';
155 $payload_copy = \array_merge($this->payload);
156 unset($payload_copy['page']);
157 switch ($payload_copy['resource']) {
158 case 'singular':
159 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND singular_id = %d", $payload_copy['resource'], $payload_copy['singular_id']);
160 break;
161 case 'author_archive':
162 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND author_id = %d", $payload_copy['resource'], $payload_copy['author_id']);
163 break;
164 case 'date_archive':
165 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND date_archive = %s", $payload_copy['resource'], $payload_copy['date_archive']);
166 break;
167 case 'post_type_archive':
168 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND post_type = %s", $payload_copy['resource'], $payload_copy['post_type']);
169 break;
170 case 'term_archive':
171 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND term_id = %s", $payload_copy['resource'], $payload_copy['term_id']);
172 break;
173 case 'search':
174 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND search_query = %s", $payload_copy['resource'], $payload_copy['search_query']);
175 break;
176 case 'home':
177 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s ", $payload_copy['resource']);
178 break;
179 case '404':
180 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND not_found_url = %s", $payload_copy['resource'], $payload_copy['not_found_url']);
181 break;
182 case 'virtual_page':
183 $query = $wpdb->prepare("SELECT * FROM {$resources_table} WHERE resource = %s AND virtual_page_id = %s", $payload_copy['resource'], $payload_copy['virtual_page_id']);
184 break;
185 }
186 $resource = $wpdb->get_row($query);
187 if (\is_null($resource)) {
188 return null;
189 }
190 return $resource;
191 }
192 private function fetch_or_create_resource() : ?Page
193 {
194 global $wpdb;
195 $resources_table = \IAWP\Query::get_table_name(\IAWP\Query::RESOURCES);
196 $resource = $this->fetch_resource();
197 if (\is_null($resource)) {
198 $payload_copy = \array_merge($this->payload);
199 unset($payload_copy['page']);
200 $wpdb->insert($resources_table, $payload_copy);
201 $resource = $this->fetch_resource();
202 }
203 if (\is_null($resource)) {
204 return null;
205 }
206 $page = Page::from_row($resource);
207 $page->update_cache();
208 return $page;
209 }
210 /**
211 * @return int|null ID of the session that should be used for this view
212 */
213 private function fetch_or_create_session() : ?int
214 {
215 $session = $this->fetch_current_session();
216 if (\is_null($session)) {
217 return $this->create_session();
218 }
219 $is_same_referrer = $this->fetch_or_create_referrer() === $session->referrer_id;
220 $is_same_resource = \intval($this->fetch_resource()->id) === $this->fetch_last_viewed_resource();
221 $same_as_previous_view = $is_same_referrer && $is_same_resource;
222 // The goal here is to prevent opening multiple tabs to the site from creating multiple sessions
223 if ($is_same_referrer) {
224 return $session->session_id;
225 }
226 // The goal here is to prevent a page refresh from creating another session
227 if ($this->is_internal_referrer($this->referrer_url) || $same_as_previous_view) {
228 return $session->session_id;
229 }
230 return $this->create_session();
231 }
232 /**
233 * @param string|null $referrer_url
234 *
235 * @return bool
236 */
237 private function is_internal_referrer(?string $referrer_url) : bool
238 {
239 return !empty($referrer_url) && String_Util::str_starts_with(\strtolower($referrer_url), \strtolower(\site_url()));
240 }
241 private function fetch_referrer(array $referrer) : int
242 {
243 $referrers_table = \IAWP\Query::get_table_name(\IAWP\Query::REFERRERS);
244 $id = \IAWP\Illuminate_Builder::new()->select('id')->from($referrers_table)->where('domain', '=', $referrer['domain'])->value('id');
245 if (\is_null($id)) {
246 $id = \IAWP\Illuminate_Builder::new()->from($referrers_table)->insertGetId(['domain' => $referrer['domain'], 'type' => $referrer['type'], 'referrer' => $referrer['referrer']]);
247 }
248 return $id;
249 }
250 private function fetch_or_create_referrer() : int
251 {
252 $url = new URL($this->referrer_url);
253 if (!$url->is_valid_url() || $this->is_internal_referrer($this->referrer_url)) {
254 return $this->fetch_referrer(['domain' => '', 'type' => 'Direct', 'referrer' => 'Direct']);
255 } elseif (!\is_null(\IAWP\Known_Referrers::get_group_for($url->get_domain()))) {
256 $group = \IAWP\Known_Referrers::get_group_for($url->get_domain());
257 return $this->fetch_referrer(['domain' => $group['domain'], 'type' => $group['type'], 'referrer' => $group['name']]);
258 } else {
259 return $this->fetch_referrer(['domain' => $url->get_domain(), 'type' => 'Referrer', 'referrer' => $this->strip_www($url->get_domain())]);
260 }
261 }
262 private function strip_www(string $string) : string
263 {
264 if (\strpos($string, "www.") !== 0) {
265 return $string;
266 }
267 return \substr($string, 4);
268 }
269 private function get_campaign() : ?int
270 {
271 global $wpdb;
272 $required_fields = ['utm_source', 'utm_medium', 'utm_campaign'];
273 $valid = \true;
274 foreach ($required_fields as $field) {
275 if (!isset($this->campaign_fields[$field])) {
276 $valid = \false;
277 }
278 }
279 if (!$valid) {
280 return null;
281 }
282 $campaigns_table = \IAWP\Query::get_table_name(\IAWP\Query::CAMPAIGNS);
283 $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));
284 if (!\is_null($campaign)) {
285 return $campaign->campaign_id;
286 }
287 $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']]);
288 $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));
289 if (!\is_null($campaign)) {
290 return $campaign->campaign_id;
291 }
292 return null;
293 }
294 private function fetch_current_session() : ?object
295 {
296 $sessions_table = \IAWP\Query::get_table_name(\IAWP\Query::SESSIONS);
297 $session = \IAWP\Illuminate_Builder::new()->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();
298 return $session;
299 }
300 private function set_views_postmeta(Page $resource) : void
301 {
302 if ($resource instanceof Page_Singular) {
303 $this->set_views_postmeta_for_singular($resource);
304 } elseif ($resource instanceof Page_Home) {
305 $this->set_views_postmeta_for_home($resource);
306 }
307 }
308 private function set_views_postmeta_for_singular(Page_Singular $resource) : void
309 {
310 $singular_id = $resource->get_singular_id();
311 if ($singular_id === null) {
312 return;
313 }
314 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
315 $resources_table = \IAWP\Query::get_table_name(\IAWP\Query::RESOURCES);
316 $total_views = \IAWP\Illuminate_Builder::new()->selectRaw('COUNT(*) AS views')->from("{$resources_table} as resources")->join("{$views_table} AS views", function (JoinClause $join) {
317 $join->on('resources.id', '=', 'views.resource_id');
318 })->where('singular_id', '=', $singular_id)->value('views');
319 \update_post_meta($singular_id, Views_Column::$meta_key, $total_views);
320 }
321 private function set_views_postmeta_for_home(Page_Home $resource) : void
322 {
323 $blog_page_id = \get_option('page_for_posts');
324 if (\is_string($blog_page_id) && \ctype_digit($blog_page_id)) {
325 $blog_page_id = \intval($blog_page_id);
326 }
327 $blog_page = \get_post($blog_page_id);
328 if ($blog_page === null) {
329 return;
330 }
331 $views_table = \IAWP\Query::get_table_name(\IAWP\Query::VIEWS);
332 $resources_table = \IAWP\Query::get_table_name(\IAWP\Query::RESOURCES);
333 $total_views = \IAWP\Illuminate_Builder::new()->selectRaw('COUNT(*) AS views')->from("{$resources_table} as resources")->join("{$views_table} AS views", function (JoinClause $join) {
334 $join->on('resources.id', '=', 'views.resource_id');
335 })->where('resource', '=', 'home')->value('views');
336 \update_post_meta($blog_page_id, Views_Column::$meta_key, $total_views);
337 }
338 }
339