PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.2.4
Post Views Counter v1.2.4
1.7.13 1.7.12 1.7.11 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3 1.3.1 1.3.10 1.3.11 1.3.12 1.3.13 1.3.2 1.3.2.1 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.7.0 1.7.1 1.7.10 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9
post-views-counter / includes / counter.php
post-views-counter / includes Last commit date
columns.php 9 years ago counter.php 9 years ago crawler-detect.php 9 years ago cron.php 9 years ago dashboard.php 9 years ago frontend.php 9 years ago functions.php 9 years ago query.php 9 years ago settings.php 9 years ago update.php 9 years ago widgets.php 9 years ago
counter.php
515 lines
1 <?php
2 // exit if accessed directly
3 if ( ! defined( 'ABSPATH' ) )
4 exit;
5
6 /**
7 * Post_Views_Counter_Counter class.
8 *
9 * @class Post_Views_Counter_Counter
10 */
11 class Post_Views_Counter_Counter {
12
13 const GROUP = 'pvc';
14 const NAME_ALLKEYS = 'cached_key_names';
15 const CACHE_KEY_SEPARATOR = '.';
16
17 private $cookie = array(
18 'exists' => false,
19 'visited_posts' => array(),
20 'expiration' => 0
21 );
22
23 public function __construct() {
24 // actions
25 add_action( 'plugins_loaded', array( $this, 'check_cookie' ), 1 );
26 add_action( 'deleted_post', array( $this, 'delete_post_views' ) );
27 add_action( 'wp', array( $this, 'check_post_php' ) );
28 add_action( 'wp_ajax_pvc-check-post', array( $this, 'check_post_ajax' ) );
29 add_action( 'wp_ajax_nopriv_pvc-check-post', array( $this, 'check_post_ajax' ) );
30 }
31
32 /**
33 * Check if IPv4 is in range.
34 *
35 * @param string $ip IP address
36 * @param string $range IP range
37 * @return boolean Whether IP is in range
38 */
39 function ipv4_in_range( $ip, $range ) {
40 $start = str_replace( '*', '0', $range );
41 $end = str_replace( '*', '255', $range );
42 $ip = (float) sprintf( "%u", ip2long( $ip ) );
43
44 return ( $ip >= (float) sprintf( "%u", ip2long( $start ) ) && $ip <= (float) sprintf( "%u", ip2long( $end ) ) );
45 }
46
47 /**
48 * Check whether to count visit.
49 *
50 * @param int $id
51 */
52 public function check_post( $id = 0 ) {
53 // get post id
54 $id = (int) ( empty( $id ) ? get_the_ID() : $id );
55
56 // empty id?
57 if ( empty( $id ) )
58 return;
59
60 // get ips
61 $ips = Post_Views_Counter()->options['general']['exclude_ips'];
62
63 // whether to count this ip
64 if ( ! empty( $ips ) && filter_var( preg_replace( '/[^0-9a-fA-F:., ]/', '', $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP ) ) {
65 // check ips
66 foreach ( $ips as $ip ) {
67 if ( strpos( $ip, '*' ) !== false ) {
68 if ( $this->ipv4_in_range( $_SERVER['REMOTE_ADDR'], $ip ) )
69 return;
70 } else {
71 if ( $_SERVER['REMOTE_ADDR'] === $ip )
72 return;
73 }
74 }
75 }
76
77 // get groups to check them faster
78 $groups = Post_Views_Counter()->options['general']['exclude']['groups'];
79
80 // whether to count this user
81 if ( is_user_logged_in() ) {
82 // exclude logged in users?
83 if ( in_array( 'users', $groups, true ) )
84 return;
85 // exclude specific roles?
86 elseif ( in_array( 'roles', $groups, true ) && $this->is_user_role_excluded( Post_Views_Counter()->options['general']['exclude']['roles'] ) )
87 return;
88 }
89 // exclude guests?
90 elseif ( in_array( 'guests', $groups, true ) )
91 return;
92
93 // whether to count robots
94 if ( in_array( 'robots', $groups, true ) && Post_Views_Counter()->crawler_detect->is_crawler() )
95 return;
96
97 // cookie already existed?
98 if ( $this->cookie['exists'] ) {
99 // post already viewed but not expired?
100 if ( in_array( $id, array_keys( $this->cookie['visited_posts'] ), true ) && current_time( 'timestamp', true ) < $this->cookie['visited_posts'][$id] ) {
101 // update cookie but do not count visit
102 $this->save_cookie( $id, $this->cookie, false );
103
104 return;
105 } else
106 // update cookie
107 $this->save_cookie( $id, $this->cookie );
108 } else
109 // set new cookie
110 $this->save_cookie( $id );
111
112 $count_visit = (bool) apply_filters( 'pvc_count_visit', true, $id );
113
114 // count visit
115 if ( $count_visit )
116 $this->count_visit( $id );
117 }
118
119 /**
120 * Check whether to count visit via PHP request.
121 */
122 public function check_post_php() {
123 // do not count admin entries
124 if ( is_admin() && ! (defined( 'DOING_AJAX' ) && DOING_AJAX) )
125 return;
126
127 // do we use PHP as counter?
128 if ( Post_Views_Counter()->options['general']['counter_mode'] != 'php' )
129 return;
130
131 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
132
133 // whether to count this post type
134 if ( empty( $post_types ) || ! is_singular( $post_types ) )
135 return;
136
137 $this->check_post( get_the_ID() );
138 }
139
140 /**
141 * Check whether to count visit via AJAX request.
142 */
143 public function check_post_ajax() {
144 if ( isset( $_POST['action'], $_POST['post_id'], $_POST['pvc_nonce'], $_POST['post_type'] ) && $_POST['action'] === 'pvc-check-post' && ($post_id = (int) $_POST['post_id']) > 0 && wp_verify_nonce( $_POST['pvc_nonce'], 'pvc-check-post' ) !== false ) {
145
146 // do we use Ajax as counter?
147 if ( Post_Views_Counter()->options['general']['counter_mode'] != 'js' )
148 exit;
149
150 // get countable post types
151 $post_types = Post_Views_Counter()->options['general']['post_types_count'];
152
153 // get post type
154 $post_type = get_post_type( $post_id );
155
156 // whether to count this post type or not
157 if ( empty( $post_types ) || empty( $post_type ) || $post_type !== $_POST['post_type'] || ! in_array( $post_type, $post_types, true ) )
158 exit;
159
160 $this->check_post( $post_id );
161 }
162
163 exit;
164 }
165
166 /**
167 * Initialize cookie session.
168 */
169 public function check_cookie() {
170 // do not run in admin except for ajax requests
171 if ( is_admin() && ! ( defined( 'DOING_AJAX' ) && DOING_AJAX ) )
172 return;
173
174 // is cookie set?
175 if ( isset( $_COOKIE['pvc_visits'] ) && ! empty( $_COOKIE['pvc_visits'] ) ) {
176 $visited_posts = $expirations = array();
177
178 foreach ( $_COOKIE['pvc_visits'] as $content ) {
179 // is cookie valid?
180 if ( preg_match( '/^(([0-9]+b[0-9]+a?)+)$/', $content ) === 1 ) {
181 // get single id with expiration
182 $expiration_ids = explode( 'a', $content );
183
184 // check every expiration => id pair
185 foreach ( $expiration_ids as $pair ) {
186 $pair = explode( 'b', $pair );
187 $expirations[] = (int) $pair[0];
188 $visited_posts[(int) $pair[1]] = (int) $pair[0];
189 }
190 }
191 }
192
193 $this->cookie = array(
194 'exists' => true,
195 'visited_posts' => $visited_posts,
196 'expiration' => max( $expirations )
197 );
198 }
199 }
200
201 /**
202 * Save cookie function.
203 *
204 * @param int $id
205 * @param array $cookie
206 * @param bool $expired
207 */
208 private function save_cookie( $id, $cookie = array(), $expired = true ) {
209 $expiration = $this->get_timestamp( Post_Views_Counter()->options['general']['time_between_counts']['type'], Post_Views_Counter()->options['general']['time_between_counts']['number'] );
210
211 // is this a new cookie?
212 if ( empty( $cookie ) ) {
213 // set cookie
214 setcookie( 'pvc_visits[0]', $expiration . 'b' . $id, $expiration, COOKIEPATH, COOKIE_DOMAIN, (isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ? true : false ), true );
215 } else {
216 if ( $expired ) {
217 // add new id or chang expiration date if id already exists
218 $cookie['visited_posts'][$id] = $expiration;
219 }
220
221 // create copy for better foreach performance
222 $visited_posts_expirations = $cookie['visited_posts'];
223
224 // get current gmt time
225 $time = current_time( 'timestamp', true );
226
227 // check whether viewed id has expired - no need to keep it in cookie (less size)
228 foreach ( $visited_posts_expirations as $post_id => $post_expiration ) {
229 if ( $time > $post_expiration )
230 unset( $cookie['visited_posts'][$post_id] );
231 }
232
233 // set new last expiration date if needed
234 $cookie['expiration'] = max( $cookie['visited_posts'] );
235
236 $cookies = $imploded = array();
237
238 // create pairs
239 foreach ( $cookie['visited_posts'] as $id => $exp ) {
240 $imploded[] = $exp . 'b' . $id;
241 }
242
243 // split cookie into chunks (4000 bytes to make sure it is safe for every browser)
244 $chunks = str_split( implode( 'a', $imploded ), 4000 );
245
246 // more then one chunk?
247 if ( count( $chunks ) > 1 ) {
248 $last_id = '';
249
250 foreach ( $chunks as $chunk_id => $chunk ) {
251 // new chunk
252 $chunk_c = $last_id . $chunk;
253
254 // is it full-length chunk?
255 if ( strlen( $chunk ) === 4000 ) {
256 // get last part
257 $last_part = strrchr( $chunk_c, 'a' );
258
259 // get last id
260 $last_id = substr( $last_part, 1 );
261
262 // add new full-lenght chunk
263 $cookies[$chunk_id] = substr( $chunk_c, 0, strlen( $chunk_c ) - strlen( $last_part ) );
264 } else {
265 // add last chunk
266 $cookies[$chunk_id] = $chunk_c;
267 }
268 }
269 } else {
270 // only one chunk
271 $cookies[] = $chunks[0];
272 }
273
274 foreach ( $cookies as $key => $value ) {
275 // set cookie
276 setcookie( 'pvc_visits[' . $key . ']', $value, $cookie['expiration'], COOKIEPATH, COOKIE_DOMAIN, (isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off' ? true : false ), true );
277 }
278 }
279 }
280
281 /**
282 * Count visit function.
283 *
284 * @global object $wpdb
285 * @param int $id
286 * @return bool
287 */
288 private function count_visit( $id ) {
289 global $wpdb;
290
291 $cache_key_names = array();
292 $using_object_cache = $this->using_object_cache();
293 $increment_amount = (int) apply_filters( 'pvc_views_increment_amount', 1, $id );
294
295 // get day, week, month and year
296 $date = explode( '-', date( 'W-d-m-Y', current_time( 'timestamp' ) ) );
297
298 foreach ( array(
299 0 => $date[3] . $date[2] . $date[1], // day like 20140324
300 1 => $date[3] . $date[0], // week like 201439
301 2 => $date[3] . $date[2], // month like 201405
302 3 => $date[3], // year like 2014
303 4 => 'total' // total views
304 ) as $type => $period ) {
305 if ( $using_object_cache ) {
306 $cache_key = $id . self::CACHE_KEY_SEPARATOR . $type . self::CACHE_KEY_SEPARATOR . $period;
307 wp_cache_add( $cache_key, 0, self::GROUP );
308 wp_cache_incr( $cache_key, $increment_amount, self::GROUP );
309 $cache_key_names[] = $cache_key;
310 } else {
311 // hit the db directly
312 // @TODO: investigate queueing these queries on the 'shutdown' hook instead instead of running them instantly?
313 $this->db_insert( $id, $type, $period, $increment_amount );
314 }
315 }
316
317 // update the list of cache keys to be flushed
318 if ( $using_object_cache && ! empty( $cache_key_names ) ) {
319 $this->update_cached_keys_list_if_needed( $cache_key_names );
320 }
321
322 do_action( 'pvc_after_count_visit', $id );
323
324 return true;
325 }
326
327 /**
328 * Remove post views from database when post is deleted.
329 *
330 * @global object $wpdb
331 * @param int $post_id
332 */
333 public function delete_post_views( $post_id ) {
334 global $wpdb;
335
336 $wpdb->delete( $wpdb->prefix . 'post_views', array( 'id' => $post_id ), array( '%d' ) );
337 }
338
339 /**
340 * Get timestamp convertion.
341 *
342 * @param string $type
343 * @param int $number
344 * @param int $timestamp
345 * @return string
346 */
347 public function get_timestamp( $type, $number, $timestamp = true ) {
348 $converter = array(
349 'minutes' => 60,
350 'hours' => 3600,
351 'days' => 86400,
352 'weeks' => 604800,
353 'months' => 2592000,
354 'years' => 946080000
355 );
356
357 return ( $timestamp ? current_time( 'timestamp', true ) : 0 ) + $number * $converter[$type];
358 }
359
360 /**
361 * Check if object cache is in use.
362 *
363 * @param bool $using
364 * @return bool
365 */
366 public function using_object_cache( $using = null ) {
367 $using = wp_using_ext_object_cache( $using );
368
369 if ( $using ) {
370 // check if explicitly disabled by flush_interval setting/option <= 0
371 $flush_interval_number = Post_Views_Counter()->options['general']['flush_interval']['number'];
372 $using = ( $flush_interval_number <= 0 ) ? false : true;
373 }
374
375 return $using;
376 }
377
378 /**
379 * Update the single cache key which holds a list of all the cache keys
380 * that need to be flushed to the db.
381 *
382 * The value of that special cache key is a giant string containing key names separated with the `|` character.
383 * Each such key name then consists of 3 elements: $id, $type, $period (separated by a `.` character).
384 * Examples:
385 * 62053.0.20150327|62053.1.201513|62053.2.201503|62053.3.2015|62053.4.total|62180.0.20150327|62180.1.201513|62180.2.201503|62180.3.2015|62180.4.total
386 * A single key is `62053.0.20150327` and that key's data is: $id = 62053, $type = 0, $period = 20150327
387 *
388 * This data format proved more efficient (avoids the (un)serialization overhead completely + duplicates filtering is a string search now)
389 *
390 * @param array $key_names
391 */
392 private function update_cached_keys_list_if_needed( $key_names = array() ) {
393 $existing_list = wp_cache_get( self::NAME_ALLKEYS, self::GROUP );
394 if ( ! $existing_list ) {
395 $existing_list = '';
396 }
397
398 $list_modified = false;
399
400 // modify the list contents if/when needed
401 if ( empty( $existing_list ) ) {
402 // the simpler case of an empty initial list where we just
403 // transform the specified key names into a string
404 $existing_list = implode( '|', $key_names );
405 $list_modified = true;
406 } else {
407 // search each specified key name and append it if it's not found
408 foreach ( $key_names as $key_name ) {
409 if ( false === strpos( $existing_list, $key_name ) ) {
410 $existing_list .= '|' . $key_name;
411 $list_modified = true;
412 }
413 }
414 }
415
416 // save modified list back in cache
417 if ( $list_modified ) {
418 wp_cache_set( self::NAME_ALLKEYS, $existing_list, self::GROUP );
419 }
420 }
421
422 /**
423 * Flush views data stored in the persistent object cache into
424 * our custom table and clear the object cache keys when done.
425 *
426 * @global object $wpdb
427 * @return bool
428 */
429 public function flush_cache_to_db() {
430 global $wpdb;
431
432 $key_names = wp_cache_get( self::NAME_ALLKEYS, self::GROUP );
433
434 if ( ! $key_names ) {
435 $key_names = array();
436 } else {
437 // create an array out of a string that's stored in the cache
438 $key_names = explode( '|', $key_names );
439 }
440
441 foreach ( $key_names as $key_name ) {
442 // get values stored within the key name itself
443 list( $id, $type, $period ) = explode( self::CACHE_KEY_SEPARATOR, $key_name );
444 // get the cached count value
445 $count = wp_cache_get( $key_name, self::GROUP );
446
447 // store cached value in the db
448 $this->db_insert( $id, $type, $period, $count );
449
450 // clear the cache key we just flushed
451 wp_cache_delete( $key_name, self::GROUP );
452 }
453
454 // delete the key holding the list itself after we've successfully flushed it
455 if ( ! empty( $key_names ) ) {
456 wp_cache_delete( self::NAME_ALLKEYS, self::GROUP );
457 }
458
459 return true;
460 }
461
462 /**
463 * Insert or update views count.
464 *
465 * @global object $wpdb
466 * @param int $id
467 * @param string $type
468 * @param string $period
469 * @param int $count
470 * @return bool
471 */
472 private function db_insert( $id, $type, $period, $count = 1 ) {
473 global $wpdb;
474
475 $count = (int) $count;
476
477 if ( ! $count ) {
478 $count = 1;
479 }
480
481 return $wpdb->query(
482 $wpdb->prepare( "
483 INSERT INTO " . $wpdb->prefix . "post_views (id, type, period, count)
484 VALUES (%d, %d, %s, %d)
485 ON DUPLICATE KEY UPDATE count = count + %d", $id, $type, $period, $count, $count
486 )
487 );
488 }
489
490 /**
491 * Check whether user has excluded roles.
492 *
493 * @param string $option
494 * @return bool
495 */
496 public function is_user_role_excluded( $option ) {
497 $user = wp_get_current_user();
498
499 if ( empty( $user ) )
500 return false;
501
502 $roles = (array) $user->roles;
503
504 if ( ! empty( $roles ) ) {
505 foreach ( $roles as $role ) {
506 if ( in_array( $role, $option, true ) )
507 return true;
508 }
509 }
510
511 return false;
512 }
513
514 }
515