PluginProbe
Statify / trunk
Statify vtrunk
2.0.2 2.0.1 trunk 0.7 0.8 0.9 1.0 1.2.8 1.3.0 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 All 32 releases
statify / inc / class-statify.php

class-statify.php in Statify trunk, at inc/class-statify.php

490 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Statify: Statify class
4 *
5 * This file contains the plugin's base class.
6 *
7 * @package Statify
8 * @since 0.1.0
9 */
10
11 // Quit if accessed outside WP context.
12 defined( 'ABSPATH' ) || exit;
13
14 /**
15 * Statify.
16 *
17 * @since 0.1.0
18 */
19 class Statify {
20 const TRACKING_METHOD_DEFAULT = 0;
21 const TRACKING_METHOD_JAVASCRIPT_WITH_NONCE_CHECK = 1;
22 const TRACKING_METHOD_JAVASCRIPT_WITHOUT_NONCE_CHECK = 2;
23
24 const SKIP_USERS_NONE = 0;
25 const SKIP_USERS_ALL = 1;
26 const SKIP_USERS_ADMIN = 2;
27
28 /**
29 * Plugin options.
30 *
31 * @since 1.4.0
32 * @since 2.0.0 removed underscore from variable name.
33 * @var array $options
34 */
35 public static $options;
36
37 /**
38 * Plugin version.
39 *
40 * @var string|null $plugin_version
41 */
42 private static $plugin_version;
43
44 /**
45 * Plugin initialization.
46 *
47 * @since 1.7 Replaces previously used instance() and __construct().
48 */
49 public static function init(): void {
50 // Nothing to do on autosave.
51 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
52 return;
53 }
54
55 // Table init.
56 Statify_Table::init();
57
58 // Plugin options.
59 self::$options = wp_parse_args(
60 get_option( 'statify' ),
61 array(
62 'days' => 14,
63 'days_show' => 14,
64 'limit' => 3,
65 'today' => 0,
66 'snippet' => 0,
67 'blacklist' => 0,
68 'show_totals' => 0,
69 'show_widget_roles' => null, // Just for documentation, the default is calculated later.
70 'skip' => array(
71 'logged_in' => self::SKIP_USERS_ALL,
72 ),
73 )
74 );
75
76 // Cron.
77 add_action( 'statify_cleanup', array( 'Statify_Cron', 'cleanup_data' ) );
78
79 if ( is_admin() ) { // Backend.
80 add_action( 'wp_initialize_site', array( 'Statify_Install', 'init_site' ) );
81 add_action( 'wp_uninitialize_site', array( 'Statify_Uninstall', 'init_site' ) );
82 add_action( 'wp_dashboard_setup', array( 'Statify_Dashboard', 'init' ) );
83 add_filter( 'plugin_row_meta', array( 'Statify_Backend', 'add_meta_link' ), 10, 2 );
84 add_filter( 'plugin_action_links_' . STATIFY_BASE, array( 'Statify_Backend', 'add_action_link' ) );
85 add_action( 'admin_init', array( 'Statify_Settings', 'register_settings' ) );
86 add_action( 'admin_menu', array( 'Statify_Settings', 'add_admin_menu' ) );
87 add_action( 'admin_init', array( 'Statify_Evaluation', 'add_capability' ) );
88 add_action( 'admin_menu', array( 'Statify_Evaluation', 'add_menu' ) );
89 add_action( 'update_option_statify', array( 'Statify_Settings', 'action_update_options' ), 10, 2 );
90 } else { // Frontend.
91 add_action( 'template_redirect', array( 'Statify_Frontend', 'track_visit' ) );
92 add_filter( 'query_vars', array( 'Statify_Frontend', 'query_vars' ) );
93 add_action( 'wp_footer', array( 'Statify_Frontend', 'wp_footer' ) );
94 if ( function_exists( 'amp_is_request' ) || function_exists( 'is_amp_endpoint' ) ) {
95 // Automattic AMP plugin present.
96 add_filter( 'amp_analytics_entries', array( 'Statify_Frontend', 'amp_analytics_entries' ) );
97 add_filter( 'amp_post_template_analytics', array( 'Statify_Frontend', 'amp_post_template_analytics' ) );
98 }
99 // Initialize REST API.
100 add_action( 'rest_api_init', array( 'Statify_Api', 'init' ) );
101 }
102 }
103
104 /**
105 * Track the page view.
106 *
107 * @param string|null $referrer Referrer URL.
108 * @param string|null $target Target URL.
109 *
110 * @return void
111 *
112 * @since 0.1.0
113 * @since 1.7.0 $is_snippet parameter added.
114 * @since 2.0.0 Migration from Statify_Frontend::track_visit to Statify::track with multiple parameters.
115 */
116 protected static function track( ?string $referrer, ?string $target ): void {
117 // Fallbacks for uninitialized or omitted target and referrer values.
118 if ( is_null( $target ) ) {
119 $target = '/';
120 }
121 if ( is_null( $referrer ) ) {
122 $referrer = '';
123 }
124
125 // Invalid target?
126 $target = wp_validate_redirect( $target );
127 if ( empty( $target ) ) {
128 return;
129 }
130
131 // Check whether tracking should be skipped for this view.
132 if ( self::skip_tracking() ) {
133 return;
134 }
135
136 // Sanitize referrer url.
137 if ( ! empty( $referrer ) && false === self::strposa( $referrer, array( home_url(), network_admin_url() ) ) ) {
138 $referrer = esc_url_raw( $referrer, array( 'http', 'https' ) );
139 } else {
140 $referrer = '';
141 }
142
143 // Relative target URL.
144 $target = user_trailingslashit( str_replace( home_url( '/', 'relative' ), '/', $target ) );
145
146 /* Global vars */
147 global $wp_rewrite;
148
149 // Trim target URL.
150 if ( $wp_rewrite->permalink_structure ) {
151 $target = wp_parse_url( $target, PHP_URL_PATH );
152 }
153
154 // Init rows.
155 $data = array(
156 'created' => current_time( 'Y-m-d' ),
157 'referrer' => $referrer,
158 'target' => $target,
159 );
160
161 // Insert.
162 global $wpdb;
163 $wpdb->insert( $wpdb->statify, $data );
164
165 /**
166 * Fires after a visit was stored in the database
167 *
168 * @param array $data
169 * @param int $wpdb- >insert_id
170 *
171 * @since 1.5.5
172 */
173 do_action( 'statify__visit_saved', $data, $wpdb->insert_id );
174 }
175
176 /**
177 * Get a readable date from YYYY-MM-DD database date.
178 *
179 * This function is designed as a wrapper around date_i18n() or wp_date(), if the latter is available (#166).
180 *
181 * @param string $date Raw date in "YYYY-MM-DD" format.
182 *
183 * @return string Parsed date in WP default format.
184 *
185 * @since 1.7.3
186 */
187 public static function parse_date( string $date ): string {
188 if ( function_exists( 'wp_date' ) ) { // Exists since WP 5.3.
189 return wp_date( get_option( 'date_format' ), strtotime( $date ) );
190 }
191
192 return date_i18n( get_option( 'date_format' ), strtotime( $date ) );
193 }
194
195 /**
196 * Check JavaScript tracking.
197 *
198 * @return bool true if and only if one of the JavaScript tracking options is enabled.
199 */
200 public static function is_javascript_tracking_enabled(): bool {
201 return in_array(
202 self::$options['snippet'],
203 array(
204 self::TRACKING_METHOD_JAVASCRIPT_WITH_NONCE_CHECK,
205 self::TRACKING_METHOD_JAVASCRIPT_WITHOUT_NONCE_CHECK,
206 ),
207 true
208 );
209 }
210
211 /**
212 * Check whether the current user can see stats.
213 *
214 * @return boolean TRUE, if the user can see stats. FALSE otherwise.
215 *
216 * @since 2.0.0 extracted into method for reusability
217 *
218 * @hook boolean statify__user_can_see_stats
219 * @see https://wordpress.org/plugins/statify/
220 */
221 public static function user_can_see_stats(): bool {
222 if ( isset( self::$options['show_widget_roles'] ) ) {
223 $statify_roles = self::$options['show_widget_roles'];
224 $current_user = wp_get_current_user();
225 $user_roles = $current_user->roles;
226
227 // Filter user_can_see_stats.
228 $allowed_roles = array_intersect( $statify_roles, $user_roles );
229 $can_see = ! empty( $allowed_roles );
230 } else {
231 // Backwards compatibility for older statify versions without this option.
232 $can_see = current_user_can( 'edit_dashboard' );
233 }
234
235 // Filter user_can_see_stats.
236 return apply_filters( 'statify__user_can_see_stats', $can_see );
237 }
238
239 /**
240 * Print JavaScript.
241 *
242 * @since 2.0.0 Moved to Statify class
243 */
244 public static function add_js(): void {
245 // Register JS.
246 wp_register_script(
247 'chartist_js',
248 plugins_url( 'js/chartist.min.js', STATIFY_FILE ),
249 array(),
250 self::get_version(),
251 true
252 );
253 wp_register_script(
254 'chartist_tooltip_js',
255 plugins_url( 'js/chartist-plugin-tooltip.min.js', STATIFY_FILE ),
256 array( 'chartist_js' ),
257 self::get_version(),
258 true
259 );
260 wp_register_script(
261 'statify_chart_js',
262 plugins_url( 'js/dashboard.min.js', STATIFY_FILE ),
263 array( 'wp-api-fetch', 'chartist_tooltip_js' ),
264 self::get_version(),
265 true
266 );
267
268 // Localize strings.
269 wp_localize_script(
270 'statify_chart_js',
271 'statifyDashboard',
272 array(
273 'sitename' => sanitize_key( get_bloginfo( 'name' ) ),
274 )
275 );
276 wp_set_script_translations( 'statify_chart_js', 'statify' );
277 }
278
279 /**
280 * Print CSS
281 *
282 * @since 0.1.0
283 * @since 2.0.0 Moved to Statify class
284 */
285 public static function add_style(): void {
286 // Register CSS.
287 wp_register_style(
288 'chartist_css',
289 plugins_url( '/css/chartist.min.css', STATIFY_FILE ),
290 array(),
291 self::get_version()
292 );
293 wp_register_style(
294 'chartist_tooltip_css',
295 plugins_url( '/css/chartist-plugin-tooltip.min.css', STATIFY_FILE ),
296 array(),
297 self::get_version()
298 );
299 wp_register_style(
300 'statify',
301 plugins_url( '/css/dashboard.min.css', STATIFY_FILE ),
302 array(),
303 self::get_version()
304 );
305
306 // Load CSS.
307 wp_enqueue_style( 'chartist_css' );
308 wp_enqueue_style( 'chartist_tooltip_css' );
309 wp_enqueue_style( 'statify' );
310 }
311
312 /**
313 * Set plugin version from plugin meta data
314 *
315 * @since 1.4.0
316 * @since 2.0.0 Moved up to Statify class.
317 */
318 protected static function get_version(): string {
319 if ( ! isset( self::$plugin_version ) ) {
320 $meta = get_plugin_data( STATIFY_FILE );
321 self::$plugin_version = $meta['Version'];
322 }
323
324 return self::$plugin_version;
325 }
326
327 /**
328 * Rules to skip the tracking.
329 *
330 * @hook boolean statify__skip_tracking
331 * @see https://wordpress.org/plugins/statify/
332 *
333 * @return boolean $skip_hook TRUE if NO tracking is desired.
334 *
335 * @since 1.2.6
336 * @since 2.0.0 Migration from Statify_Frontend to Statify class.
337 */
338 private static function skip_tracking(): bool {
339 // Skip tracking via Hook.
340 $skip_hook = apply_filters( 'statify__skip_tracking', null );
341 if ( null !== $skip_hook ) {
342 return $skip_hook;
343 }
344
345 // Skip tracking via User Agent.
346 $user_agent = sanitize_text_field( isset( $_SERVER['HTTP_USER_AGENT'] ) ? wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) : '' );
347 if ( is_null( $user_agent )
348 || false === $user_agent
349 || self::is_bot() ) {
350 return true;
351 }
352
353 // Skip tracking via Referrer check.
354 if ( self::check_referrer() ) {
355 return true;
356 }
357
358 // Skip for trackbacks and robots.
359 if ( is_trackback() || is_robots() ) {
360 return true;
361 }
362
363 // Skip logged in users, if enabled.
364 if ( ( self::SKIP_USERS_ALL === self::$options['skip']['logged_in'] && is_user_logged_in() ) ||
365 // Only skip administrators.
366 ( self::SKIP_USERS_ADMIN === self::$options['skip']['logged_in'] && current_user_can( 'manage_options' ) ) ) {
367 return true;
368 }
369
370 // Skip for "internal" requests.
371 return self::is_internal();
372 }
373
374 /**
375 * Checks if user agent is a bot.
376 *
377 * @return boolean $is_bot TRUE if user agent is a bot, FALSE if not.
378 *
379 * @since 1.7.0
380 * @since 2.0.0 Migration from Statify_Frontend to Statify class, removed $user_agent parameter.
381 */
382 private static function is_bot(): bool {
383 $crawler_detect = new \Jaybizzle\CrawlerDetect\CrawlerDetect();
384
385 // Check the user agent of the current 'visitor' via the user agent and http_from header.
386 return $crawler_detect->isCrawler();
387 }
388
389 /**
390 * Rules to detect internal calls to skip tracking and not print code snippet.
391 *
392 * @return boolean $skip_hook TRUE if NO tracking is desired
393 *
394 * @since 1.6.1
395 * @since 2.0.0 Migration from Statify_Frontend to Statify class.
396 */
397 protected static function is_internal(): bool {
398 // Skip for preview, 404 calls, feed, search, favicon and sitemap access.
399 return is_preview() || is_404() || is_feed() || is_search()
400 || ( function_exists( 'is_favicon' ) && is_favicon() )
401 || '' !== get_query_var( 'sitemap' ) || '' !== get_query_var( 'sitemap-stylesheet' );
402 }
403
404 /**
405 * Compare the referrer URL to the disallowed keys list.
406 * De/activate this feature via settings in the Dashboard widget.
407 *
408 * @return boolean TRUE of referrer matches disallowed keys entry and should thus be excluded.
409 *
410 * @since 1.5.0
411 * @since 2.0.0 Migration from Statify_Frontend to Statify class.
412 */
413 private static function check_referrer(): bool {
414 // Return false if the disallowed-keys filter (formerly blacklist) is inactive.
415 if ( ! self::$options['blacklist'] ) {
416 return false;
417 }
418
419 $referrer = filter_var(
420 ( isset( $_SERVER['HTTP_REFERER'] ) ? wp_unslash( $_SERVER['HTTP_REFERER'] ) : '' ),
421 FILTER_SANITIZE_URL
422 );
423 if ( ! is_null( $referrer ) && false !== $referrer ) {
424 $referrer = wp_parse_url( $referrer, PHP_URL_HOST );
425 }
426
427 // Fallback for wp_parse_url() returning array instead of host only.
428 if ( is_array( $referrer ) && isset( $referrer['host'] ) ) {
429 $referrer = $referrer['host'];
430 }
431
432 // Return false if there still is no referrer to check.
433 if ( ! is_string( $referrer ) ) {
434 return false;
435 }
436
437 // Finally compare referrer against the disallowed keys.
438 $disallowed_keys = self::get_disallowed_keys();
439 foreach ( $disallowed_keys as $item ) {
440 if ( strpos( $referrer, $item ) !== false ) {
441 return true;
442 }
443 }
444
445 return false;
446 }
447
448 /**
449 * Get an array from the disallowed_keys option of 'Settings' - 'Discussion' - 'Disallowed Comment Keys'.
450 *
451 * @return string[]
452 *
453 * @since 1.7.3 Renamed to "get_disallowed_keys" to match WP 5.5. wording.
454 * @since 2.0.0 Migration from Statify_Frontend to Statify class.
455 */
456 private static function get_disallowed_keys(): array {
457 $disallowed_keys = get_option( 'disallowed_keys' );
458
459 if ( false === $disallowed_keys ) {
460 // WordPress < 5.5 uses the old key.
461 $disallowed_keys = get_option( 'blacklist_keys' ); // phpcs:ignore WordPress.WP.DeprecatedParameterValues.Found
462 }
463
464 if ( empty( $disallowed_keys ) ) {
465 return array();
466 }
467
468 return (array) explode( "\n", $disallowed_keys );
469 }
470
471 /**
472 * Check whether any of the given substrings occurs in a string.
473 *
474 * @param string $haystack The string to search in.
475 * @param array $needle The string to search for.
476 *
477 * @return boolean
478 */
479 private static function strposa( string $haystack, array $needle ): bool {
480
481 foreach ( $needle as $query ) {
482 if ( strpos( $haystack, $query ) !== false ) {
483 return true;
484 } // Stop on first true result.
485 }
486
487 return false;
488 }
489 }
490