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