PluginProbe
WebberZone Top 10 — Popular Posts / trunk
WebberZone Top 10 — Popular Posts vtrunk
4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 1.6.2 All 116 releases
top-10 / includes / admin / class-dashboard.php

class-dashboard.php in WebberZone Top 10 — Popular Posts trunk, at includes/admin/class-dashboard.php

819 lines 27.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Dashboard class.
4 *
5 * @package WebberZone\Top_Ten\Admin
6 */
7
8 namespace WebberZone\Top_Ten\Admin;
9
10 use WebberZone\Top_Ten\Admin\Settings\Settings_API;
11 use WebberZone\Top_Ten\Database;
12
13 if ( ! defined( 'WPINC' ) ) {
14 die;
15 }
16
17 /**
18 * Admin Dashboard Class.
19 *
20 * @since 3.0.0
21 */
22 class Dashboard {
23
24 /**
25 * Parent Menu ID.
26 *
27 * @since 3.0.0
28 *
29 * @var string Parent Menu ID.
30 */
31 public $parent_id;
32
33 /**
34 * Constructor class.
35 *
36 * @since 3.0.0
37 */
38 public function __construct() {
39 add_action( 'admin_menu', array( $this, 'admin_menu' ) );
40 add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ), 9 );
41 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
42 add_action( 'wp_ajax_tptn_chart_data', array( $this, 'get_chart_data' ) );
43 add_action( 'wp_ajax_tptn_dashboard_tab', array( $this, 'get_dashboard_tab' ) );
44 }
45
46 /**
47 * Render the settings page.
48 *
49 * @since 3.0.0
50 */
51 public function render_page() {
52 ob_start();
53
54 // Add date selector.
55 $chart_to_date = current_time( 'd M Y' );
56 $chart_from_date = gmdate( 'd M Y', strtotime( '-1 month' ) );
57
58 $post_date_from = ( isset( $_REQUEST['post-date-filter-from'] ) && check_admin_referer( 'tptn-dashboard' ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['post-date-filter-from'] ) ) : $chart_from_date;
59
60 $post_date_to = ( isset( $_REQUEST['post-date-filter-to'] ) && check_admin_referer( 'tptn-dashboard' ) ) ? sanitize_text_field( wp_unslash( $_REQUEST['post-date-filter-to'] ) ) : $chart_to_date;
61 $tabs = $this->get_tabs();
62 $initial_tab = '';
63 foreach ( $tabs as $tab_id => $tab ) {
64 if ( empty( $tab['hide'] ) ) {
65 $initial_tab = $tab_id;
66 break;
67 }
68 }
69
70 ?>
71 <div class="wrap">
72 <h1><?php esc_html_e( 'Top 10 Dashboard', 'top-10' ); ?></h1>
73 <?php
74 /**
75 * Fires at the top of a Top 10 admin page, after the heading.
76 *
77 * @since 4.0.0
78 */
79 do_action( 'tptn_settings_page_header' );
80 ?>
81
82 <?php settings_errors(); ?>
83
84 <div id="poststuff">
85 <div id="post-body" class="metabox-holder columns-2">
86 <div id="post-body-content">
87 <form method="post" >
88 <?php wp_nonce_field( 'tptn-dashboard' ); ?>
89
90 <div>
91 <input type="text" id="datepicker-from" name="post-date-filter-from" value="<?php echo esc_attr( $post_date_from ); ?>" size="11" />
92 <input type="text" id="datepicker-to" name="post-date-filter-to" value="<?php echo esc_attr( $post_date_to ); ?>" size="11" />
93 <?php
94 submit_button(
95 __( 'Update', 'top-10' ),
96 'primary',
97 'filter_action',
98 false,
99 array(
100 'id' => 'top-10-chart-submit',
101 'onclick' => 'updateChart(); return false;',
102 )
103 );
104 ?>
105 </div>
106 <div>
107 <canvas id="visits" width="400" height="150" aria-label="<?php esc_html_e( 'Top 10 Visits', 'top-10' ); ?>" role="img"></canvas>
108 </div>
109
110 </form>
111
112 <h2><?php esc_html_e( 'Historical visits', 'top-10' ); ?></h2>
113 <div id="dashboard-historical-visits" data-tptn-network="<?php echo esc_attr( is_network_admin() ? '1' : '0' ); ?>">
114 <ul class="nav-tab-wrapper" style="padding:0; border-bottom: 1px solid #ccc;">
115 <?php
116 foreach ( $tabs as $tab_id => $tab_name ) {
117 $tab_class = 'nav-tab';
118 $tab_style = '';
119
120 // Check if this tab should be hidden initially.
121 if ( isset( $tab_name['hide'] ) && $tab_name['hide'] ) {
122 $tab_style = 'display:none;';
123 }
124
125 // Add custom class if specified.
126 if ( isset( $tab_name['class'] ) ) {
127 $tab_class .= ' ' . $tab_name['class'];
128 }
129
130 echo '<li style="padding:0; border:0; margin:0;"><a href="#' . esc_attr( $tab_id ) . '" title="' . esc_attr( $tab_name['title'] ) . '" class="' . esc_attr( $tab_class ) . '" style="' . esc_attr( $tab_style ) . '">';
131 echo esc_html( $tab_name['title'] );
132 echo '</a></li>';
133
134 }
135 ?>
136 </ul>
137
138 <form method="post" action="options.php">
139
140 <?php foreach ( $tabs as $tab_id => $tab_name ) : ?>
141
142 <div id="<?php echo esc_attr( $tab_id ); ?>" data-tptn-tab="<?php echo esc_attr( $tab_id ); ?>" data-tptn-loaded="<?php echo esc_attr( $tab_id === $initial_tab ? '1' : '0' ); ?>">
143 <div class="tptn-dashboard-tab-content">
144 <?php
145 $output = '';
146 if ( empty( $tab_name['hide'] ) && $tab_id === $initial_tab ) {
147 $tab_name['network'] = is_network_admin();
148 $output = $this->display_popular_posts( $tab_name );
149 } elseif ( empty( $tab_name['hide'] ) ) {
150 $output = '<p class="tptn-dashboard-tab-loading" aria-live="polite">' . esc_html__( 'Loading…', 'top-10' ) . '</p>';
151 }
152 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
153 ?>
154 </div>
155
156 <div style="font-weight:bold;padding:5px;">
157
158 <?php
159 $query_args = array(
160 'order' => 'desc',
161 );
162 $daily = ( isset( $tab_name['daily'] ) ) ? $tab_name['daily'] : true;
163
164 if ( $daily ) {
165 $query_args['orderby'] = 'daily_count';
166
167 if ( ! empty( $tab_name['from_date'] ) ) {
168 $query_args['post-date-filter-from'] = gmdate( 'd+M+Y', strtotime( $tab_name['from_date'] ) );
169 }
170 if ( ! empty( $tab_name['to_date'] ) ) {
171 $query_args['post-date-filter-to'] = gmdate( 'd+M+Y', strtotime( $tab_name['to_date'] ) );
172 }
173 } else {
174 $query_args['orderby'] = 'total_count';
175 }
176 $base_url = is_network_admin() ? network_admin_url( 'admin.php?page=tptn_network_pop_posts_page' ) : admin_url( 'admin.php?page=tptn_popular_posts' );
177 $url = add_query_arg( $query_args, $base_url );
178
179 ?>
180
181 <a href="<?php echo esc_url( $url ); ?>"><?php esc_html_e( 'View all popular posts', 'top-10' ); ?> &raquo;</a>
182
183 </div>
184
185 </div><!-- /#tab_id-->
186
187 <?php endforeach; ?>
188
189 </form>
190 </div>
191
192 </div><!-- /#post-body-content -->
193
194 <div id="postbox-container-1" class="postbox-container">
195
196 <div id="side-sortables" class="meta-box-sortables ui-sortable">
197 <?php include_once 'sidebar.php'; ?>
198 </div><!-- /#side-sortables -->
199
200 </div><!-- /#postbox-container-1 -->
201 </div><!-- /#post-body -->
202 <br class="clear" />
203 </div><!-- /#poststuff -->
204
205 </div><!-- /.wrap -->
206
207 <?php
208 echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
209 }
210
211 /**
212 * Admin Menu.
213 *
214 * @since 3.0.0
215 */
216 public function admin_menu() {
217 $roles = wp_parse_list( \tptn_get_option( 'show_dashboard_to_roles' ) );
218
219 $this->parent_id = add_menu_page(
220 esc_html__( 'Top 10 Dashboard', 'top-10' ),
221 esc_html__( 'Top 10', 'top-10' ),
222 Settings_API::get_capability_for_menu( $roles ),
223 'tptn_dashboard',
224 array( $this, 'render_page' ),
225 'dashicons-editor-ol'
226 );
227
228 add_submenu_page(
229 'tptn_dashboard',
230 esc_html__( 'Top 10 Dashboard', 'top-10' ),
231 esc_html__( 'Dashboard', 'top-10' ),
232 Settings_API::get_capability_for_menu( $roles ),
233 'tptn_dashboard',
234 array( $this, 'render_page' )
235 );
236
237 add_action( 'load-' . $this->parent_id, array( $this, 'help_tabs' ) );
238 }
239
240 /**
241 * Network Admin Menu.
242 *
243 * @since 4.2.0
244 */
245 public function network_admin_menu() {
246 $this->parent_id = add_menu_page(
247 esc_html__( 'Top 10 Dashboard', 'top-10' ),
248 esc_html__( 'Top 10', 'top-10' ),
249 'manage_network_options',
250 'tptn_dashboard',
251 array( $this, 'render_page' ),
252 'dashicons-editor-ol'
253 );
254
255 add_submenu_page(
256 'tptn_dashboard',
257 esc_html__( 'Top 10 Dashboard', 'top-10' ),
258 esc_html__( 'Dashboard', 'top-10' ),
259 'manage_network_options',
260 'tptn_dashboard',
261 array( $this, 'render_page' )
262 );
263
264 add_action( 'load-' . $this->parent_id, array( $this, 'help_tabs' ) );
265 }
266
267 /**
268 * Enqueue scripts in admin area.
269 *
270 * @since 3.0.0
271 *
272 * @param string $hook The current admin page.
273 */
274 public function admin_enqueue_scripts( $hook ) {
275
276 if ( $hook === $this->parent_id ) {
277 wp_enqueue_script( 'moment' );
278 wp_enqueue_script( 'top-ten-chart-js' );
279 wp_enqueue_script( 'top-ten-chart-datalabels-js' );
280 wp_enqueue_script( 'top-ten-chartjs-adapter-luxon-js' );
281 wp_enqueue_script( 'top-ten-chart-data-js' );
282 wp_enqueue_script( 'top-ten-admin-js' );
283
284 $enable_bar_click = function_exists( '\\WebberZone\\Top_Ten\\tptn_freemius' ) && \WebberZone\Top_Ten\tptn_freemius()->can_use_premium_code();
285
286 $stats_base_url = is_network_admin()
287 ? network_admin_url( 'admin.php?page=tptn_network_pop_posts_page' )
288 : admin_url( 'admin.php?page=tptn_popular_posts' );
289
290 wp_localize_script(
291 'top-ten-chart-data-js',
292 'tptn_chart_data',
293 array(
294 'security' => wp_create_nonce( 'tptn-dashboard' ),
295 'datasetlabel' => __( 'Visits', 'top-10' ),
296 'charttitle' => __( 'Daily Visits', 'top-10' ),
297 'network' => is_network_admin() ? 1 : 0,
298 'enableBarClick' => $enable_bar_click ? 1 : 0,
299 'statsUrl' => $stats_base_url,
300 'clickBarHint' => __( 'Click to view top posts for this day', 'top-10' ),
301 'tabs_url' => admin_url( 'admin-ajax.php' ),
302 'tab_error' => __( 'Unable to load this tab. Please refresh the page and try again.', 'top-10' ),
303 )
304 );
305 wp_enqueue_style( 'top-ten-admin-css' );
306 }
307 }
308
309 /**
310 * Fetch chart data for visits over time.
311 *
312 * @since 4.2.0
313 *
314 * @param string $from_date Start date in Y-m-d format.
315 * @param string $to_date End date in Y-m-d format.
316 * @param bool $network Whether to fetch network-wide data.
317 * @return array Chart data with date and visits.
318 */
319 public static function fetch_visits_by_date( $from_date, $to_date, $network = false ) {
320 global $wpdb;
321
322 $date_range = self::get_datetime_range( $from_date, $to_date );
323
324 if ( $network ) {
325 $sql = $wpdb->prepare(
326 " SELECT SUM(cntaccess) AS visits, DATE(dp_date) as date
327 FROM {$wpdb->base_prefix}top_ten_daily
328 WHERE dp_date >= %s
329 AND dp_date < %s
330 GROUP BY date
331 ORDER BY date ASC
332 ",
333 $date_range['from'],
334 $date_range['until']
335 );
336 } else {
337 $blog_id = get_current_blog_id();
338
339 $sql = $wpdb->prepare(
340 " SELECT SUM(cntaccess) AS visits, DATE(dp_date) as date
341 FROM {$wpdb->base_prefix}top_ten_daily
342 WHERE dp_date >= %s
343 AND dp_date < %s
344 AND blog_id = %d
345 GROUP BY date
346 ORDER BY date ASC
347 ",
348 $date_range['from'],
349 $date_range['until'],
350 $blog_id
351 );
352 }
353
354 $result = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
355
356 $data = array();
357 foreach ( $result as $row ) {
358 $data[] = $row;
359 }
360
361 return $data;
362 }
363
364 /**
365 * Convert inclusive calendar dates into a half-open datetime range.
366 *
367 * @since 4.5.0
368 *
369 * @param string $from_date Inclusive start date.
370 * @param string $to_date Inclusive end date.
371 * @return array{from:string,until:string} Datetime boundaries.
372 */
373 private static function get_datetime_range( $from_date, $to_date ) {
374 return array(
375 'from' => gmdate( 'Y-m-d 00:00:00', strtotime( $from_date ) ),
376 'until' => gmdate( 'Y-m-d 00:00:00', strtotime( $to_date . ' +1 day' ) ),
377 );
378 }
379
380 /**
381 * Function to add an action to search for tags using Ajax.
382 *
383 * @since 3.0.0
384 */
385 public function get_chart_data() {
386 $roles = wp_parse_list( \tptn_get_option( 'show_dashboard_to_roles' ) );
387
388 if ( ! current_user_can( Settings_API::get_capability_for_menu( $roles ) ) ) {
389 wp_die();
390 }
391 check_ajax_referer( 'tptn-dashboard', 'security' );
392
393 // Add date selector.
394 $to_date = isset( $_REQUEST['to_date'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['to_date'] ) ) : current_time( 'd M Y' );
395 $from_date = isset( $_REQUEST['from_date'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['from_date'] ) ) : gmdate( 'd M Y', strtotime( '-1 month' ) );
396
397 $post_date_from = gmdate( 'Y-m-d', strtotime( $from_date ) );
398 $post_date_to = gmdate( 'Y-m-d', strtotime( $to_date ) );
399
400 $network_request = is_multisite() && isset( $_REQUEST['network'] ) && 1 === (int) $_REQUEST['network'];
401
402 $data = self::fetch_visits_by_date( $post_date_from, $post_date_to, $network_request );
403
404 echo wp_json_encode( $data );
405 wp_die();
406 }
407
408 /**
409 * Load one historical dashboard tab.
410 *
411 * @since 4.5.0
412 */
413 public function get_dashboard_tab() {
414 $is_network = is_multisite() && isset( $_REQUEST['network'] ) && 1 === (int) $_REQUEST['network'];
415 $roles = wp_parse_list( \tptn_get_option( 'show_dashboard_to_roles' ) );
416 $capability = $is_network ? 'manage_network_options' : Settings_API::get_capability_for_menu( $roles );
417
418 if ( ! current_user_can( $capability ) ) {
419 wp_send_json_error( array( 'message' => __( 'You are not allowed to view this data.', 'top-10' ) ), 403 );
420 }
421
422 check_ajax_referer( 'tptn-dashboard', 'security' );
423
424 $tab_id = isset( $_REQUEST['tab'] ) ? sanitize_key( $_REQUEST['tab'] ) : '';
425 $tabs = $this->get_tabs();
426 if ( '' === $tab_id || ! isset( $tabs[ $tab_id ] ) || ! empty( $tabs[ $tab_id ]['hide'] ) ) {
427 wp_send_json_error( array( 'message' => __( 'The requested dashboard tab is not available.', 'top-10' ) ), 400 );
428 }
429
430 $args = $tabs[ $tab_id ];
431 $args['network'] = $is_network;
432
433 wp_send_json_success(
434 array(
435 'html' => $this->display_popular_posts( $args ),
436 )
437 );
438 }
439
440
441 /**
442 * Array containing the settings' sections.
443 *
444 * @since 3.0.0
445 *
446 * @return array Settings array
447 */
448 public function get_tabs() {
449 $tabs = array(
450 'today' => array(
451 'title' => __( 'Today', 'top-10' ),
452 'from_date' => current_time( 'd M Y' ),
453 'to_date' => current_time( 'd M Y' ),
454 ),
455 'yesterday' => array(
456 'title' => __( 'Yesterday', 'top-10' ),
457 'from_date' => gmdate( 'd M Y', strtotime( '-1 day' ) ),
458 'to_date' => gmdate( 'd M Y', strtotime( '-1 day' ) ),
459 ),
460 'lastweek' => array(
461 'title' => __( 'Last 7 days', 'top-10' ),
462 'from_date' => gmdate( 'd M Y', strtotime( '-6 days' ) ),
463 'to_date' => current_time( 'd M Y' ),
464 ),
465 'lastfortnight' => array(
466 'title' => __( 'Last 14 days', 'top-10' ),
467 'from_date' => gmdate( 'd M Y', strtotime( '-13 days' ) ),
468 'to_date' => current_time( 'd M Y' ),
469 ),
470 'lastmonth' => array(
471 'title' => __( 'Last 30 days', 'top-10' ),
472 'from_date' => gmdate( 'd M Y', strtotime( '-29 days' ) ),
473 'to_date' => current_time( 'd M Y' ),
474 ),
475 'overall' => array(
476 'title' => __( 'All time', 'top-10' ),
477 'daily' => false,
478 ),
479 );
480
481 /**
482 * Filters the tabs for the admin dashboard.
483 *
484 * @since 4.1.0
485 *
486 * @param array $tabs Array of tabs.
487 */
488 $tabs = apply_filters( 'tptn_admin_dashboard_tabs', $tabs );
489
490 return $tabs;
491 }
492
493 /**
494 * Get popular posts for a date range.
495 *
496 * @since 3.0.0
497 *
498 * @param string|array $args {
499 * Optional. Array or string of Query parameters.
500 *
501 * @type bool $daily Set to true to get the daily/custom period posts. False for overall.
502 * @type string $from_date From date. A date/time string.
503 * @type int $numberposts Number of posts to fetch.
504 * @type string $to_date To date. A date/time string.
505 * }
506 * @return string HTML table with popular posts.
507 */
508 public function display_popular_posts( $args = array() ) {
509 $output = '';
510
511 $defaults = array(
512 'daily' => true,
513 'from_date' => null,
514 'numberposts' => 20,
515 'to_date' => null,
516 );
517 $args = wp_parse_args( $args, $defaults );
518
519 $results = $this->get_popular_posts( $args );
520
521 ob_start();
522 $explicit_network = isset( $args['network'] ) ? (bool) $args['network'] : null;
523 $is_network = ( null !== $explicit_network ) ? $explicit_network : ( is_multisite() && is_network_admin() );
524 if ( $results ) :
525 ?>
526
527 <table class="widefat striped">
528 <thead>
529 <tr>
530 <th><?php esc_html_e( 'Post', 'top-10' ); ?></th>
531 <?php if ( $is_network ) : ?>
532 <th><?php esc_html_e( 'Site', 'top-10' ); ?></th>
533 <?php endif; ?>
534 <th style="text-align:center;"><?php esc_html_e( 'Visits', 'top-10' ); ?></th>
535 </tr>
536 </thead>
537 <tbody>
538 <?php
539 foreach ( $results as $result ) :
540 $visits_int = (int) $result['visits'];
541 $visits = \WebberZone\Top_Ten\Util\Helpers::number_format_i18n( $visits_int );
542 $context_key = class_exists( 'WebberZone\\Top_Ten\\Pro\\Sitewide_Database' ) && \WebberZone\Top_Ten\Pro\Sitewide_Database::is_available()
543 ? \WebberZone\Top_Ten\Pro\Sitewide_Database::get_context_key( $result['ID'], $result['blog_id'] ?? null )
544 : '';
545 if ( $is_network && isset( $result['blog_id'] ) ) {
546 $post = '' === $context_key ? get_blog_post( (int) $result['blog_id'], (int) $result['ID'] ) : null;
547 $permalink = '' === $context_key ? ( is_multisite() ? get_blog_permalink( (int) $result['blog_id'], (int) $result['ID'] ) : get_permalink( (int) $result['ID'] ) ) : '';
548 } else {
549 $post = '' === $context_key ? get_post( (int) $result['ID'] ) : null;
550 $permalink = '' === $context_key ? get_permalink( (int) $result['ID'] ) : '';
551 }
552 if ( ! $post && '' === $context_key ) {
553 continue;
554 }
555 $title = '' !== $context_key ? \WebberZone\Top_Ten\Pro\Sitewide_Database::get_context_label( $context_key ) : get_the_title( $post );
556 ?>
557 <tr>
558 <td>
559 <?php if ( '' !== $context_key ) : ?>
560 <?php echo esc_html( $title ); ?> <span class="tptn-sitewide-label">(<?php esc_html_e( 'site-wide', 'top-10' ); ?>)</span>
561 <?php else : ?>
562 <a href="<?php echo esc_url( $permalink ); ?>" target="_blank"><?php echo esc_html( $title ); ?></a>
563 <?php endif; ?>
564 </td>
565 <?php if ( $is_network && isset( $result['blog_id'] ) ) : ?>
566 <td>
567 <?php
568 $blog_details = get_blog_details( (int) $result['blog_id'] );
569 if ( $blog_details ) {
570 $site_url = add_query_arg(
571 array(
572 'page' => 'tptn_popular_posts',
573 ),
574 get_admin_url( (int) $result['blog_id'], 'admin.php' )
575 );
576 ?>
577 <a href="<?php echo esc_url( $site_url ); ?>" target="_blank"><?php echo esc_html( $blog_details->blogname ); ?></a>
578 <?php
579 } else {
580 /* translators: %d is the blog ID */
581 echo esc_html( sprintf( __( 'Blog ID: %d', 'top-10' ), (int) $result['blog_id'] ) );
582 }
583 ?>
584 </td>
585 <?php endif; ?>
586 <td style="text-align:center;"><?php echo esc_html( $visits ); ?></td>
587 </tr>
588
589 <?php endforeach; ?>
590 </tbody>
591 <?php
592 $colspan = $is_network ? 2 : 1;
593 $total_visits_formatted = \WebberZone\Top_Ten\Util\Helpers::number_format_i18n( $this->get_period_total_visits( $args ) );
594 $label = $is_network ? __( 'Total network visits for this period', 'top-10' ) : __( 'Total visits for this period', 'top-10' );
595 ?>
596 <tfoot>
597 <tr>
598 <th colspan="<?php echo esc_attr( (string) $colspan ); ?>" style="text-align:right;"><?php echo esc_html( $label ); ?></th>
599 <th style="text-align:center;"><?php echo esc_html( $total_visits_formatted ); ?></th>
600 </tr>
601 </tfoot>
602 </table>
603
604 <?php else : ?>
605
606 <?php esc_html_e( 'Sorry, no popular posts found.', 'top-10' ); ?>
607
608 <?php endif; ?>
609
610 <?php
611
612 $output = ob_get_clean();
613 return $output;
614 }
615
616 /**
617 * Get the raw total visits for a period, matching the chart methodology.
618 *
619 * @since 4.3.0
620 *
621 * @param array $args Query parameters (daily, from_date, to_date, blog_id, network).
622 * @return int Total visits.
623 */
624 public function get_period_total_visits( $args = array() ) {
625 global $wpdb;
626
627 $defaults = array(
628 'blog_id' => get_current_blog_id(),
629 'daily' => true,
630 'from_date' => null,
631 'to_date' => null,
632 );
633 $args = wp_parse_args( $args, $defaults );
634
635 $explicit_network = isset( $args['network'] ) ? (bool) $args['network'] : null;
636 $is_network = ( null !== $explicit_network ) ? $explicit_network : ( is_multisite() && is_network_admin() );
637 $table_name = Database::get_table( $args['daily'] );
638
639 $has_dates = ! empty( $args['from_date'] ) && ! empty( $args['to_date'] );
640 $date_range = $has_dates ? self::get_datetime_range( $args['from_date'], $args['to_date'] ) : array();
641
642 if ( $args['daily'] && $has_dates && $is_network ) {
643 $sql = $wpdb->prepare(
644 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
645 "SELECT SUM(cntaccess) FROM {$table_name} WHERE dp_date >= %s AND dp_date < %s",
646 $date_range['from'],
647 $date_range['until']
648 );
649 } elseif ( $args['daily'] && $has_dates ) {
650 $blog_id = (int) $args['blog_id'];
651 $sql = $wpdb->prepare(
652 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
653 "SELECT SUM(cntaccess) FROM {$table_name} WHERE dp_date >= %s AND dp_date < %s AND blog_id = %d",
654 $date_range['from'],
655 $date_range['until'],
656 $blog_id
657 );
658 } elseif ( $is_network ) {
659 $sql = "SELECT SUM(cntaccess) FROM {$table_name}"; // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
660 } else {
661 $blog_id = (int) $args['blog_id'];
662 // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
663 $sql = $wpdb->prepare( "SELECT SUM(cntaccess) FROM {$table_name} WHERE blog_id = %d", $blog_id );
664 }
665
666 $total = $wpdb->get_var( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
667
668 return (int) $total;
669 }
670
671 /**
672 * Retrieve the popular posts.
673 *
674 * @since 3.0.0
675 *
676 * @param string|array $args {
677 * Optional. Array or string of Query parameters.
678 *
679 * @type array|string $blog_id An array or comma-separated string of blog IDs.
680 * @type bool $daily Set to true to get the daily/custom period posts. False for overall.
681 * @type string $from_date From date. A date/time string.
682 * @type int $numberposts Number of posts to fetch.
683 * @type int $offset Offset.
684 * @type string $to_date To date. A date/time string.
685 * }
686 * @return array Array of post objects.
687 */
688 public function get_popular_posts( $args = array() ) {
689 global $wpdb;
690
691 // Initialise some variables.
692 $fields = array();
693 $where = '';
694 $join = '';
695 $groupby = '';
696 $orderby = '';
697 $limits = '';
698
699 $defaults = array(
700 'blog_id' => get_current_blog_id(),
701 'daily' => true,
702 'from_date' => null,
703 'numberposts' => 20,
704 'offset' => 0,
705 'to_date' => null,
706 );
707 $args = wp_parse_args( $args, $defaults );
708
709 $explicit_network = isset( $args['network'] ) ? (bool) $args['network'] : null;
710 $is_network = ( null !== $explicit_network ) ? $explicit_network : ( is_multisite() && is_network_admin() );
711 $table_name = Database::get_table( $args['daily'] );
712 $date_range = ( $args['daily'] && ! empty( $args['from_date'] ) && ! empty( $args['to_date'] ) )
713 ? self::get_datetime_range( $args['from_date'], $args['to_date'] )
714 : array();
715 $sitewide_ids = class_exists( 'WebberZone\\Top_Ten\\Pro\\Sitewide_Database' ) && \WebberZone\Top_Ten\Pro\Sitewide_Database::is_available() ? \WebberZone\Top_Ten\Pro\Sitewide_Database::get_context_ids() : array();
716 $sitewide_sql = empty( $sitewide_ids ) ? '0=1' : $table_name . '.postnumber IN (' . implode( ',', array_map( 'intval', $sitewide_ids ) ) . ')';
717
718 if ( $is_network ) {
719 // Network-wide: aggregate across all blogs.
720 $fields[] = ( $args['daily'] ) ? "SUM({$table_name}.cntaccess) as visits" : "{$table_name}.cntaccess as visits";
721 $fields[] = "{$table_name}.postnumber as ID";
722 $fields[] = "{$table_name}.blog_id as blog_id";
723
724 $fields = implode( ', ', $fields );
725
726 $where = ' 1=1 ';
727
728 if ( ! empty( $date_range ) ) {
729 $where .= $wpdb->prepare( " AND {$table_name}.dp_date >= %s AND {$table_name}.dp_date < %s ", $date_range['from'], $date_range['until'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
730 }
731
732 $groupby = ' ';
733 $groupby = " GROUP BY {$table_name}.postnumber, {$table_name}.blog_id ";
734
735 $orderby = ' visits DESC ';
736 $orderby = " ORDER BY {$orderby} ";
737
738 $limits = $wpdb->prepare( ' LIMIT %d, %d ', $args['offset'], $args['numberposts'] );
739
740 $sql = "SELECT $fields FROM {$table_name} WHERE $where $groupby $orderby $limits";
741 } else {
742 // Single site: existing behaviour filtered by current blog.
743 $fields[] = ( $args['daily'] ) ? "SUM({$table_name}.cntaccess) as visits" : "{$table_name}.cntaccess as visits";
744 $fields[] = "{$table_name}.postnumber as ID";
745
746 $fields = implode( ', ', $fields );
747
748 // Create the JOIN clause.
749 $join = " LEFT JOIN {$wpdb->posts} ON {$table_name}.postnumber={$wpdb->posts}.ID ";
750
751 // Create the base WHERE clause.
752 $where = $wpdb->prepare( " AND {$table_name}.blog_id = %d ", $args['blog_id'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
753 $where .= " AND (($wpdb->posts.post_status = 'publish' OR $wpdb->posts.post_status = 'inherit') OR {$sitewide_sql}) "; // Show published posts, attachments and site-wide contexts.
754 $where .= " AND (($wpdb->posts.post_type <> 'revision' ) OR {$sitewide_sql}) "; // No revisions.
755
756 if ( ! empty( $date_range ) ) {
757 $where .= $wpdb->prepare( " AND {$table_name}.dp_date >= %s AND {$table_name}.dp_date < %s ", $date_range['from'], $date_range['until'] ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
758 }
759
760 // Create the base GROUP BY clause.
761 if ( $args['daily'] ) {
762 $groupby = " {$table_name}.postnumber";
763 }
764
765 // Create the base ORDER BY clause.
766 $orderby = ' visits DESC ';
767 $orderby = " ORDER BY {$orderby} ";
768
769 // Create the base LIMITS clause.
770 $limits = $wpdb->prepare( ' LIMIT %d, %d ', $args['offset'], $args['numberposts'] );
771
772 if ( ! empty( $groupby ) ) {
773 $groupby = " GROUP BY {$groupby} ";
774 }
775
776 $sql = "SELECT DISTINCT $fields FROM {$table_name} $join WHERE 1=1 $where $groupby $orderby $limits";
777 }
778
779 $result = $wpdb->get_results( $sql, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
780
781 return $result;
782 }
783
784 /**
785 * Generates the help tabs.
786 *
787 * @since 3.0.0
788 */
789 public function help_tabs() {
790
791 $screen = get_current_screen();
792
793 $screen->set_help_sidebar(
794 /* translators: 1: Support link. */
795 '<p>' . sprintf( __( 'For more information or how to get support visit the <a href="%1$s">WebberZone support site</a>.', 'top-10' ), esc_url( 'https://webberzone.com/support/' ) ) . '</p>' .
796 /* translators: 1: Forum link. */
797 '<p>' . sprintf( __( 'Support queries should be posted in the <a href="%1$s">WordPress.org support forums</a>.', 'top-10' ), esc_url( 'https://wordpress.org/support/plugin/top-10' ) ) . '</p>' .
798 '<p>' . sprintf(
799 /* translators: 1: Github Issues link, 2: Github page. */
800 __( '<a href="%1$s">Post an issue</a> on <a href="%2$s">GitHub</a> (bug reports only).', 'top-10' ),
801 esc_url( 'https://github.com/WebberZone/top-10/issues' ),
802 esc_url( 'https://github.com/WebberZone/top-10' )
803 ) . '</p>'
804 );
805
806 $screen->add_help_tab(
807 array(
808 'id' => 'tptn-dashboard-general',
809 'title' => __( 'General', 'top-10' ),
810 'content' =>
811 '<p>' . __( 'This screen displays the historical traffic on your site.', 'top-10' ) . '</p>' .
812 /* translators: 1: Constant holding number of days data is stored. */
813 '<p>' . sprintf( __( 'The data is pulled from the daily tables in the database. If you have enabled maintenance then the amount of historical data that is available will be limited to %d days.', 'top-10' ), TOP_TEN_STORE_DATA ) . '</p>' .
814 '<p>' . __( 'You can change this by setting the constant TOP_TEN_STORE_DATA to the number of days of your choice in your wp-config.php.', 'top-10' ) . '</p>',
815 )
816 );
817 }
818 }
819