PluginProbe ʕ •ᴥ•ʔ
Post Views Counter / 1.7.13
Post Views Counter v1.7.13
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 / functions.php
post-views-counter / includes Last commit date
class-admin.php 2 weeks ago class-columns-modal.php 2 weeks ago class-columns.php 2 weeks ago class-counter.php 2 weeks ago class-crawler-detect.php 2 weeks ago class-cron.php 2 weeks ago class-dashboard.php 2 weeks ago class-emails-mailer.php 2 weeks ago class-emails-period.php 2 weeks ago class-emails-query.php 2 weeks ago class-emails-scheduler.php 2 weeks ago class-emails-template.php 2 weeks ago class-emails.php 2 weeks ago class-frontend.php 2 weeks ago class-functions.php 2 weeks ago class-import.php 2 weeks ago class-integration-gutenberg.php 2 weeks ago class-integrations.php 2 weeks ago class-query.php 2 weeks ago class-settings-api.php 2 weeks ago class-settings-display.php 2 weeks ago class-settings-emails.php 2 weeks ago class-settings-general.php 2 weeks ago class-settings-integrations.php 2 weeks ago class-settings-other.php 2 weeks ago class-settings-reports.php 2 weeks ago class-settings.php 2 weeks ago class-toolbar.php 2 weeks ago class-traffic-signals.php 2 weeks ago class-update.php 2 weeks ago class-widgets.php 2 weeks ago functions.php 2 weeks ago
functions.php
986 lines
1 <?php
2 /**
3 * Post Views Counter pluggable template functions
4 *
5 * Override any of those functions by copying it to your theme or replace it via plugin
6 *
7 * @author Digital Factory
8 * @package Post Views Counter
9 * @since 1.0.0
10 */
11 // exit if accessed directly
12 if ( ! defined( 'ABSPATH' ) )
13 exit;
14
15 /**
16 * Get post views for a post or array of posts.
17 *
18 * @global object $wpdb
19 *
20 * @param int|array $post_id
21 * @param string $period
22 * @param array $args Optional arguments to override period parsing
23 *
24 * @return int
25 */
26 if ( ! function_exists( 'pvc_get_post_views' ) ) {
27 function pvc_get_post_views( $post_id = 0, $period = 'total', $args = [] ) {
28 global $wpdb;
29
30 // ensure args is an array
31 if ( ! is_array( $args ) ) {
32 $args = [];
33 }
34
35 // sanitize period
36 $period = sanitize_key( $period );
37
38 if ( empty( $post_id ) )
39 $post_id = get_the_ID();
40
41 if ( is_array( $post_id ) ) {
42 $numbers = array_filter( array_unique( array_map( 'intval', $post_id ) ) );
43 $post_id = implode( ',', $numbers );
44 } else if ( $post_id === 'all' ) {
45 $numbers = [];
46 } else {
47 $post_id = (int) $post_id;
48 $numbers = [ $post_id ];
49 }
50
51 // set where clause
52 $where = [ 'type' => 'type = 4' ];
53
54 // override type if explicitly provided in args
55 if ( isset( $args['type'] ) ) {
56 $where['type'] = 'type = ' . (int) $args['type'];
57 } elseif ( isset( $args['period_type'] ) ) {
58 $period_type = sanitize_key( $args['period_type'] );
59 $type_map = [
60 'day' => 0,
61 'week' => 1,
62 'month' => 2,
63 'year' => 3,
64 'total' => 4
65 ];
66 if ( isset( $type_map[ $period_type ] ) ) {
67 $where['type'] = 'type = ' . $type_map[ $period_type ];
68 }
69 }
70
71 // optional period range (e.g., for day-based ranges within a month)
72 $range_from = null;
73 $range_to = null;
74
75 if ( isset( $args['period_range'] ) && is_array( $args['period_range'] ) ) {
76 $range = array_values( $args['period_range'] );
77 if ( isset( $range[0], $range[1] ) ) {
78 $range_from = (int) $range[0];
79 $range_to = (int) $range[1];
80 }
81 } elseif ( isset( $args['period_from'], $args['period_to'] ) ) {
82 $range_from = (int) $args['period_from'];
83 $range_to = (int) $args['period_to'];
84 }
85
86 if ( $range_from && $range_to ) {
87 $range_min = min( $range_from, $range_to );
88 $range_max = max( $range_from, $range_to );
89
90 $where['period'] = 'CAST( period AS SIGNED ) <= ' . $range_max . ' AND CAST( period AS SIGNED ) >= ' . $range_min;
91
92 // default to day type when using explicit ranges and no type set
93 if ( ! isset( $where['type'] ) ) {
94 $where['type'] = 'type = 0';
95 }
96 }
97
98 $is_total_period_query = false;
99
100 if (
101 $period === 'total'
102 && ( ! isset( $args['type'] ) || (int) $args['type'] === 4 )
103 && ( ! isset( $args['period_type'] ) || sanitize_key( $args['period_type'] ) === 'total' )
104 ) {
105 $is_total_period_query = true;
106 } elseif (
107 isset( $args['period_type'] )
108 && sanitize_key( $args['period_type'] ) === 'total'
109 && ( ! isset( $args['type'] ) || (int) $args['type'] === 4 )
110 ) {
111 $is_total_period_query = true;
112 } elseif (
113 isset( $args['type'] )
114 && (int) $args['type'] === 4
115 && ! isset( $args['period_type'] )
116 && in_array( $period, [ '', 'total' ], true )
117 ) {
118 $is_total_period_query = true;
119 }
120
121 // total views are stored as a dedicated period; constrain the query so existing indexes can be used.
122 if ( $is_total_period_query && ! $range_from && ! $range_to ) {
123 $where['period'] = "period = 'total'";
124 }
125
126 // handle explicit content parameter for custom implementations
127 if ( isset( $args['content'] ) ) {
128 $where['content'] = 'content = ' . (int) $args['content'];
129 }
130
131 // update where clause
132 $where = apply_filters( 'pvc_get_post_views_period_where', $where, $period, $post_id, $args );
133
134 // updated where clause
135 $_where = [];
136
137 // sanitize where clause
138 foreach ( $where as $index => $value ) {
139 if ( $index === 'type' || $index === 'content' )
140 $_where[$index] = preg_replace( '/[^0-9]/', '', $value );
141 elseif ( $index === 'period' ) {
142 if ( is_string( $value ) && preg_match( "/period\s*=\s*'?total'?/i", $value ) ) {
143 $_where['period'] = [ 'total' ];
144 continue;
145 }
146
147 $values = preg_match_all( '/\d+/', $value, $matches );
148
149 // any values?
150 if ( $values !== false && $values > 0 )
151 $_where['period'] = $matches[0];
152 }
153 }
154
155 // get current number of ids
156 $ids_count = count( $numbers );
157
158 $where_clause = '';
159
160 // add post ids if needed
161 if ( $ids_count ) {
162 $where_clause .= " WHERE id IN (" . implode( ',', array_fill( 0, $ids_count, '%d' ) ) . ") AND";
163 } else {
164 $where_clause .= " WHERE";
165 }
166
167 // validate where clause
168 foreach( $_where as $index => $value ) {
169 if ( $index === 'type' ) {
170 $where_clause .= ' type = %d';
171 $numbers[] = (int) $value;
172 } elseif ( $index === 'content' && pvc_post_views_has_content_column() ) {
173 $where_clause .= ' AND content = %d';
174 $numbers[] = (int) $value;
175 } elseif ( $index === 'period' ) {
176 $nop = count( $_where['period'] );
177
178 if ( $nop === 1 ) {
179 if ( $_where['period'][0] === 'total' ) {
180 $where_clause .= ' AND period = %s';
181 $numbers[] = 'total';
182 } else {
183 $where_clause .= ' AND CAST( period AS SIGNED ) = %d';
184 $numbers[] = (int) $_where['period'][0];
185 }
186 } elseif ( $nop === 2 ) {
187 $where_clause .= ' AND CAST( period AS SIGNED ) <= %d AND CAST( period AS SIGNED ) >= %d';
188 $numbers[] = (int) $_where['period'][0];
189 $numbers[] = (int) $_where['period'][1];
190 }
191 }
192 }
193
194 // prepare query
195 $query = $wpdb->prepare( "SELECT SUM(count) AS views FROM " . $wpdb->prefix . "post_views" . $where_clause, $numbers );
196
197 // calculate query hash
198 $query_hash = md5( $query );
199
200 // get cached data
201 $post_views = wp_cache_get( $query_hash, 'pvc-get_post_views' );
202
203 // cached data not found?
204 if ( $post_views === false ) {
205 // get post views
206 $post_views = (int) $wpdb->get_var( $query );
207
208 // set the cache expiration, 5 minutes by default
209 $expire = absint( apply_filters( 'pvc_object_cache_expire', 300 ) );
210
211 // add cached post views
212 wp_cache_add( $query_hash, $post_views, 'pvc-get_post_views', $expire );
213 }
214
215 return (int) apply_filters( 'pvc_get_post_views', $post_views, $post_id, $period, $args );
216 }
217 }
218
219 /**
220 * Check if content column exists in post_views table.
221 *
222 * @global object $wpdb
223 *
224 * @return bool
225 */
226 function pvc_post_views_has_content_column() {
227 global $wpdb;
228
229 static $has_content_column = null;
230
231 // check column existence only once
232 if ( $has_content_column === null ) {
233 // check content column using SHOW COLUMNS (more robust than information_schema)
234 $columns = $wpdb->get_col( "SHOW COLUMNS FROM " . $wpdb->prefix . "post_views LIKE 'content'" );
235
236 // cache result
237 $has_content_column = ! empty( $columns );
238 }
239
240 // always allow override via filter (applied on every call)
241 return apply_filters( 'pvc_post_views_has_content_column', $has_content_column );
242 }
243
244 /**
245 * Get views query.
246 *
247 * @global object $wpdb
248 *
249 * @param array $args
250 *
251 * @return int|array
252 */
253 if ( ! function_exists( 'pvc_get_views' ) ) {
254 function pvc_get_views( $args = [] ) {
255 global $wpdb;
256
257 $range = [];
258 $defaults = [
259 'fields' => 'views',
260 'post_id' => '',
261 'post_type' => '',
262 'views_query' => [
263 'year' => '',
264 'month' => '',
265 'week' => '',
266 'day' => '',
267 'after' => '', // string or array
268 'before' => '', // string or array
269 'inclusive' => true
270 ]
271 ];
272
273 // merge default options with new arguments
274 $args = array_merge( $defaults, $args );
275
276 // check views query
277 if ( ! is_array( $args['views_query'] ) )
278 $args['views_query'] = $defaults['views_query'];
279
280 // merge views query too
281 $args['views_query'] = array_merge( $defaults['views_query'], $args['views_query'] );
282
283 // filter arguments
284 $args = apply_filters( 'pvc_get_views_args', $args );
285
286 // check post types
287 if ( is_string( $args['post_type'] ) )
288 $args['post_type'] = [ $args['post_type'] ];
289 elseif ( ! is_array( $args['post_type'] ) )
290 $args['post_type'] = [];
291
292 // get number of post types
293 $post_types_count = count( $args['post_type'] );
294
295 // check post ids
296 if ( is_array( $args['post_id'] ) && ! empty( $args['post_id'] ) )
297 $args['post_id'] = array_filter( array_unique( array_map( 'intval', $args['post_id'] ) ) );
298 elseif ( is_string( $args['post_id'] ) || is_numeric( $args['post_id'] ) ) {
299 $post_id = (int) $args['post_id'];
300
301 if ( $post_id === 0 )
302 $args['post_id'] = [];
303 else
304 $args['post_id'] = [ $post_id ];
305 } else
306 $args['post_id'] = [];
307
308 // get number of post ids
309 $post_ids_count = count( $args['post_id'] );
310
311 // placeholder for empty query data
312 $query_data = [ 1 ];
313
314 // set query data
315 if ( $post_ids_count === 0 && $post_types_count === 0 )
316 $query_data = [ 1 ];
317 elseif ( $post_ids_count === 0 )
318 $query_data = array_merge( $query_data, array_values( $args['post_type'] ) );
319 elseif ( $post_types_count === 0 )
320 $query_data = array_merge( $query_data, array_values( $args['post_id'] ) );
321 else
322 $query_data = array_merge( $query_data, array_values( $args['post_id'] ), array_values( $args['post_type'] ) );
323
324 // set where clause
325 $where = apply_filters( 'pvc_get_views_period_where', [], $args );
326
327 // check fields
328 if ( ! in_array( $args['fields'], [ 'views', 'date=>views' ], true ) )
329 $args['fields'] = $defaults['fields'];
330
331 $query_chunks = [];
332 $views_query = '';
333
334 // views query after/before parameters work only when fields == views
335 if ( $args['fields'] === 'views' ) {
336 // check views query inclusive
337 if ( ! isset( $args['views_query']['inclusive'] ) )
338 $args['views_query']['inclusive'] = $defaults['views_query']['inclusive'];
339 else
340 $args['views_query']['inclusive'] = (bool) $args['views_query']['inclusive'];
341
342 // check after and before dates
343 foreach ( [ 'after' => '>', 'before' => '<' ] as $date => $type ) {
344 $year_ = null;
345 $month_ = null;
346 $week_ = null;
347 $day_ = null;
348
349 // check views query date
350 if ( ! empty( $args['views_query'][$date] ) ) {
351 // is it a date array?
352 if ( is_array( $args['views_query'][$date] ) ) {
353 // check views query $date date year
354 if ( ! empty( $args['views_query'][$date]['year'] ) )
355 $year_ = str_pad( (int) $args['views_query'][$date]['year'], 4, 0, STR_PAD_LEFT );
356
357 // check views query date month
358 if ( ! empty( $args['views_query'][$date]['month'] ) )
359 $month_ = str_pad( (int) $args['views_query'][$date]['month'], 2, 0, STR_PAD_LEFT );
360
361 // check views query date week
362 if ( ! empty( $args['views_query'][$date]['week'] ) )
363 $week_ = str_pad( (int) $args['views_query'][$date]['week'], 2, 0, STR_PAD_LEFT );
364
365 // check views query date day
366 if ( ! empty( $args['views_query'][$date]['day'] ) )
367 $day_ = str_pad( (int) $args['views_query'][$date]['day'], 2, 0, STR_PAD_LEFT );
368 // is it a date string?
369 } elseif ( is_string( $args['views_query'][$date] ) ) {
370 $time_ = strtotime( $args['views_query'][$date] );
371
372 // valid datetime?
373 if ( $time_ !== false ) {
374 // week does not exists here, string dates are always treated as year + month + day
375 list( $day_, $month_, $year_ ) = explode( ' ', date( "d m Y", $time_ ) );
376 }
377 }
378
379 // valid date?
380 if ( ! ( $year_ === null && $month_ === null && $week_ === null && $day_ === null ) ) {
381 $query_chunks[] = [
382 'year' => $year_,
383 'month' => $month_,
384 'day' => $day_,
385 'week' => $week_,
386 'type' => $type . ( $args['views_query']['inclusive'] ? '=' : '' )
387 ];
388 }
389 }
390 }
391
392 if ( ! empty( $query_chunks ) ) {
393 $valid_dates = true;
394
395 // after and before?
396 if ( count( $query_chunks ) === 2 ) {
397 // before and after dates should be the same
398 foreach ( [ 'year', 'month', 'day', 'week' ] as $date_type ) {
399 if ( ! ( ( $query_chunks[0][$date_type] !== null && $query_chunks[1][$date_type] !== null ) || ( $query_chunks[0][$date_type] === null && $query_chunks[1][$date_type] === null ) ) )
400 $valid_dates = false;
401 }
402 }
403
404 if ( $valid_dates ) {
405 foreach ( $query_chunks as $chunk ) {
406 // year
407 if ( isset( $chunk['year'] ) ) {
408 // year, week
409 if ( isset( $chunk['week'] ) ) {
410 $where['type'] = 'pvc.type = 1';
411 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['year'] . $chunk['week'] );
412 }
413 // year, month
414 elseif ( isset( $chunk['month'] ) ) {
415 // year, month, day
416 if ( isset( $chunk['day'] ) ) {
417 $where['type'] = 'pvc.type = 0';
418 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['year'] . $chunk['month'] . $chunk['day'] );
419 }
420 // year, month
421 else {
422 $where['type'] = 'pvc.type = 2';
423 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['year'] . $chunk['month'] );
424 }
425 // year
426 } else {
427 $where['type'] = 'pvc.type = 3';
428 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['year'] );
429 }
430 // month
431 } elseif ( isset( $chunk['month'] ) ) {
432 // month, day
433 if ( isset( $chunk['day'] ) ) {
434 $where['type'] = 'pvc.type = 0';
435 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 4 ) AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['month'] . $chunk['day'] );
436 // month
437 } else {
438 $where['type'] = 'pvc.type = 2';
439 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 2 ) AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['month'] );
440 }
441 // week
442 } elseif ( isset( $chunk['week'] ) ) {
443 $where['type'] = 'pvc.type = 1';
444 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 2 ) AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['week'] );
445 }
446 // day
447 elseif ( isset( $chunk['day'] ) ) {
448 $where['type'] = 'pvc.type = 0';
449 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 2 ) AS SIGNED ) " . $chunk['type'] . " " . (int) ( $chunk['day'] );
450 }
451 }
452 }
453 }
454 }
455
456 $special_views_query = ( $views_query !== '' );
457
458 if ( $args['fields'] === 'date=>views' || $views_query === '' ) {
459 // check views query year
460 if ( ! empty( $args['views_query']['year'] ) )
461 $year = str_pad( (int) $args['views_query']['year'], 4, 0, STR_PAD_LEFT );
462
463 // check views query month
464 if ( ! empty( $args['views_query']['month'] ) )
465 $month = str_pad( (int) $args['views_query']['month'], 2, 0, STR_PAD_LEFT );
466
467 // check views query week
468 if ( ! empty( $args['views_query']['week'] ) )
469 $week = str_pad( (int) $args['views_query']['week'], 2, 0, STR_PAD_LEFT );
470
471 // check views query day
472 if ( ! empty( $args['views_query']['day'] ) )
473 $day = str_pad( (int) $args['views_query']['day'], 2, 0, STR_PAD_LEFT );
474
475 // year
476 if ( isset( $year ) ) {
477 // year, week
478 if ( isset( $week ) ) {
479 if ( $args['fields'] === 'date=>views' ) {
480 // create date based on week number
481 $date = new DateTime( $year . 'W' . $week );
482
483 // get monday
484 $monday = $date->format( 'd' );
485
486 // get month of monday
487 $monday_month = $date->format( 'm' );
488
489 // prepare range
490 for( $i = 1; $i <= 6; $i++ ) {
491 $range[(string) ( $date->format( 'Y' ) . $date->format( 'm' ) . $date->format( 'd' ) )] = 0;
492
493 $date->modify( '+1days' );
494 }
495
496 $range[(string) ( $date->format( 'Y' ) . $date->format( 'm' ) . $date->format( 'd' ) )] = 0;
497
498 // get month of sunday
499 $sunday_month = $date->format( 'm' );
500
501 $where['type'] = 'pvc.type = 0';
502 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) >= " . (int) ( $year . $monday_month . $monday ) . " AND CAST( pvc.period AS SIGNED ) <= " . (int) ( $date->format( 'Y' ) . $sunday_month . $date->format( 'd' ) );
503 } else {
504 $where['type'] = 'pvc.type = 1';
505 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) = " . (int) ( $year . $week );
506 }
507 // year, month
508 } elseif ( isset( $month ) ) {
509 // year, month, day
510 if ( isset( $day ) ) {
511 if ( $args['fields'] === 'date=>views' )
512 // prepare range
513 $range[(string) ( $year . $month . $day )] = 0;
514
515 $where['type'] = 'pvc.type = 0';
516 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) = " . (int) ( $year . $month . $day );
517 // year, month
518 } else {
519 if ( $args['fields'] === 'date=>views' ) {
520 // create date
521 $date = new DateTime( $year . '-' . $month . '-01' );
522
523 // get last day
524 $last = $date->format( 't' );
525
526 // prepare range
527 for( $i = 1; $i <= $last; $i++ ) {
528 $range[(string) ( $year . $month . str_pad( $i, 2, 0, STR_PAD_LEFT ) )] = 0;
529 }
530
531 $where['type'] = 'pvc.type = 0';
532 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) >= " . (int) ( $year . $month ) . "01 AND CAST( pvc.period AS SIGNED ) <= " . (int) ( $year . $month . $last );
533 } else {
534 $where['type'] = 'pvc.type = 2';
535 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) = " . (int) ( $year . $month );
536 }
537 }
538 // year
539 } else {
540 if ( $args['fields'] === 'date=>views' ) {
541 // prepare range
542 for( $i = 1; $i <= 12; $i++ ) {
543 $range[(string) ( $year . str_pad( $i, 2, 0, STR_PAD_LEFT ) )] = 0;
544 }
545
546 // create date
547 $date = new DateTime( $year . '-12-01' );
548
549 $where['type'] = 'pvc.type = 2';
550 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) >= " . (int) ( $year ) . "01 AND CAST( pvc.period AS SIGNED ) <= " . (int) ( $year ) . "12";
551 } else {
552 $where['type'] = 'pvc.type = 3';
553 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( pvc.period AS SIGNED ) = " . (int) ( $year );
554 }
555 }
556 // month
557 } elseif ( isset( $month ) ) {
558 // month, day
559 if ( isset( $day ) ) {
560 $where['type'] = 'pvc.type = 0';
561 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 4 ) AS SIGNED ) = " . (int) ( $month . $day );
562 // month
563 } else {
564 $where['type'] = 'pvc.type = 2';
565 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 2 ) AS SIGNED ) = " . (int) ( $month );
566 }
567 // week
568 } elseif ( isset( $week ) ) {
569 $where['type'] = 'pvc.type = 1';
570 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 2 ) AS SIGNED ) = " . (int) ( $week );
571 // day
572 } elseif ( isset( $day ) ) {
573 $where['type'] = 'pvc.type = 0';
574 $views_query .= ' AND ' . implode( ' AND ', $where ) . " AND CAST( RIGHT( pvc.period, 2 ) AS SIGNED ) = " . (int) ( $day );
575 }
576 }
577
578 // update where clause
579 $where['type'] = [ 'pvc.type = 4' ];
580
581 $query = $wpdb->prepare(
582 "SELECT " . ( $args['fields'] === 'date=>views' ? 'pvc.period, ' : '' ) . "SUM( COALESCE( pvc.count, 0 ) ) AS post_views
583 FROM " . $wpdb->prefix . "posts wpp
584 LEFT JOIN " . $wpdb->prefix . "post_views pvc ON pvc.id = wpp.ID AND 1 = %d" . ( $views_query !== '' ? ' ' . $views_query : ' AND ' . implode( ' AND ', $where ) ) . ( ! empty( $args['post_id'] ) ? ' AND pvc.id IN (' . implode( ',', array_fill( 0, $post_ids_count, '%d' ) ) . ')' : '' ) . "
585 " . ( ! empty( $args['post_type'] ) ? 'WHERE wpp.post_type IN (' . implode( ',', array_fill( 0, $post_types_count, '%s' ) ) . ')' : '' ) . "
586 " . ( $views_query !== '' && $special_views_query === false ? 'GROUP BY pvc.period HAVING post_views > 0' : '' ),
587 $query_data
588 );
589
590 $query = apply_filters( 'pvc_get_views_query_sql', $query, $args, $views_query );
591
592 // get cached data
593 $post_views = wp_cache_get( md5( $query ), 'pvc-get_views' );
594
595 // cached data not found?
596 if ( $post_views === false ) {
597 if ( $args['fields'] === 'date=>views' && ! empty( $range ) ) {
598 $results = $wpdb->get_results( $query );
599
600 if ( ! empty( $results ) ) {
601 foreach ( $results as $row ) {
602 $range[$row->period] = (int) $row->post_views;
603 }
604 }
605
606 $post_views = $range;
607 } else
608 $post_views = (int) $wpdb->get_var( $query );
609
610 // set the cache expiration, 5 minutes by default
611 $expire = absint( apply_filters( 'pvc_object_cache_expire', 300 ) );
612
613 wp_cache_add( md5( $query ), $post_views, 'pvc-get_views', $expire );
614 }
615
616 return apply_filters( 'pvc_get_views', $post_views );
617 }
618 }
619
620 /**
621 * Display post views for a given post.
622 *
623 * @param int $post_id
624 * @param bool $display
625 *
626 * @return string|void
627 */
628 if ( ! function_exists( 'pvc_post_views' ) ) {
629 function pvc_post_views( $post_id = 0, $display = true, $period = '' ) {
630 // get all data
631 $post_id = (int) ( empty( $post_id ) ? get_the_ID() : $post_id );
632
633 // get display options
634 $options = Post_Views_Counter()->options['display'];
635
636 // get post views
637 $views = pvc_get_post_views( $post_id, $period !== '' ? $period : $options['display_period'] );
638
639 // use number format?
640 $views = $options['use_format'] ? number_format_i18n( $views ) : $views;
641
642 // container class
643 $class = apply_filters( 'pvc_post_views_class', 'post-views content-post post-' . $post_id . ' entry-meta', $post_id );
644
645 // dynamic loading?
646 $class .= $options['dynamic_loading'] === true ? ' load-dynamic' : ' load-static';
647
648 // prepare display
649 $label = apply_filters( 'pvc_post_views_label', ( function_exists( 'icl_t' ) ? icl_t( 'Post Views Counter', 'Post Views Label', $options['label'] ) : $options['label'] ), $post_id );
650
651 // add dashicons class if needed
652 $icon_class = strpos( $options['icon_class'], 'dashicons' ) === false ? $options['icon_class'] : 'dashicons ' . $options['icon_class'];
653
654 // prepare icon output
655 $icon = apply_filters( 'pvc_post_views_icon', '<span class="post-views-icon ' . esc_attr( $icon_class ) . '"></span> ', $post_id );
656
657 // final views
658 $views = apply_filters( 'pvc_post_views_number_format', $views, $post_id );
659
660 $html = apply_filters(
661 'pvc_post_views_html',
662 '<div class="' . esc_attr( $class ) . '">
663 ' . ( $options['display_style']['icon'] ? $icon : '' )
664 . ( $options['display_style']['text'] ? '<span class="post-views-label">' . esc_html( $label ) . '</span> ' : '' )
665 . '<span class="post-views-count">' . $views . '</span>
666 </div>',
667 $post_id,
668 $views,
669 $label,
670 $icon
671 );
672
673 if ( $display )
674 echo wp_kses_post( $html );
675 else
676 return $html;
677 }
678 }
679
680 /**
681 * Get most viewed posts.
682 *
683 * @param array $args
684 *
685 * @return array
686 */
687 if ( ! function_exists( 'pvc_get_most_viewed_posts' ) ) {
688 function pvc_get_most_viewed_posts( $args = [] ) {
689 $args = array_merge(
690 [
691 'posts_per_page' => 10,
692 'order' => 'desc',
693 'post_type' => [ 'post' ],
694 'post_status' => [ 'publish' ],
695 'fields' => 'all',
696 'period' => 'total'
697 ],
698 $args
699 );
700
701 if ( ( is_array( $args['post_type'] ) && in_array( 'attachment', $args['post_type'], true ) ) || ( is_string( $args['post_type'] ) && $args['post_type'] === 'attachment' ) )
702 $args['post_status'][] = 'inherit';
703
704 $args = apply_filters( 'pvc_get_most_viewed_posts_args', $args );
705
706 // force to use filters
707 $args['suppress_filters'] = false;
708
709 // force to use post views as order
710 $args['orderby'] = 'post_views';
711
712 return apply_filters( 'pvc_get_most_viewed_posts', get_posts( $args ), $args );
713 }
714 }
715
716 /**
717 * Display a list of most viewed posts.
718 *
719 * @param array $args
720 * @param bool $display
721 *
722 * @return void|string
723 */
724 if ( ! function_exists( 'pvc_most_viewed_posts' ) ) {
725 function pvc_most_viewed_posts( $args = [], $display = true ) {
726 $defaults = [
727 'number_of_posts' => 5,
728 'post_type' => [ 'post' ],
729 'order' => 'desc',
730 'thumbnail_size' => 'thumbnail',
731 'list_type' => 'unordered',
732 'show_post_views' => true,
733 'show_post_thumbnail' => false,
734 'show_post_author' => false,
735 'show_post_excerpt' => false,
736 'no_posts_message' => __( 'No most viewed posts found.', 'post-views-counter' ),
737 'item_before' => '',
738 'item_after' => '',
739 'period' => 'total'
740 ];
741
742 $args = apply_filters( 'pvc_most_viewed_posts_args', wp_parse_args( $args, $defaults ) );
743
744 // get periods
745 $periods = apply_filters( 'pvc_display_period_options', [ 'total' => __( 'Total Views', 'post-views-counter' ) ] );
746
747 // sanitize arguments
748 $args['show_post_views'] = (bool) $args['show_post_views'];
749 $args['show_post_thumbnail'] = (bool) $args['show_post_thumbnail'];
750 $args['show_post_author'] = (bool) $args['show_post_author'];
751 $args['show_post_excerpt'] = (bool) $args['show_post_excerpt'];
752 $args['period'] = isset( $args['period'] ) && array_key_exists( $args['period'], $periods ) ? $args['period'] : $defaults['period'];
753 $args['post_type'] = isset( $args['post_type'] ) ? $args['post_type'] : $defaults['post_type'];
754
755 // no post types?
756 if ( empty( $args['post_type'] ) )
757 $html = $args['no_posts_message'];
758 else {
759 // get posts
760 $posts = pvc_get_most_viewed_posts( [
761 'posts_per_page' => isset( $args['number_of_posts'] ) ? (int) $args['number_of_posts'] : $defaults['number_of_posts'],
762 'order' => isset( $args['order'] ) ? $args['order'] : $defaults['order'],
763 'post_type' => $args['post_type'],
764 'period' => $args['period']
765 ] );
766
767 if ( ! empty( $posts ) ) {
768 $html = ( $args['list_type'] === 'unordered' ? '<ul>' : '<ol>' );
769
770 foreach ( $posts as $post ) {
771 setup_postdata( $post );
772
773 $html .= '<li>';
774 $html .= apply_filters( 'pvc_most_viewed_posts_item_before', $args['item_before'], $post );
775
776 if ( $args['show_post_thumbnail'] && has_post_thumbnail( $post->ID ) ) {
777 $html .= '<span class="post-thumbnail">' . get_the_post_thumbnail( $post->ID, $args['thumbnail_size'] ) . '</span>';
778 }
779
780 $html .= '<a class="post-title" href="' . get_permalink( $post->ID ) . '">' . get_the_title( $post->ID ) . '</a>' . ( $args['show_post_author'] ? ' <span class="author">(' . get_the_author_meta( 'display_name', $post->post_author ) . ')</span> ' : '' ) . ( $args['show_post_views'] ? ' <span class="count">(' . number_format_i18n( (int) ( property_exists( $post, 'post_views' ) ? $post->post_views : pvc_get_post_views( $post->ID, $args['period'] ) ) ) . ')</span>' : '' );
781
782 if ( $args['show_post_excerpt'] ) {
783 $excerpt = '';
784
785 if ( empty( $post->post_excerpt ) )
786 $text = $post->post_content;
787 else
788 $text = $post->post_excerpt;
789
790 if ( ! empty( $text ) )
791 $excerpt = wp_trim_words( str_replace( ']]>', ']]&gt;', strip_shortcodes( $text ) ), apply_filters( 'excerpt_length', 55 ), apply_filters( 'excerpt_more', ' ' . '[&hellip;]' ) );
792
793 if ( ! empty( $excerpt ) )
794 $html .= '<div class="post-excerpt">' . esc_html( $excerpt ) . '</div>';
795 }
796
797 $html .= apply_filters( 'pvc_most_viewed_posts_item_after', $args['item_after'], $post );
798 $html .= '</li>';
799 }
800
801 wp_reset_postdata();
802
803 $html .= ( $args['list_type'] === 'unordered' ? '</ul>' : '</ol>' );
804 } else
805 $html = $args['no_posts_message'];
806 }
807
808 $html = apply_filters( 'pvc_most_viewed_posts_html', $html, $args );
809
810 if ( $display )
811 echo wp_kses_post( $html );
812 else
813 return $html;
814 }
815 }
816
817 /**
818 * Update total number of post views for a post.
819 *
820 * @global object $wpdb
821 *
822 * @param int $post_id
823 * @param int $post_views
824 *
825 * @return bool|int
826 */
827 function pvc_update_post_views( $post_id = 0, $post_views = 0 ) {
828 global $wpdb;
829
830 // cast post ID
831 $post_id = (int) $post_id;
832
833 // get post
834 $post = get_post( $post_id );
835
836 // check if post exists
837 if ( empty( $post ) )
838 return false;
839
840 // cast number of views
841 $post_views = (int) $post_views;
842 $post_views = $post_views < 0 ? 0 : $post_views;
843
844 // change post views?
845 $post_views = apply_filters( 'pvc_update_post_views_count', $post_views, $post_id );
846
847 // insert or update database post views count
848 $wpdb->query( $wpdb->prepare( "INSERT INTO " . $wpdb->prefix . "post_views (id, type, period, count) VALUES (%d, %d, %s, %d) ON DUPLICATE KEY UPDATE count = %d", $post_id, 4, 'total', $post_views, $post_views ) );
849
850 // query fails only if it returns false
851 return apply_filters( 'pvc_update_post_views', $post_id );
852 }
853
854 /**
855 * View post manually function.
856 *
857 * By default this function has limitations. It works properly only between
858 * wp_loaded (minimum priority 10) and wp_head (maximum priority 6) actions and
859 * it can handle only one function execution per site request.
860 *
861 * To bypass these limitations there is a $bypass_content argument. It requires
862 * JavaScript or REST API as counter mode but it extends the ability to use
863 * pvc_view_post up to wp_print_footer_scripts (maximum priority 10) action. It
864 * also bypass one function execution limitation to allow multiple function
865 * calls during one site request. This also includes the correct saving of
866 * cookies.
867 *
868 * @since 1.2.0
869 *
870 * @param int $post_id
871 * @param bool $bypass_content
872 *
873 * @return bool
874 */
875 function pvc_view_post( $post_id = 0, $bypass_content = false ) {
876 // no post id?
877 if ( empty( $post_id ) ) {
878 // get current id
879 $post_id = get_the_ID();
880 } else {
881 // cast post id
882 $post_id = (int) $post_id;
883 }
884
885 // get post
886 $post = get_post( $post_id );
887
888 // invalid post?
889 if ( ! is_a( $post, 'WP_Post' ) )
890 return false;
891
892 // get main instance
893 $pvc = Post_Views_Counter();
894
895 if ( $bypass_content )
896 $pvc->counter->add_to_queue( $post_id );
897 else
898 $pvc->counter->check_post( $post_id );
899
900 return true;
901 }
902
903 /**
904 * Convert string to date.
905 *
906 * @param string $period
907 * @return DateTiem object
908 */
909 if ( ! function_exists( 'pvc_period2date' ) ) {
910 function pvc_period2date( $period ) {
911 $datetime = false;
912
913 // check requested period by string length
914 $length = is_string( $period ) ? strlen( $period ) : 0;
915
916 if ( $length ) {
917 switch ( $length ) {
918 // day
919 case 8:
920 $datetime = date_create_from_format( 'Ymd' , $period );
921 break;
922
923 // week
924 case 7:
925 // get year and week from string
926 $period_year = (int) substr( $period, 0, -3 );
927 $period_week = (int) substr( $period, 4, -1 );
928
929 $datetime = new DateTime();
930 $datetime->setISODate( $period_year, $period_week );
931 break;
932
933 // month
934 case 6:
935 $datetime = date_create_from_format( 'Ymd' , $period . '01' );
936 break;
937 // year
938 case 4:
939 $datetime = date_create_from_format( 'Y' , $period );
940 break;
941
942 default:
943 $datetime = new DateTime();
944 }
945 }
946
947 return apply_filters( 'pvc_period2date', $datetime, $period );
948 }
949 }
950
951 /**
952 * Convert period to timestamp.
953 *
954 * @param string $period
955 * @return int
956 */
957 if ( ! function_exists( 'pvc_period2timestamp' ) ) {
958 function pvc_period2timestamp( $period ) {
959 $period = preg_replace( '/[^a-z0-9_|]/', '', $period );
960
961 // default time
962 $timestamp = current_time( 'timestamp', false );
963
964 // whitelisted period?
965 if ( in_array( $period, [ 'this_week', 'this_year', 'this_month' ], true ) ) {
966 $timestamp = current_time( 'timestamp', false );
967 // backward compatibility
968 } else if ( preg_match( '/^([0-9]{2}\|[0-9]{4})$/', $period ) === 1 ) {
969 // month|year
970 $date = explode( '|', $period, 2 );
971
972 // get timestamp
973 $timestamp = strtotime( (string) $date[1] . '-' . (string) $date[0] . '-13' );
974 } else {
975 // convert string to DateTime()
976 $d = pvc_period2date( $period );
977
978 if ( $d ) {
979 $timestamp = $d->getTimestamp();
980 }
981 }
982
983 return $timestamp;
984 }
985 }
986