PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / trunk
WP Popular Posts vtrunk
4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 5.0.0 5.0.1 5.0.2 5.1.0 5.2.0 5.2.1 5.2.2 5.2.3 5.2.4 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.2.0 6.2.1 6.3.0 6.3.1 6.3.2 6.3.3 6.3.4 6.4.0 6.4.1 6.4.2 7.0.0 7.0.1 7.1.0 7.2.0 7.3.0 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.3.7 7.3.8 7.4.0 trunk 2.3.7 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.2 4.0.3 4.0.5 4.0.6
wordpress-popular-posts / src / Admin / Admin.php
wordpress-popular-posts / src / Admin Last commit date
Admin.php 3 weeks ago ListColumnTotalViews.php 3 weeks ago admin-page.php 3 weeks ago screen-debug.php 3 weeks ago screen-stats.php 3 weeks ago screen-tools.php 3 weeks ago
Admin.php
1356 lines
1 <?php
2 /**
3 * The admin-facing functionality of the plugin.
4 *
5 * Defines hooks to enqueue the admin-specific stylesheet and JavaScript,
6 * plugin settings and other admin stuff.
7 *
8 * @package WordPressPopularPosts
9 * @subpackage WordPressPopularPosts/Admin
10 * @author Hector Cabrera <me@cabrerahector.com>
11 */
12
13 namespace WordPressPopularPosts\Admin;
14
15 use WordPressPopularPosts\{Helper, Image, Output, Query};
16
17 class Admin {
18
19 /**
20 * Slug of the plugin screen.
21 *
22 * @since 3.0.0
23 * @var string
24 */
25 protected $screen_hook_suffix = null;
26
27 /**
28 * Plugin options.
29 *
30 * @var array $config
31 * @access private
32 */
33 private $config;
34
35 /**
36 * Image object
37 *
38 * @since 4.0.2
39 * @var WordPressPopularPosts\Image
40 */
41 private $thumbnail;
42
43 /**
44 * Construct.
45 *
46 * @since 5.0.0
47 * @param array $config Admin settings.
48 * @param \WordPressPopularPosts\Image $thumbnail Image class.
49 */
50 public function __construct(array $config, Image $thumbnail)
51 {
52 $this->config = $config;
53 $this->thumbnail = $thumbnail;
54
55 // Delete old data on demand
56 if ( 1 == $this->config['tools']['log']['limit'] ) {
57 if ( ! wp_next_scheduled('wpp_cache_event') ) {
58 $midnight = strtotime('midnight') - ( get_option('gmt_offset') * HOUR_IN_SECONDS ) + DAY_IN_SECONDS;
59 wp_schedule_event($midnight, 'daily', 'wpp_cache_event');
60 }
61 } else {
62 // Remove the scheduled event if exists
63 $timestamp = wp_next_scheduled('wpp_cache_event');
64
65 if ( $timestamp ) {
66 wp_unschedule_event($timestamp, 'wpp_cache_event');
67 }
68 }
69
70 // Allow WP themers / coders to override data sampling status (active/inactive)
71 $this->config['tools']['sampling']['active'] = apply_filters('wpp_data_sampling', $this->config['tools']['sampling']['active']);
72
73 if (
74 ! ( wp_using_ext_object_cache() && defined('WPP_CACHE_VIEWS') && WPP_CACHE_VIEWS ) // Not using a persistent object cache
75 && ! $this->config['tools']['sampling']['active'] // Not using Data Sampling
76 ) {
77 // Schedule performance nag
78 if ( ! wp_next_scheduled('wpp_maybe_performance_nag') ) {
79 wp_schedule_event(time(), 'hourly', 'wpp_maybe_performance_nag');
80 }
81 } else {
82 // Remove the scheduled performance nag if found
83 $timestamp = wp_next_scheduled('wpp_maybe_performance_nag');
84
85 if ( $timestamp ) {
86 wp_unschedule_event($timestamp, 'wpp_maybe_performance_nag');
87 }
88 }
89 }
90
91 /**
92 * WordPress public-facing hooks.
93 *
94 * @since 5.0.0
95 */
96 public function hooks()
97 {
98 // Hook fired when a new blog is activated on WP Multisite
99 add_action('wpmu_new_blog', [$this, 'activate_new_site']);
100 // Hook fired when a blog is deleted on WP Multisite
101 add_filter('wpmu_drop_tables', [$this, 'delete_site_data'], 10, 2);
102 // At-A-Glance
103 add_filter('dashboard_glance_items', [$this, 'at_a_glance_stats']);
104 add_action('admin_head', [$this, 'at_a_glance_stats_css']);
105 // Dashboard Trending Now widget
106 add_action('wp_dashboard_setup', [$this, 'add_dashboard_widgets']);
107 // Load WPP's admin styles and scripts
108 add_action('admin_enqueue_scripts', [$this, 'enqueue_assets']);
109 // Add admin screen
110 add_action('admin_menu', [$this, 'add_plugin_admin_menu']);
111 // Contextual help
112 add_action('admin_head', [$this, 'add_contextual_help']);
113 // Add plugin settings link
114 add_filter('plugin_action_links', [$this, 'add_plugin_settings_link'], 10, 2);
115 // Update chart
116 add_action('wp_ajax_wpp_update_chart', [$this, 'update_chart']);
117 // Get lists
118 add_action('wp_ajax_wpp_get_most_viewed', [$this, 'get_popular_items']);
119 add_action('wp_ajax_wpp_get_most_commented', [$this, 'get_popular_items']);
120 add_action('wp_ajax_wpp_get_trending', [$this, 'get_popular_items']);
121 // Reset plugin's default thumbnail
122 add_action('wp_ajax_wpp_reset_thumbnail', [$this, 'get_default_thumbnail']);
123 // Empty plugin's images cache
124 add_action('wp_ajax_wpp_clear_thumbnail', [$this, 'clear_thumbnails']);
125 // Flush cached thumbnail on featured image change/deletion
126 add_action('updated_post_meta', [$this, 'updated_post_meta'], 10, 4);
127 add_action('deleted_post_meta', [$this, 'deleted_post_meta'], 10, 4);
128 // Purge transients when sending post/page to trash
129 add_action('wp_trash_post', [$this, 'purge_data_cache']);
130 // Purge post data on post/page deletion
131 add_action('admin_init', [$this, 'purge_post_data']);
132 // Purge old data on demand
133 add_action('wpp_cache_event', [$this, 'purge_data']);
134 // Maybe performance nag
135 add_action('wpp_maybe_performance_nag', [$this, 'performance_check']);
136 add_action('wp_ajax_wpp_handle_performance_notice', [$this, 'handle_performance_notice']);
137 // Show notices
138 add_action('admin_notices', [$this, 'notices']);
139 // Show admin views column
140 add_action('admin_init', [$this, 'register_views_column']);
141 }
142
143 /**
144 * Renders a "Views" column on post list screens.
145 *
146 * @since 7.4.0
147 */
148 public function register_views_column()
149 {
150 if ( $this->config['tools']['views_column']['active'] ) {
151 new ListColumnTotalViews($this->config);
152 }
153 }
154
155 /**
156 * Checks whether a performance tweak may be necessary.
157 *
158 * @since 5.0.2
159 */
160 public function performance_check()
161 {
162 $performance_nag = get_option('wpp_performance_nag');
163
164 if ( ! $performance_nag ) {
165 $performance_nag = [
166 'status' => 0,
167 'last_checked' => null
168 ];
169 add_option('wpp_performance_nag', $performance_nag);
170 }
171
172 if ( 3 != $performance_nag['status'] ) { // 0 = inactive, 1 = active, 2 = remind me later, 3 = dismissed
173 global $wpdb;
174
175 $summary_table = "{$wpdb->prefix}popularpostssummary";
176
177 //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
178 $views_count = $wpdb->get_var(
179 $wpdb->prepare(
180 "SELECT IFNULL(SUM(pageviews), 0) AS views FROM %i WHERE view_datetime > DATE_SUB(%s, INTERVAL 1 HOUR);",
181 $summary_table,
182 Helper::now()
183 )
184 );
185 //phpcs:enable
186
187 // This site is probably a mid/high traffic one,
188 // display performance nag
189 if ( $views_count >= 420 ) {
190 if ( 0 == $performance_nag['status'] ) {
191 $performance_nag['status'] = 1;
192 $performance_nag['last_checked'] = Helper::timestamp();
193 update_option('wpp_performance_nag', $performance_nag);
194 }
195 }
196 }
197 }
198
199 /**
200 * Fired when a new blog is activated on WP Multisite.
201 *
202 * @since 3.0.0
203 * @param int $blog_id New blog ID
204 */
205 public function activate_new_site(int $blog_id)
206 {
207 if ( 1 !== did_action('wpmu_new_blog') ) {
208 return;
209 }
210
211 // run activation for the new blog
212 switch_to_blog($blog_id);
213 \WordPressPopularPosts\Activation\Activator::track_new_site();
214 // switch back to current blog
215 restore_current_blog();
216 }
217
218 /**
219 * Fired when a blog is deleted on WP Multisite.
220 *
221 * @since 4.0.0
222 * @param array $tables
223 * @param int $blog_id
224 * @return array
225 */
226 public function delete_site_data(array $tables, int $blog_id)
227 {
228 global $wpdb;
229
230 $tables[] = $wpdb->prefix . 'popularpostsdata';
231 $tables[] = $wpdb->prefix . 'popularpostssummary';
232
233 return $tables;
234 }
235
236 /**
237 * Display some statistics at the "At a Glance" box from the Dashboard.
238 *
239 * @since 4.1.0
240 */
241 public function at_a_glance_stats()
242 {
243 global $wpdb;
244
245 $glances = [];
246 $args = ['post', 'page'];
247 $post_type_placeholders = '%s, %s';
248
249 if (
250 isset($this->config['stats']['post_type'])
251 && ! empty($this->config['stats']['post_type'])
252 ) {
253 $args = array_map('trim', explode(',', $this->config['stats']['post_type']));
254 $post_type_placeholders = implode(', ', array_fill(0, count($args), '%s'));
255 }
256
257 $args[] = Helper::now();
258
259 $posts_table = "{$wpdb->prefix}posts";
260 $summary_table = "{$wpdb->prefix}popularpostssummary";
261
262 //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $post_type_placeholder is safe to use
263 $query = $wpdb->prepare(
264 "SELECT SUM(pageviews) AS total
265 FROM %i v LEFT JOIN %i p ON v.postid = p.ID
266 WHERE p.post_type IN({$post_type_placeholders}) AND p.post_status = 'publish' AND p.post_password = '' AND v.view_datetime > DATE_SUB(%s, INTERVAL 1 HOUR);",
267 [$summary_table, $posts_table, ...$args]
268 );
269 //phpcs:enable
270
271 $total_views = $wpdb->get_var($query); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- $query is built and prepared above
272 $total_views = (float) $total_views;
273
274 $pageviews = sprintf(
275 _n('%s view in the last hour', '%s views in the last hour', $total_views, 'wordpress-popular-posts'),
276 number_format_i18n($total_views)
277 );
278
279 if ( current_user_can('edit_published_posts') ) {
280 $glances[] = '<a class="wpp-views-count" href="' . admin_url('options-general.php?page=wordpress-popular-posts') . '">' . $pageviews . '</a>';
281 }
282 else {
283 $glances[] = '<span class="wpp-views-count">' . $pageviews . '</a>';
284 }
285
286 return $glances;
287 }
288
289 /**
290 * Add custom inline CSS styles for At a Glance stats.
291 *
292 * @since 4.1.0
293 */
294 public function at_a_glance_stats_css()
295 {
296 echo '<style>#dashboard_right_now a.wpp-views-count:before, #dashboard_right_now span.wpp-views-count:before {content: "\f177";}</style>';
297 }
298
299 /**
300 * Adds a widget to the dashboard.
301 *
302 * @since 5.0.0
303 */
304 public function add_dashboard_widgets()
305 {
306 if ( current_user_can('edit_published_posts') ) {
307 wp_add_dashboard_widget(
308 'wpp_trending_dashboard_widget',
309 __('Trending now', 'wordpress-popular-posts'),
310 [$this, 'trending_dashboard_widget']
311 );
312 }
313 }
314
315 /**
316 * Outputs the contents of our Trending Dashboard Widget.
317 *
318 * @since 5.0.0
319 */
320 public function trending_dashboard_widget()
321 {
322 ?>
323 <style>
324 #wpp_trending_dashboard_widget .inside {
325 overflow: hidden;
326 position: relative;
327 min-height: 150px;
328 padding-bottom: 22px;
329 }
330
331 #wpp_trending_dashboard_widget .inside::after {
332 position: absolute;
333 top: 0;
334 left: 0;
335 opacity: 0.2;
336 display: block;
337 content: '';
338 width: 100%;
339 height: 100%;
340 z-index: 1;
341 background-image: url('<?php echo esc_url(plugin_dir_url(dirname(dirname(__FILE__)))) . 'assets/images/flame.png'; ?>');
342 background-position: right bottom;
343 background-repeat: no-repeat;
344 background-size: 34% auto;
345 }
346
347 #wpp_trending_dashboard_widget .inside .no-data {
348 position: absolute;
349 top: calc(50% - 11px);
350 left: 50%;
351 z-index: 2;
352 margin: 0;
353 padding: 0;
354 width: 96%;
355 transform: translate(-50.0001%, -50.0001%);
356 }
357
358 #wpp_trending_dashboard_widget .inside .popular-posts-list,
359 #wpp_trending_dashboard_widget .inside p#wpp_read_more {
360 position: relative;
361 z-index: 2;
362 }
363
364 #wpp_trending_dashboard_widget .inside .popular-posts-list {
365 margin: 1em 0;
366 }
367
368 #wpp_trending_dashboard_widget .inside p#wpp_read_more {
369 position: absolute;
370 left: 0;
371 bottom: 0;
372 width: 100%;
373 text-align: center;
374 }
375 </style>
376 <?php
377 $args = [
378 'range' => 'custom',
379 'time_quantity' => 1,
380 'time_unit' => 'HOUR',
381 'post_type' => $this->config['stats']['post_type'],
382 'limit' => 5,
383 'stats_tag' => [
384 'views' => 1,
385 'comment_count' => 1
386 ]
387 ];
388 $options = apply_filters('wpp_trending_dashboard_widget_args', []);
389
390 if ( is_array($options) && ! empty($options) ) {
391 $args = Helper::merge_array_r($args, $options);
392 }
393
394 $query = new Query($args);
395 $posts = $query->get_posts();
396
397 $this->render_list($posts, 'trending');
398 echo '<p id="wpp_read_more"><a href="' . esc_url(admin_url('options-general.php?page=wordpress-popular-posts')) . '">' . esc_html(__('View more', 'wordpress-popular-posts')) . '</a><p>';
399
400 }
401
402 /**
403 * Enqueues admin facing assets.
404 *
405 * @since 5.0.0
406 */
407 public function enqueue_assets()
408 {
409 $screen = get_current_screen();
410
411 if ( isset($screen->id) ) {
412 if ( $screen->id == $this->screen_hook_suffix ) {
413 wp_enqueue_media();
414
415 wp_enqueue_script('chartjs', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/vendor/chart.3.8.0.min.js', [], WPP_VERSION);
416
417 wp_register_script('wpp-chart', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/chart.js', ['chartjs'], WPP_VERSION);
418 wp_localize_script('wpp-chart', 'wpp_chart_params', [
419 'colors' => $this->get_admin_color_scheme()
420 ]);
421 wp_enqueue_script('wpp-chart');
422
423 wp_register_script('wordpress-popular-posts-admin-script', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/admin.js', [], WPP_VERSION, true);
424 wp_localize_script('wordpress-popular-posts-admin-script', 'wpp_admin_params', [
425 'label_media_upload_button' => __('Use this image', 'wordpress-popular-posts'),
426 'nonce' => wp_create_nonce('wpp_admin_nonce'),
427 'nonce_reset_thumbnails' => wp_create_nonce('wpp_nonce_reset_thumbnails'),
428 'text_confirm_image_cache_reset' => __('This operation will delete all cached thumbnails and cannot be undone.', 'wordpress-popular-posts'),
429 'text_image_cache_cleared' => __('Success! All files have been deleted!', 'wordpress-popular-posts'),
430 'text_image_cache_already_empty' => __('The thumbnail cache is already empty!', 'wordpress-popular-posts'),
431 'text_continue' => __('Do you want to continue?', 'wordpress-popular-posts'),
432 'text_insufficient_permissions' => __('Sorry, you do not have enough permissions to do this. Please contact the site administrator for support.', 'wordpress-popular-posts'),
433 'text_invalid_action' => __('Invalid action.', 'wordpress-popular-posts')
434 ]);
435 wp_enqueue_script('wordpress-popular-posts-admin-script');
436 }
437
438 if ( $screen->id == $this->screen_hook_suffix || 'dashboard' == $screen->id ) {
439 // Fontello icons
440 wp_enqueue_style('wpp-fontello', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/fontello.css', [], WPP_VERSION, 'all');
441 wp_enqueue_style('wordpress-popular-posts-admin-styles', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/admin.css', [], WPP_VERSION, 'all');
442 }
443 }
444
445 $performance_nag = get_option('wpp_performance_nag');
446
447 if (
448 isset($performance_nag['status'])
449 && 3 != $performance_nag['status'] // 0 = inactive, 1 = active, 2 = remind me later, 3 = dismissed
450 ) {
451 $now = Helper::timestamp();
452
453 // How much time has passed since the notice was last displayed?
454 $last_checked = isset($performance_nag['last_checked']) ? $performance_nag['last_checked'] : 0;
455
456 if ( $last_checked ) {
457 $last_checked = ($now - $last_checked) / (60 * 60);
458 }
459
460 if (
461 1 == $performance_nag['status']
462 || ( 2 == $performance_nag['status'] && $last_checked && $last_checked >= 24 )
463 ) {
464 wp_register_script('wpp-admin-notices', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/admin-notices.js', [], WPP_VERSION);
465 wp_localize_script('wpp-admin-notices', 'wpp_admin_notices_params', [
466 'nonce_performance_nag' => wp_create_nonce('wpp_nonce_performance_nag')
467 ]);
468 wp_enqueue_script('wpp-admin-notices');
469 }
470 }
471 }
472
473 /**
474 * Register the administration menu for this plugin into the WordPress Dashboard menu.
475 *
476 * @since 1.0.0
477 */
478 public function add_plugin_admin_menu()
479 {
480 $this->screen_hook_suffix = add_options_page(
481 'WP Popular Posts',
482 'WP Popular Posts',
483 'edit_published_posts',
484 'wordpress-popular-posts',
485 [$this, 'display_plugin_admin_page']
486 );
487 }
488
489 /**
490 * Render the settings page for this plugin.
491 *
492 * @since 1.0.0
493 */
494 public function display_plugin_admin_page()
495 {
496 include_once plugin_dir_path(__FILE__) . 'admin-page.php';
497 }
498
499 /**
500 * Adds contextual help menu.
501 *
502 * @since 4.0.0
503 */
504 public function add_contextual_help()
505 {
506 $screen = get_current_screen();
507
508 if ( isset($screen->id) && $screen->id == $this->screen_hook_suffix ){
509 $screen->add_help_tab(
510 [
511 'id' => 'wpp_help_overview',
512 'title' => __('Overview', 'wordpress-popular-posts'),
513 'content' => '<p>' . __("Welcome to WP Popular Posts' Dashboard! In this screen you will find statistics on what's popular on your site, tools to further tweak WPP to your needs, and more!", 'wordpress-popular-posts') . '</p>'
514 ]
515 );
516 $screen->add_help_tab(
517 [
518 'id' => 'wpp_help_donate',
519 'title' => __('Like this plugin?', 'wordpress-popular-posts'),
520 'content' => '
521 <p style="text-align: center;">' . __('Each donation motivates me to keep releasing free stuff for the WordPress community!', 'wordpress-popular-posts') . '</p>
522 <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top" style="margin: 0; padding: 0; text-align: center;">
523 <input type="hidden" name="cmd" value="_s-xclick">
524 <input type="hidden" name="hosted_button_id" value="RP9SK8KVQHRKS">
525 <input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" style="display: inline; margin: 0;">
526 <img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
527 </form>
528 <p style="text-align: center;">' . sprintf(__('You can <a href="%s" target="_blank">leave a review</a>, too!', 'wordpress-popular-posts'), 'https://wordpress.org/support/view/plugin-reviews/wordpress-popular-posts?rate=5#postform') . '</p>'
529 ]
530 );
531
532 // Help sidebar
533 $screen->set_help_sidebar(
534 sprintf(
535 __('<p><strong>For more information:</strong></p><ul><li><a href="%1$s">Documentation</a></li><li><a href="%2$s">Support</a></li></ul>', 'wordpress-popular-posts'),
536 'https://github.com/cabrerahector/wordpress-popular-posts/',
537 'https://wordpress.org/support/plugin/wordpress-popular-posts/'
538 )
539 );
540 }
541 }
542
543 /**
544 * Registers Settings link on plugin description.
545 *
546 * @since 2.3.3
547 * @param array $links
548 * @param string $file
549 * @return array
550 */
551 public function add_plugin_settings_link(array $links, string $file)
552 {
553 $plugin_file = 'wordpress-popular-posts/wordpress-popular-posts.php';
554
555 if (
556 is_plugin_active($plugin_file)
557 && $plugin_file == $file
558 ) {
559 array_unshift(
560 $links,
561 '<a href="' . admin_url('options-general.php?page=wordpress-popular-posts') . '">' . __('Settings') . '</a>', // phpcs:ignore WordPress.WP.I18n.MissingArgDomain -- We're using WordPress' translation here
562 '<a href="https://wordpress.org/support/plugin/wordpress-popular-posts/">' . __('Support', 'wordpress-popular-posts') . '</a>'
563 );
564 }
565
566 return $links;
567 }
568
569 /**
570 * Gets current admin color scheme.
571 *
572 * @since 4.0.0
573 * @return array
574 */
575 private function get_admin_color_scheme()
576 {
577 global $_wp_admin_css_colors;
578
579 if (
580 is_array($_wp_admin_css_colors)
581 && count($_wp_admin_css_colors)
582 ) {
583 $current_user = wp_get_current_user();
584 $color_scheme = get_user_option('admin_color', $current_user->ID);
585
586 if (
587 empty($color_scheme)
588 || ! isset($_wp_admin_css_colors[ $color_scheme])
589 ) {
590 $color_scheme = 'fresh';
591 }
592
593 if ( isset($_wp_admin_css_colors[$color_scheme]) && isset($_wp_admin_css_colors[$color_scheme]->colors) ) {
594 return $_wp_admin_css_colors[$color_scheme]->colors;
595 }
596
597 }
598
599 // Fallback, just in case
600 return ['#333', '#999', '#881111', '#a80000'];
601 }
602
603 /**
604 * Fetches chart data.
605 *
606 * @since 4.0.0
607 * @return string
608 */
609 public function get_chart_data(string $range = 'last7days', string $time_unit = 'HOUR', int $time_quantity = 24)
610 {
611 list($start_date, $end_date, $include_timestamps) = $this->get_dates($range, $time_unit, $time_quantity);
612 $date_range = Helper::get_date_range($start_date, $end_date, ($include_timestamps ? 'Y-m-d H:i:s' : 'Y-m-d'));
613 $views_data = $this->get_range_item_count($start_date, $end_date, 'views', $include_timestamps);
614 $views = [];
615 $comments_data = $this->get_range_item_count($start_date, $end_date, 'comments', $include_timestamps);
616 $comments = [];
617
618 if ( 'today' != $range ) {
619 foreach($date_range as $date) {
620 $key = date('Y-m-d', strtotime($date));
621 $views[] = ( ! isset($views_data[$key]) ) ? 0 : $views_data[$key]->pageviews;
622 $comments[] = ( ! isset($comments_data[$key]) ) ? 0 : $comments_data[$key]->comments;
623 }
624 } else {
625 $key = date('Y-m-d', strtotime($start_date));
626 $views[] = ( ! isset($views_data[$key]) ) ? 0 : $views_data[$key]->pageviews;
627 $comments[] = ( ! isset($comments_data[$key]) ) ? 0 : $comments_data[$key]->comments;
628 }
629
630 if ( $start_date != $end_date && 'today' != $range ) {
631 $label_date_format = $include_timestamps ? 'M, D d h:i:s a' : 'M, D d';
632 $label_date_range = date_i18n($label_date_format, strtotime($start_date)) . ' &mdash; ' . date_i18n($label_date_format, strtotime($end_date));
633 } else {
634 $label_date_range = date_i18n('M, D d', strtotime($start_date));
635 }
636
637 $total_views = array_sum($views);
638 $total_comments = array_sum($comments);
639
640 $label_summary = sprintf(_n('%s view', '%s views', $total_views, 'wordpress-popular-posts'), '<strong>' . number_format_i18n($total_views) . '</strong>') . ' / ' . sprintf(_n('%s comment', '%s comments', $total_comments, 'wordpress-popular-posts'), '<strong>' . number_format_i18n($total_comments) . '</strong>');
641
642 // Format labels
643 if ( 'today' != $range ) {
644 $date_range = array_map(function($d) {
645 return date_i18n('D d', strtotime($d));
646 }, $date_range);
647 } else {
648 $date_range = [date_i18n('D d', strtotime($date_range[0]))];
649 $comments = [array_sum($comments)];
650 $views = [array_sum($views)];
651 }
652
653 $response = [
654 'totals' => [
655 'label_summary' => $label_summary,
656 'label_date_range' => $label_date_range,
657 ],
658 'labels' => $date_range,
659 'datasets' => [
660 [
661 'label' => __('Comments', 'wordpress-popular-posts'),
662 'data' => $comments
663 ],
664 [
665 'label' => __('Views', 'wordpress-popular-posts'),
666 'data' => $views
667 ]
668 ]
669 ];
670
671 return json_encode($response);
672 }
673
674 /**
675 * Returns an array of dates.
676 *
677 * @since 5.0.0
678 * @return array|bool
679 */
680 private function get_dates(string $range = 'last7days', string $time_unit = 'HOUR', int $time_quantity = 24)
681 {
682 $start_date = '';
683 $end_date = '';
684
685 $include_timestamps = true;
686
687 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- 'start_date' and 'end_date' are date strings, and we're validating those below
688 if (
689 'custom' == $range
690 && isset($_GET['start_date'])
691 && ! empty($_GET['start_date'])
692 && isset($_GET['end_date'])
693 && ! empty($_GET['end_date'])
694 && Helper::is_valid_date($_GET['start_date'])
695 && Helper::is_valid_date($_GET['end_date'])
696 ) {
697
698 $start_date = $_GET['start_date'];
699 $end_date = $_GET['end_date'];
700
701 if ( strtotime($start_date) > strtotime($end_date) ) {
702 $start_date = $end_date;
703 }
704
705 $start_date .= ' 00:00:00';
706 $end_date .= ' 23:59:59';
707 }
708 // phpcs:enable
709
710 if ( ! $start_date ) {
711 list($dates, $datetimes, $include_timestamps) = Helper::get_dates(
712 $range,
713 $time_unit,
714 $time_quantity
715 );
716
717 if ( $include_timestamps ) {
718 list($start_date, $end_date) = $datetimes;
719 } else {
720 list($start_date, $end_date) = $dates;
721 }
722 }
723
724 return [$start_date, $end_date, $include_timestamps];
725 }
726
727 /**
728 * Returns an array of dates with views/comments count.
729 *
730 * @since 5.0.0
731 * @param string $start_date
732 * @param string $end_date
733 * @param string $item
734 * @param bool $include_timestamps
735 * @return array
736 */
737 public function get_range_item_count(string $start_date, string $end_date, string $item = 'views', bool $include_timestamps = false)
738 {
739 global $wpdb;
740
741 $args = array_map('trim', explode(',', $this->config['stats']['post_type']));
742
743 $types = get_post_types([
744 'public' => true
745 ], 'names' );
746 $types = array_values($types);
747
748 // Let's make sure we're getting valid post types
749 $args = array_intersect($types, $args);
750
751 if ( empty($args) ) {
752 $args = ['post', 'page'];
753 }
754
755 $post_type_placeholders = array_fill(0, count($args), '%s');
756
757 if ( $this->config['stats']['freshness'] ) {
758 $args[] = $start_date;
759 }
760
761 if ( $item === 'comments' && ! $include_timestamps ) {
762 $start_date .= ' 00:00:00';
763 $end_date .= ' 23:59:59';
764 }
765
766 // Append dates to arguments list
767 array_unshift($args, $start_date, $end_date);
768
769 $posts_table = "{$wpdb->posts}";
770
771 if ( $item == 'comments' ) {
772 $comments_table = "{$wpdb->comments}";
773
774 //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $post_type_placeholders is already prepared above
775 $query = $wpdb->prepare(
776 "SELECT DATE(`c`.`comment_date`) AS `c_date`, COUNT(*) AS `comments`
777 FROM %i c INNER JOIN %i p ON `c`.`comment_post_ID` = `p`.`ID`
778 WHERE (`c`.`comment_date` BETWEEN %s AND %s) AND `c`.`comment_approved` = '1' AND `p`.`post_type` IN (" . implode(', ', $post_type_placeholders) . ") AND `p`.`post_status` = 'publish' AND `p`.`post_password` = ''
779 " . ( $this->config['stats']['freshness'] ? ' AND `p`.`post_date` >= %s' : '' ) . '
780 GROUP BY `c_date` ORDER BY `c_date` DESC;',
781 [$comments_table, $posts_table, ...$args]
782 );
783 //phpcs:enable
784 } else {
785 $views_table = "{$wpdb->prefix}popularpostssummary";
786
787 $views_date_field_condition = $include_timestamps
788 ? '(`v`.`view_datetime` BETWEEN %s AND %s)'
789 : '(`v`.`view_date` BETWEEN %s AND %s)';
790
791 //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $post_type_placeholders is already prepared above
792 $query = $wpdb->prepare(
793 "SELECT `v`.`view_date`, SUM(`v`.`pageviews`) AS `pageviews`
794 FROM %i v INNER JOIN %i p ON `v`.`postid` = `p`.`ID`
795 WHERE " . $views_date_field_condition . " AND `p`.`post_type` IN (" . implode(', ', $post_type_placeholders) . ") AND `p`.`post_status` = 'publish' AND `p`.`post_password` = ''
796 " . ( $this->config['stats']['freshness'] ? ' AND `p`.`post_date` >= %s' : '' ) . '
797 GROUP BY `v`.`view_date` ORDER BY `v`.`view_date` DESC;',
798 [$views_table, $posts_table, ...$args]
799 );
800 //phpcs:enable
801 }
802
803 return $wpdb->get_results($query, OBJECT_K); //phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- at this point $query has been prepared already
804 }
805
806 /**
807 * Updates chart via AJAX.
808 *
809 * @since 4.0.0
810 */
811 public function update_chart()
812 {
813 $response = [
814 'status' => 'error'
815 ];
816 $nonce = isset($_GET['nonce']) ? $_GET['nonce'] : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a nonce
817
818 if ( wp_verify_nonce($nonce, 'wpp_admin_nonce') ) {
819
820 $valid_ranges = ['today', 'daily', 'last24hours', 'weekly', 'last7days', 'monthly', 'last30days', 'all', 'custom'];
821 $time_units = ['MINUTE', 'HOUR', 'DAY'];
822
823 $range = ( isset($_GET['range']) && in_array($_GET['range'], $valid_ranges) ) ? $_GET['range'] : 'last7days';
824 $time_quantity = ( isset($_GET['time_quantity']) && filter_var($_GET['time_quantity'], FILTER_VALIDATE_INT) ) ? $_GET['time_quantity'] : 24;
825 $time_unit = ( isset($_GET['time_unit']) && in_array(strtoupper($_GET['time_unit']), $time_units) ) ? $_GET['time_unit'] : 'hour';
826
827 $this->config['stats']['range'] = $range;
828 $this->config['stats']['time_quantity'] = $time_quantity;
829 $this->config['stats']['time_unit'] = $time_unit;
830
831 update_option('wpp_settings_config', $this->config);
832
833 $response = [
834 'status' => 'ok',
835 'data' => json_decode(
836 $this->get_chart_data($this->config['stats']['range'], $this->config['stats']['time_unit'], $this->config['stats']['time_quantity']),
837 true
838 )
839 ];
840 }
841
842 wp_send_json($response);
843 }
844
845 /**
846 * Fetches most viewed/commented/trending posts via AJAX.
847 *
848 * @since 5.0.0
849 */
850 public function get_popular_items()
851 {
852 $items = isset($_GET['items']) ? $_GET['items'] : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification happens below
853 $nonce = isset($_GET['nonce']) ? $_GET['nonce'] : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a nonce
854
855 if ( wp_verify_nonce($nonce, 'wpp_admin_nonce') ) {
856 $args = [
857 'range' => $this->config['stats']['range'],
858 'time_quantity' => $this->config['stats']['time_quantity'],
859 'time_unit' => $this->config['stats']['time_unit'],
860 'post_type' => $this->config['stats']['post_type'],
861 'freshness' => $this->config['stats']['freshness'],
862 'limit' => $this->config['stats']['limit'],
863 'stats_tag' => [
864 'date' => [
865 'active' => 1
866 ]
867 ]
868 ];
869
870 if ( 'most-commented' == $items ) {
871 $args['order_by'] = 'comments';
872 $args['stats_tag']['comment_count'] = 1;
873 $args['stats_tag']['views'] = 0;
874 } elseif ( 'trending' == $items ) {
875 $args['range'] = 'custom';
876 $args['time_quantity'] = 1;
877 $args['time_unit'] = 'HOUR';
878 $args['stats_tag']['comment_count'] = 1;
879 $args['stats_tag']['views'] = 1;
880 } else {
881 $args['stats_tag']['comment_count'] = 0;
882 $args['stats_tag']['views'] = 1;
883 }
884
885 if ( 'trending' != $items ) {
886 $datepicker_start_date = $_GET['start_date'] ?? null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a nonce
887 $datepicker_end_date = $_GET['end_date'] ?? null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is not a nonce
888
889 if (
890 'custom' == $args['range']
891 && Helper::is_valid_date($datepicker_start_date)
892 && Helper::is_valid_date($datepicker_end_date)
893 ) {
894 $start_date = $datepicker_start_date;
895 $end_date = $datepicker_end_date;
896
897 if ( strtotime($start_date) > strtotime($end_date) ) {
898 $start_date = $end_date;
899 }
900
901 $start_date .= ' 00:00:00';
902 $end_date .= ' 23:59:59';
903
904 $filter_by_date_range = function($join, $options) use ($items, $start_date, $end_date) {
905 global $wpdb;
906
907 if ( 'most-commented' == $items ) {
908 return $wpdb->prepare(
909 'INNER JOIN (SELECT comment_post_ID, COUNT(comment_post_ID) AS comment_count, comment_date FROM %i WHERE (comment_date BETWEEN %s AND %s) AND comment_approved = "1" GROUP BY comment_post_ID) c ON p.ID = c.comment_post_ID',
910 $wpdb->comments,
911 $start_date,
912 $end_date
913 );
914 }
915
916 return $wpdb->prepare(
917 'INNER JOIN (SELECT SUM(pageviews) AS pageviews, view_date, postid FROM %i WHERE (view_datetime BETWEEN %s AND %s) GROUP BY postid) v ON p.ID = v.postid',
918 $wpdb->prefix . 'popularpostssummary',
919 $start_date,
920 $end_date
921 );
922 };
923
924 add_filter('wpp_query_join', $filter_by_date_range, 1, 2);
925 }
926 }
927
928 $query = new Query($args);
929 $posts = $query->get_posts();
930
931 if ( 'trending' != $items ) {
932 remove_all_filters('wpp_query_join', 1);
933 }
934
935 $this->render_list($posts, $items);
936 }
937
938 wp_die();
939 }
940
941 /**
942 * Renders popular posts lists.
943 *
944 * @since 5.0.0
945 * @param array
946 */
947 public function render_list(array $posts, $list = 'most-viewed')
948 {
949 if ( ! empty($posts) ) {
950 ?>
951 <ol class="popular-posts-list">
952 <?php
953 foreach( $posts as $post ) {
954 $pageviews = isset($post->pageviews) ? (int) $post->pageviews : 0;
955 $comments_count = isset($post->comment_count) ? (int) $post->comment_count : 0;
956 ?>
957 <li>
958 <a href="<?php echo esc_url(get_permalink($post->id)); ?>" class="wpp-title"><?php echo esc_html(sanitize_text_field(apply_filters('the_title', $post->title, $post->id))); ?></a>
959 <div>
960 <?php if ( 'most-viewed' == $list ) : ?>
961 <span><?php printf(esc_html(_n('%s view', '%s views', $pageviews, 'wordpress-popular-posts')), esc_html(number_format_i18n($pageviews))); ?></span>
962 <?php elseif ( 'most-commented' == $list ) : ?>
963 <span><?php printf(esc_html(_n('%s comment', '%s comments', $comments_count, 'wordpress-popular-posts')), esc_html(number_format_i18n($comments_count))); ?></span>
964 <?php else : ?>
965 <span><?php printf(esc_html(_n('%s view', '%s views', $pageviews, 'wordpress-popular-posts')), esc_html(number_format_i18n($pageviews))); ?></span>, <span><?php printf(esc_html(_n('%s comment', '%s comments', $comments_count, 'wordpress-popular-posts')), esc_html(number_format_i18n($comments_count))); ?></span>
966 <?php endif; ?>
967 <small> &mdash; <a href="<?php echo esc_url(get_permalink($post->id)); ?>"><?php esc_html_e('View'); ?></a><?php if ( current_user_can('edit_others_posts') ): ?> | <a href="<?php echo esc_url(get_edit_post_link($post->id)); ?>"><?php esc_html_e('Edit'); ?></a><?php endif; ?></small>
968 </div>
969 </li>
970 <?php
971 }
972 ?>
973 </ol>
974 <?php
975 }
976 else {
977 ?>
978 <p class="no-data" style="text-align: center;"><?php _e("Looks like your site's activity is a little low right now. <br />Spread the word and come back later!", 'wordpress-popular-posts'); //phpcs:ignore WordPress.Security.EscapeOutput.UnsafePrintingFunction ?></p>
979 <?php
980 }
981 }
982
983 /**
984 * Deletes cached (transient) data.
985 *
986 * @since 3.0.0
987 * @access private
988 */
989 private function flush_transients()
990 {
991 global $wpdb;
992 $transients_table = "{$wpdb->prefix}popularpoststransients";
993
994 $wpp_transients = $wpdb->get_results($wpdb->prepare("SELECT tkey FROM %i;", $transients_table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
995
996 if ( $wpp_transients && is_array($wpp_transients) && ! empty($wpp_transients) ) {
997 foreach( $wpp_transients as $wpp_transient ) {
998 try {
999 delete_transient($wpp_transient->tkey);
1000 } catch (\Throwable $e) {
1001 if ( defined('WP_DEBUG') && WP_DEBUG ) {
1002 error_log( "Error: " . $e->getMessage() );
1003 }
1004 continue;
1005 }
1006 }
1007
1008 $wpdb->query($wpdb->prepare("TRUNCATE TABLE %i;", $transients_table));
1009 }
1010 }
1011
1012 /**
1013 * Returns WPP's default thumbnail.
1014 *
1015 * @since 6.3.4
1016 */
1017 public function get_default_thumbnail()
1018 {
1019 echo esc_url(plugins_url('assets/images/no_thumb.jpg', dirname(__FILE__, 2)));
1020 wp_die();
1021 }
1022
1023 /**
1024 * Truncates thumbnails cache on demand.
1025 *
1026 * @since 2.0.0
1027 * @global object $wpdb
1028 */
1029 public function clear_thumbnails()
1030 {
1031 $wpp_uploads_dir = $this->thumbnail->get_plugin_uploads_dir();
1032 $token = isset($_POST['token']) ? $_POST['token'] : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is a nonce
1033
1034 if (
1035 current_user_can('edit_published_posts')
1036 && wp_verify_nonce($token, 'wpp_nonce_reset_thumbnails')
1037 ) {
1038 echo $this->delete_thumbnails();
1039 } else {
1040 echo 4;
1041 }
1042
1043 wp_die();
1044 }
1045
1046 /**
1047 * Deletes WPP thumbnails from the uploads/wordpress-popular-posts folder.
1048 *
1049 * @since 7.0.0
1050 * @return int 1 on success, 2 if no thumbnails were found, 3 if WPP's folder can't be reached
1051 */
1052 private function delete_thumbnails()
1053 {
1054 $wpp_uploads_dir = $this->thumbnail->get_plugin_uploads_dir();
1055
1056 if (
1057 is_array($wpp_uploads_dir)
1058 && ! empty($wpp_uploads_dir)
1059 && is_dir($wpp_uploads_dir['basedir'])
1060 ) {
1061 $files = glob("{$wpp_uploads_dir['basedir']}/*");
1062
1063 if ( is_array($files) && ! empty($files) ) {
1064 foreach( $files as $file ) {
1065 if ( is_file($file) ) {
1066 @unlink($file); // delete file
1067 }
1068 }
1069
1070 return 1;
1071 }
1072
1073 return 2;
1074 }
1075
1076 return 3;
1077 }
1078
1079 /**
1080 * Fires immediately after deleting metadata of a post.
1081 *
1082 * @since 5.0.0
1083 *
1084 * @param int $meta_id Metadata ID.
1085 * @param int $post_id Post ID.
1086 * @param string $meta_key Meta key.
1087 * @param mixed $meta_value Meta value.
1088 */
1089 public function updated_post_meta(int $meta_id, int $post_id, string $meta_key, $meta_value) /** @TODO: starting PHP 8.0 $meta_valued can be declared as mixed $meta_value, see https://www.php.net/manual/en/language.types.declarations.php */
1090 {
1091 if ( '_thumbnail_id' == $meta_key ) {
1092 $this->flush_post_thumbnail($post_id);
1093 }
1094 }
1095
1096 /**
1097 * Fires immediately after deleting metadata of a post.
1098 *
1099 * @since 5.0.0
1100 *
1101 * @param array $meta_ids An array of deleted metadata entry IDs.
1102 * @param int $post_id Post ID.
1103 * @param string $meta_key Meta key.
1104 * @param mixed $meta_value Meta value.
1105 */
1106 public function deleted_post_meta(array $meta_ids, int $post_id, string $meta_key, $meta_value) /** @TODO: starting PHP 8.0 $meta_valued can be declared as mixed $meta_value */
1107 {
1108 if ( '_thumbnail_id' == $meta_key ) {
1109 $this->flush_post_thumbnail($post_id);
1110 }
1111 }
1112
1113 /**
1114 * Flushes post's cached thumbnail(s).
1115 *
1116 * @since 3.3.4
1117 *
1118 * @param integer $post_id Post ID
1119 */
1120 public function flush_post_thumbnail(int $post_id)
1121 {
1122 $wpp_uploads_dir = $this->thumbnail->get_plugin_uploads_dir();
1123
1124 if ( is_array($wpp_uploads_dir) && ! empty($wpp_uploads_dir) ) {
1125 $files = glob("{$wpp_uploads_dir['basedir']}/{$post_id}-*.*"); // get all related images
1126
1127 if ( is_array($files) && ! empty($files) ) {
1128 foreach( $files as $file ){ // iterate files
1129 if ( is_file($file) ) {
1130 @unlink($file); // delete file
1131 }
1132 }
1133 }
1134 }
1135 }
1136
1137 /**
1138 * Purges data cache when a post/page is trashed.
1139 *
1140 * @since 5.5.0
1141 */
1142 public function purge_data_cache()
1143 {
1144 $this->flush_transients();
1145 }
1146
1147 /**
1148 * Purges post from data/summary tables.
1149 *
1150 * @since 3.3.0
1151 */
1152 public function purge_post_data()
1153 {
1154 if ( current_user_can('delete_posts') ) {
1155 add_action('delete_post', [$this, 'purge_post']);
1156 }
1157 }
1158
1159 /**
1160 * Purges post from data/summary tables.
1161 *
1162 * @since 3.3.0
1163 * @param int $post_ID
1164 * @global object $wpdb
1165 */
1166 public function purge_post(int $post_ID)
1167 {
1168 global $wpdb;
1169 $data_table = "{$wpdb->prefix}popularpostsdata";
1170
1171 $post_ID_exists = $wpdb->get_var($wpdb->prepare("SELECT postid FROM %i WHERE postid = %d", $data_table, $post_ID)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
1172
1173 if ( $post_ID_exists ) {
1174 $summary_table = "{$wpdb->prefix}popularpostssummary";
1175
1176 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
1177 // Delete from data table
1178 $wpdb->query($wpdb->prepare("DELETE FROM %i WHERE postid = %d;", $data_table, $post_ID));
1179 // Delete from summary table
1180 $wpdb->query($wpdb->prepare("DELETE FROM %i WHERE postid = %d;", $summary_table, $post_ID));
1181 // phpcs:enable
1182 }
1183
1184 // Delete cached thumbnail(s) as well
1185 $this->flush_post_thumbnail($post_ID);
1186 }
1187
1188 /**
1189 * Purges old post data from summary table.
1190 *
1191 * @since 2.0.0
1192 * @global object $wpdb
1193 */
1194 public function purge_data()
1195 {
1196 global $wpdb;
1197 $summary_table = "{$wpdb->prefix}popularpostssummary";
1198
1199 $now = new \DateTime(Helper::now(), wp_timezone());
1200 $days_limit = $this->config['tools']['log']['expires_after'];
1201
1202 if ( ! Helper::is_number($days_limit) || $days_limit <= 0 ) {
1203 $days_limit = 180;
1204 }
1205
1206 $delete_from = $now->sub(new \DateInterval('P' . $days_limit . 'D'))->format('Y-m-d');
1207 $batch_size = 1000;
1208
1209 do {
1210 try {
1211 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
1212 $rows_affected = $wpdb->query(
1213 $wpdb->prepare(
1214 'DELETE FROM %i WHERE view_date <= %s LIMIT %d;',
1215 $summary_table,
1216 $delete_from,
1217 $batch_size
1218 )
1219 );
1220 //phpcs:enable
1221
1222 // Pause briefly between batches to reduce server load
1223 usleep(500000); // 0.5 seconds
1224 } catch (\Exception $e) {
1225 if ( defined('WP_DEBUG') && WP_DEBUG ) {
1226 error_log('WP Popular Posts failed to delete old views data: ' . $e->getMessage());
1227 }
1228
1229 break;
1230 }
1231 } while ($rows_affected > 0);
1232 }
1233
1234 /**
1235 * Displays admin notices.
1236 *
1237 * @since 5.0.2
1238 */
1239 public function notices()
1240 {
1241 /** Performance nag */
1242 $performance_nag = get_option('wpp_performance_nag');
1243
1244 if (
1245 isset($performance_nag['status'])
1246 && 3 != $performance_nag['status'] // 0 = inactive, 1 = active, 2 = remind me later, 3 = dismissed
1247 ) {
1248 $now = Helper::timestamp();
1249
1250 // How much time has passed since the notice was last displayed?
1251 $last_checked = isset($performance_nag['last_checked']) ? $performance_nag['last_checked'] : 0;
1252
1253 if ( $last_checked ) {
1254 $last_checked = ($now - $last_checked) / (60 * 60);
1255 }
1256
1257 if (
1258 1 == $performance_nag['status']
1259 || ( 2 == $performance_nag['status'] && $last_checked && $last_checked >= 24 )
1260 ) {
1261 ?>
1262 <div class="notice notice-warning">
1263 <p>
1264 <strong>WP Popular Posts:</strong>
1265 <?php
1266 printf(
1267 wp_kses(
1268 __('It seems that your site is popular (great!) You may want to check <a href="%s">these recommendations</a> to make sure that its performance stays up to par.', 'wordpress-popular-posts'),
1269 [
1270 'a' => [
1271 'href' => []
1272 ]
1273 ]
1274 ),
1275 'https://github.com/cabrerahector/wordpress-popular-posts/wiki/7.-Performance'
1276 );
1277 ?>
1278 </p>
1279 <?php if ( current_user_can('manage_options') ) : ?>
1280 <p><a class="button button-primary wpp-dismiss-performance-notice" href="<?php echo esc_url(add_query_arg('wpp_dismiss_performance_notice', '1')); ?>"><?php esc_html_e('Dismiss', 'wordpress-popular-posts'); ?></a> <a class="button wpp-remind-performance-notice" href="<?php echo esc_url(add_query_arg('wpp_remind_performance_notice', '1')); ?>"><?php esc_html_e('Remind me later', 'wordpress-popular-posts'); ?></a> <span class="spinner" style="float: none;"></span></p>
1281 <?php endif; ?>
1282 </div>
1283 <?php
1284 }
1285 }
1286
1287 $pretty_permalinks_enabled = get_option('permalink_structure');
1288
1289 if ( ! $pretty_permalinks_enabled ) {
1290 ?>
1291 <div class="notice notice-warning">
1292 <p>
1293 <strong>WP Popular Posts:</strong>
1294 <?php
1295 printf(
1296 wp_kses(
1297 /* translators: third placeholder corresponds to the I18N version of the "Plain" permalink structure option */
1298 __('It looks like your site is not using <a href="%s">Pretty Permalinks</a>. Please <a href="%s">select a permalink structure</a> other than <em>%s</em> so WP Popular Posts can do its job.', 'wordpress-popular-posts'),
1299 [
1300 'a' => [
1301 'href' => []
1302 ],
1303 'em' => []
1304 ]
1305 ),
1306 'https://wordpress.org/documentation/article/customize-permalinks/#pretty-permalinks',
1307 esc_url(admin_url('options-permalink.php')),
1308 __('Plain')
1309 );
1310 ?>
1311 </p>
1312 </div>
1313 <?php
1314 }
1315 }
1316
1317 /**
1318 * Handles performance notice click event.
1319 *
1320 * @since
1321 */
1322 public function handle_performance_notice()
1323 {
1324 $response = [
1325 'status' => 'error'
1326 ];
1327 $token = isset($_POST['token']) ? $_POST['token'] : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is a nonce
1328 $dismiss = isset($_POST['dismiss']) ? (int) $_POST['dismiss'] : 0;
1329
1330 if (
1331 current_user_can('manage_options')
1332 && wp_verify_nonce($token, 'wpp_nonce_performance_nag')
1333 ) {
1334 $now = Helper::timestamp();
1335
1336 // User dismissed the notice
1337 if ( 1 == $dismiss ) {
1338 $performance_nag['status'] = 3;
1339 } // User asked us to remind them later
1340 else {
1341 $performance_nag['status'] = 2;
1342 }
1343
1344 $performance_nag['last_checked'] = $now;
1345
1346 update_option('wpp_performance_nag', $performance_nag);
1347
1348 $response = [
1349 'status' => 'success'
1350 ];
1351 }
1352
1353 wp_send_json($response);
1354 }
1355 }
1356