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 / class-query.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
class-query.php
563 lines
1 <?php
2 // exit if accessed directly
3 if ( ! defined( 'ABSPATH' ) )
4 exit;
5
6 /**
7 * Post_Views_Counter_Query class.
8 *
9 * @class Post_Views_Counter_Query
10 */
11 class Post_Views_Counter_Query {
12
13 private $join_sql = '';
14
15 /**
16 * Class constructor.
17 *
18 * @return void
19 */
20 public function __construct() {
21 // actions
22 add_action( 'pre_get_posts', [ $this, 'extend_pre_query' ], 1 );
23
24 // filters
25 add_filter( 'query_vars', [ $this, 'query_vars' ] );
26 add_filter( 'posts_join', [ $this, 'posts_join' ], 100, 2 );
27 add_filter( 'posts_groupby', [ $this, 'posts_groupby' ], 10, 2 );
28 add_filter( 'posts_orderby', [ $this, 'posts_orderby' ], 10, 2 );
29 add_filter( 'posts_distinct', [ $this, 'posts_distinct' ], 10, 2 );
30 add_filter( 'posts_fields', [ $this, 'posts_fields' ], 10, 2 );
31 add_filter( 'the_posts', [ $this, 'the_posts' ], 10, 2 );
32 }
33
34 /**
35 * Register views_query var.
36 *
37 * @param array $query_vars
38 * @return array
39 */
40 public function query_vars( $query_vars ) {
41 $query_vars[] = 'views_query';
42
43 return $query_vars;
44 }
45
46 /**
47 * Extend query with post_views orderby parameter.
48 *
49 * @param object $query
50 * @return void
51 */
52 public function extend_pre_query( $query ) {
53 // skip empty sort order
54 if ( empty( $query->query_vars['orderby'] ) )
55 return;
56
57 if ( is_string( $query->query_vars['orderby'] ) ) {
58 // simple order by post_views
59 if ( $query->query_vars['orderby'] === 'post_views' )
60 $query->pvc_orderby = true;
61 // multisort post_views as string
62 elseif ( strpos( $query->query_vars['orderby'], 'post_views' ) !== false ) {
63 // explode orderby
64 $sort = explode( ' ', $query->query_vars['orderby'] );
65
66 // make sure only full string is available
67 if ( ! empty( $sort ) ) {
68 // clear it
69 $sort = array_filter( $sort );
70
71 if ( in_array( 'post_views', $sort, true ) )
72 $query->pvc_orderby = true;
73 }
74 }
75 // post_views in array
76 } elseif ( is_array( $query->query_vars['orderby'] ) && array_key_exists( 'post_views', $query->query_vars['orderby'] ) )
77 $query->pvc_orderby = true;
78 }
79
80 /**
81 * Modify the database query to use post_views parameter.
82 *
83 * @global object $wpdb
84 *
85 * @param string $join
86 * @param object $query
87 * @return string
88 */
89 public function posts_join( $join, $query ) {
90 $sql = '';
91 $query_chunks = [];
92
93 // views query?
94 if ( ! empty( $query->query['views_query'] ) ) {
95 if ( isset( $query->query['views_query']['inclusive'] ) )
96 $query->query['views_query']['inclusive'] = (bool) $query->query['views_query']['inclusive'];
97 else
98 $query->query['views_query']['inclusive'] = true;
99
100 // check after and before dates
101 foreach ( [ 'after' => '>', 'before' => '<' ] as $date => $type ) {
102 $year_ = null;
103 $month_ = null;
104 $week_ = null;
105 $day_ = null;
106
107 // check views query date
108 if ( ! empty( $query->query['views_query'][$date] ) ) {
109 // is it a date array?
110 if ( is_array( $query->query['views_query'][$date] ) ) {
111 // check views query $date date year
112 if ( ! empty( $query->query['views_query'][$date]['year'] ) )
113 $year_ = str_pad( (int) $query->query['views_query'][$date]['year'], 4, 0, STR_PAD_LEFT );
114
115 // check views query date month
116 if ( ! empty( $query->query['views_query'][$date]['month'] ) )
117 $month_ = str_pad( (int) $query->query['views_query'][$date]['month'], 2, 0, STR_PAD_LEFT );
118
119 // check views query date week
120 if ( ! empty( $query->query['views_query'][$date]['week'] ) )
121 $week_ = str_pad( (int) $query->query['views_query'][$date]['week'], 2, 0, STR_PAD_LEFT );
122
123 // check views query date day
124 if ( ! empty( $query->query['views_query'][$date]['day'] ) )
125 $day_ = str_pad( (int) $query->query['views_query'][$date]['day'], 2, 0, STR_PAD_LEFT );
126 // is it a date string?
127 } elseif ( is_string( $query->query['views_query'][$date] ) ) {
128 $time_ = strtotime( $query->query['views_query'][$date] );
129
130 // valid datetime?
131 if ( $time_ !== false ) {
132 // week does not exists here, string dates are always treated as year + month + day
133 list( $day_, $month_, $year_ ) = explode( ' ', date( "d m Y", $time_ ) );
134 }
135 }
136
137 // valid date?
138 if ( ! ( $year_ === null && $month_ === null && $week_ === null && $day_ === null ) ) {
139 $query_chunks[] = [
140 'year' => $year_,
141 'month' => $month_,
142 'day' => $day_,
143 'week' => $week_,
144 'type' => $type . ( $query->query['views_query']['inclusive'] ? '=' : '' )
145 ];
146 }
147 }
148 }
149
150 // any after, before query chunks?
151 if ( ! empty( $query_chunks ) ) {
152 $valid_dates = true;
153
154 // check only if both dates are in query
155 if ( count( $query_chunks ) === 2 ) {
156 // before and after dates should be the same
157 foreach ( [ 'year', 'month', 'day', 'week' ] as $date_type ) {
158 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 ) ) )
159 $valid_dates = false;
160 }
161 }
162
163 // after and before dates should be both valid
164 if ( $valid_dates ) {
165 foreach ( $query_chunks as $chunk ) {
166 // year
167 if ( isset( $chunk['year'] ) ) {
168 // year, week
169 if ( isset( $chunk['week'] ) )
170 $sql .= " AND pvc.type = 1 AND pvc.period " . $chunk['type'] . " '" . $chunk['year'] . $chunk['week'] . "'";
171 // year, month
172 elseif ( isset( $chunk['month'] ) ) {
173 // year, month, day
174 if ( isset( $chunk['day'] ) )
175 $sql .= " AND pvc.type = 0 AND pvc.period " . $chunk['type'] . " '" . $chunk['year'] . $chunk['month'] . $chunk['day'] . "'";
176 // year, month
177 else
178 $sql .= " AND pvc.type = 2 AND pvc.period " . $chunk['type'] . " '" . $chunk['year'] . $chunk['month'] . "'";
179 // year
180 } else
181 $sql .= " AND pvc.type = 3 AND pvc.period " . $chunk['type'] . " '" . $chunk['year'] . "'";
182 // month
183 } elseif ( isset( $chunk['month'] ) ) {
184 // month, day
185 if ( isset( $chunk['day'] ) )
186 $sql .= " AND pvc.type = 0 AND RIGHT( pvc.period, 4 ) " . $chunk['type'] . " '" . $chunk['month'] . $chunk['day'] . "'";
187 // month
188 else
189 $sql .= " AND pvc.type = 2 AND RIGHT( pvc.period, 2 ) " . $chunk['type'] . " '" . $chunk['month'] . "'";
190 // week
191 } elseif ( isset( $chunk['week'] ) )
192 $sql .= " AND pvc.type = 1 AND RIGHT( pvc.period, 2 ) " . $chunk['type'] . " '" . $chunk['week'] . "'";
193 // day
194 elseif ( isset( $chunk['day'] ) )
195 $sql .= " AND pvc.type = 0 AND RIGHT( pvc.period, 2 ) " . $chunk['type'] . " '" . $chunk['day'] . "'";
196 }
197 }
198 }
199
200 // standard query
201 if ( $sql === '' ) {
202 // check year
203 if ( isset( $query->query['views_query']['year'] ) )
204 $year = (int) $query->query['views_query']['year'];
205
206 // check month
207 if ( isset( $query->query['views_query']['month'] ) )
208 $month = (int) $query->query['views_query']['month'];
209
210 // check week
211 if ( isset( $query->query['views_query']['week'] ) )
212 $week = (int) $query->query['views_query']['week'];
213
214 // check day
215 if ( isset( $query->query['views_query']['day'] ) )
216 $day = (int) $query->query['views_query']['day'];
217
218 // year
219 if ( isset( $year ) ) {
220 // year, week
221 if ( isset( $week ) && $this->is_date_valid( 'yw', $year, 0, 0, $week ) )
222 $sql = " AND pvc.type = 1 AND pvc.period = '" . str_pad( $year, 4, 0, STR_PAD_LEFT ) . str_pad( $week, 2, 0, STR_PAD_LEFT ) . "'";
223 // year, month
224 elseif ( isset( $month ) ) {
225 // year, month, day
226 if ( isset( $day ) && $this->is_date_valid( 'ymd', $year, $month, $day ) )
227 $sql = " AND pvc.type = 0 AND pvc.period = '" . str_pad( $year, 4, 0, STR_PAD_LEFT ) . str_pad( $month, 2, 0, STR_PAD_LEFT ) . str_pad( $day, 2, 0, STR_PAD_LEFT ) . "'";
228 // year, month
229 elseif ( $this->is_date_valid( 'ym', $year, $month ) )
230 $sql = " AND pvc.type = 2 AND pvc.period = '" . str_pad( $year, 4, 0, STR_PAD_LEFT ) . str_pad( $month, 2, 0, STR_PAD_LEFT ) . "'";
231 // year
232 } elseif ( $this->is_date_valid( 'y', $year ) )
233 $sql = " AND pvc.type = 3 AND pvc.period = '" . str_pad( $year, 4, 0, STR_PAD_LEFT ) . "'";
234 // month
235 } elseif ( isset( $month ) ) {
236 // month, day
237 if ( isset( $day ) && $this->is_date_valid( 'md', 0, $month, $day ) ) {
238 $sql = " AND pvc.type = 0 AND RIGHT( pvc.period, 4 ) = '" . str_pad( $month, 2, 0, STR_PAD_LEFT ) . str_pad( $day, 2, 0, STR_PAD_LEFT ) . "'";
239 // month
240 } elseif ( $this->is_date_valid( 'm', 0, $month ) )
241 $sql = " AND pvc.type = 2 AND RIGHT( pvc.period, 2 ) = '" . str_pad( $month, 2, 0, STR_PAD_LEFT ) . "'";
242 // week
243 } elseif ( isset( $week ) && $this->is_date_valid( 'w', 0, 0, 0, $week ) )
244 $sql = " AND pvc.type = 1 AND RIGHT( pvc.period, 2 ) = '" . str_pad( $week, 2, 0, STR_PAD_LEFT ) . "'";
245 // day
246 elseif ( isset( $day ) && $this->is_date_valid( 'd', 0, 0, $day ) )
247 $sql = " AND pvc.type = 0 AND RIGHT( pvc.period, 2 ) = '" . str_pad( $day, 2, 0, STR_PAD_LEFT ) . "'";
248 }
249
250 if ( $sql !== '' )
251 $query->pvc_query = true;
252 }
253
254 // is it sorted by post views?
255 if ( ( $sql === '' && isset( $query->pvc_orderby ) && $query->pvc_orderby ) || apply_filters( 'pvc_extend_post_object', false, $query ) === true )
256 $sql = ' AND pvc.type = 4';
257
258 // add date range
259 if ( $sql !== '' ) {
260 global $wpdb;
261
262 $join .= " LEFT JOIN " . $wpdb->prefix . "post_views pvc ON pvc.id = " . $wpdb->prefix . "posts.ID" . $sql;
263
264 $this->join_sql = $join;
265 }
266
267 return $join;
268 }
269
270 /**
271 * Group posts using the post ID.
272 *
273 * @global object $wpdb
274 * @global string $pagenow
275 *
276 * @param string $groupby
277 * @param object $query
278 * @return string
279 */
280 public function posts_groupby( $groupby, $query ) {
281 // is it sorted by post views or views_query is used?
282 if ( ( isset( $query->pvc_orderby ) && $query->pvc_orderby ) || ( isset( $query->pvc_query ) && $query->pvc_query ) || apply_filters( 'pvc_extend_post_object', false, $query ) === true ) {
283 global $pagenow;
284
285 // needed only for sorting
286 if ( $pagenow === 'upload.php' || $pagenow === 'edit.php' )
287 $query->query['views_query']['hide_empty'] = false;
288
289 global $wpdb;
290
291 $groupby = trim( $groupby );
292 $groupby_aliases = [];
293 $groupby_values = [];
294 $groupby_sql = '';
295 $groupby_set = false;
296
297 // standard group by
298 if ( strpos( $groupby, $wpdb->prefix . 'posts.ID' ) === false )
299 $groupby_aliases[] = $wpdb->prefix . 'posts.ID';
300 else
301 $groupby_set = true;
302
303 // tax query group by
304 $groupby_aliases[] = $this->get_groupby_meta_aliases( $query );
305
306 // meta query group by
307 if ( $this->join_sql ) {
308 $groupby_aliases[] = $this->get_groupby_tax_aliases( $query, $this->join_sql );
309
310 // clear join to avoid possible issues
311 $this->join_sql = '';
312 }
313
314 // any group by aliases?
315 if ( ! empty( $groupby_aliases ) ) {
316 foreach ( $groupby_aliases as $alias ) {
317 if ( is_array( $alias ) ) {
318 $groupby_values = array_merge( $groupby_values, $alias );
319 } else
320 $groupby_values[] = $alias;
321 }
322 }
323
324 // any group by values?
325 if ( ! empty( $groupby_values ) ) {
326 $groupby = ( $groupby !== '' ? $groupby . ', ' : '' ) . implode( ', ', $groupby_values );
327
328 // set group by flag
329 $groupby_set = true;
330 }
331
332 if ( $groupby_set )
333 $query->pvc_groupby = true;
334
335 // hide empty?
336 if ( ! isset( $query->query['views_query']['hide_empty'] ) || $query->query['views_query']['hide_empty'] === true )
337 $groupby .= ' HAVING post_views > 0';
338 }
339
340 return $groupby;
341 }
342
343 /**
344 * Order posts by post views.
345 *
346 * @param string $orderby
347 * @param object $query
348 * @return string
349 */
350 public function posts_orderby( $orderby, $query ) {
351 // is it sorted by post views?
352 if ( ( isset( $query->pvc_orderby ) && $query->pvc_orderby ) ) {
353 // get order
354 $order = strtoupper( (string) $query->get( 'order' ) );
355 $org_orderby = $query->get( 'orderby' );
356
357 if ( ! in_array( $order, [ 'ASC', 'DESC' ], true ) )
358 $order = 'DESC';
359
360 if ( is_array( $org_orderby ) && array_key_exists( 'post_views', $org_orderby ) ) {
361 $post_views_order = strtoupper( (string) $org_orderby['post_views'] );
362
363 if ( in_array( $post_views_order, [ 'ASC', 'DESC' ], true ) )
364 $order = $post_views_order;
365 }
366
367 $post_views_orderby = 'post_views ' . $order;
368
369 if ( ! is_string( $orderby ) || trim( $orderby ) === '' )
370 $orderby = $post_views_orderby;
371 elseif ( preg_match( '/\bpost_views\b/i', $orderby ) !== 1 )
372 $orderby = $post_views_orderby . ', ' . $orderby;
373 }
374
375 return $orderby;
376 }
377
378 /**
379 * Add DISTINCT clause.
380 *
381 * @param string $distinct
382 * @param object $query
383 * @return string
384 */
385 public function posts_distinct( $distinct, $query ) {
386 if ( ( ( isset( $query->pvc_groupby ) && $query->pvc_groupby ) || ( isset( $query->pvc_orderby ) && $query->pvc_orderby ) || ( isset( $query->pvc_query ) && $query->pvc_query ) || apply_filters( 'pvc_extend_post_object', false, $query ) === true ) && ( strpos( $distinct, 'DISTINCT' ) === false ) )
387 $distinct = $distinct . ' DISTINCT ';
388
389 return $distinct;
390 }
391
392 /**
393 * Return post views in queried post objects.
394 *
395 * @param string $fields
396 * @param object $query
397 * @return string
398 */
399 public function posts_fields( $fields, $query ) {
400 if ( ( ! isset( $query->query['fields'] ) || $query->query['fields'] === '' || $query->query['fields'] === 'all' ) && ( ( isset( $query->pvc_orderby ) && $query->pvc_orderby ) || ( isset( $query->pvc_query ) && $query->pvc_query ) || apply_filters( 'pvc_extend_post_object', false, $query ) === true ) )
401 $fields = $fields . ', SUM( COALESCE( pvc.count, 0 ) ) AS post_views';
402
403 return $fields;
404 }
405
406 /**
407 * Get tax table aliases from query.
408 *
409 * @global object $wpdb
410 *
411 * @param object $query
412 * @param string $join_sql
413 * @return array
414 */
415 private function get_groupby_tax_aliases( $query, $join_sql ) {
416 global $wpdb;
417
418 $groupby = [];
419
420 // trim join sql
421 $join_sql = trim( $join_sql );
422
423 // any join sql? valid query with tax query?
424 if ( $join_sql !== '' && is_a( $query, 'WP_Query' ) && ! empty( $query->tax_query ) && is_a( $query->tax_query, 'WP_Tax_Query' ) ) {
425 // unfortunately there is no way to get table_aliases by native function
426 // tax query does not have get_clauses either like meta query does
427 // we have to find aliases the hard way
428 $chunks = explode( 'JOIN', $join_sql );
429
430 // any join clauses?
431 if ( ! empty( $chunks ) ) {
432 $aliases = [];
433
434 foreach ( $chunks as $chunk ) {
435 // standard join
436 if ( strpos( $chunk, $wpdb->prefix . 'term_relationships ON' ) !== false )
437 $aliases[] = $wpdb->prefix . 'term_relationships';
438 // alias join
439 elseif ( strpos( $chunk, $wpdb->prefix . 'term_relationships AS' ) !== false && preg_match( '/' . $wpdb->prefix . 'term_relationships AS ([a-z0-9]+) ON/i', $chunk, $matches ) === 1 )
440 $aliases[] = $matches[1];
441 }
442
443 // any aliases?
444 if ( ! empty( $aliases ) ) {
445 foreach ( array_unique( $aliases ) as $alias ) {
446 $groupby[] = $alias . '.term_taxonomy_id';
447 }
448 }
449 }
450 }
451
452 return $groupby;
453 }
454
455 /**
456 * Get meta table aliases from query.
457 *
458 * @param object $query
459 * @return array
460 */
461 private function get_groupby_meta_aliases( $query ) {
462 $groupby = [];
463
464 // valid query with meta query?
465 if ( is_a( $query, 'WP_Query' ) && ! empty( $query->meta_query ) && is_a( $query->meta_query, 'WP_Meta_Query' ) ) {
466 // get meta clauses, we can't use table_aliases here since it's protected value
467 $clauses = $query->meta_query->get_clauses();
468
469 // any meta clauses?
470 if ( ! empty( $clauses ) ) {
471 $aliases = [];
472
473 foreach ( $clauses as $clause ) {
474 $aliases[] = $clause['alias'];
475 }
476
477 // any aliases?
478 if ( ! empty( $aliases ) ) {
479 foreach ( array_unique( $aliases ) as $alias ) {
480 $groupby[] = $alias . '.meta_id';
481 }
482 }
483 }
484 }
485
486 return $groupby;
487 }
488
489 /**
490 * Extend query object with total post views.
491 *
492 * @param array $posts
493 * @param object $query
494 * @return array
495 */
496 public function the_posts( $posts, $query ) {
497 if ( ( isset( $query->pvc_orderby ) && $query->pvc_orderby ) || ( isset( $query->pvc_query ) && $query->pvc_query ) || apply_filters( 'pvc_extend_post_object', false, $query ) === true ) {
498 $sum = 0;
499
500 // any posts found?
501 if ( ! empty( $posts ) ) {
502 foreach ( $posts as $post ) {
503 if ( ! empty( $post->post_views ) )
504 $sum += (int) $post->post_views;
505 }
506 }
507
508 // pass total views
509 $query->total_views = $sum;
510 }
511
512 return $posts;
513 }
514
515 /**
516 * Check whether date is valid.
517 *
518 * @param string $type
519 * @param int $year
520 * @param int $month
521 * @param int $day
522 * @param int $week
523 * @return bool
524 */
525 private function is_date_valid( $type, $year = 0, $month = 0, $day = 0, $week = 0 ) {
526 switch ( $type ) {
527 case 'y':
528 $bool = ( $year >= 1 && $year <= 32767 );
529 break;
530
531 case 'yw':
532 $bool = ( $year >= 1 && $year <= 32767 && $week >= 0 && $week <= 53 );
533 break;
534
535 case 'ym':
536 $bool = ( $year >= 1 && $year <= 32767 && $month >= 1 && $month <= 12 );
537 break;
538
539 case 'ymd':
540 $bool = checkdate( $month, $day, $year );
541 break;
542
543 case 'm':
544 $bool = ( $month >= 1 && $month <= 12 );
545 break;
546
547 case 'md':
548 $bool = ( $month >= 1 && $month <= 12 && $day >= 1 && $day <= 31 );
549 break;
550
551 case 'w':
552 $bool = ( $week >= 0 && $week <= 53 );
553 break;
554
555 case 'd':
556 $bool = ( $day >= 1 && $day <= 31 );
557 break;
558 }
559
560 return $bool;
561 }
562 }
563