PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / 7.3.6
WP Popular Posts v7.3.6
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 8 months ago admin-page.php 8 months ago screen-debug.php 8 months ago screen-stats.php 8 months ago screen-tools.php 8 months ago
Admin.php
1407 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 }
140
141 /**
142 * Checks whether a performance tweak may be necessary.
143 *
144 * @since 5.0.2
145 */
146 public function performance_check()
147 {
148 $performance_nag = get_option('wpp_performance_nag');
149
150 if ( ! $performance_nag ) {
151 $performance_nag = [
152 'status' => 0,
153 'last_checked' => null
154 ];
155 add_option('wpp_performance_nag', $performance_nag);
156 }
157
158 if ( 3 != $performance_nag['status'] ) { // 0 = inactive, 1 = active, 2 = remind me later, 3 = dismissed
159 global $wpdb;
160
161 $summary_table = "{$wpdb->prefix}popularpostssummary";
162
163 //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
164 $views_count = $wpdb->get_var(
165 $wpdb->prepare(
166 "SELECT IFNULL(SUM(pageviews), 0) AS views FROM %i WHERE view_datetime > DATE_SUB(%s, INTERVAL 1 HOUR);",
167 $summary_table,
168 Helper::now()
169 )
170 );
171 //phpcs:enable
172
173 // This site is probably a mid/high traffic one,
174 // display performance nag
175 if ( $views_count >= 420 ) {
176 if ( 0 == $performance_nag['status'] ) {
177 $performance_nag['status'] = 1;
178 $performance_nag['last_checked'] = Helper::timestamp();
179 update_option('wpp_performance_nag', $performance_nag);
180 }
181 }
182 }
183 }
184
185 /**
186 * Fired when a new blog is activated on WP Multisite.
187 *
188 * @since 3.0.0
189 * @param int $blog_id New blog ID
190 */
191 public function activate_new_site(int $blog_id)
192 {
193 if ( 1 !== did_action('wpmu_new_blog') ) {
194 return;
195 }
196
197 // run activation for the new blog
198 switch_to_blog($blog_id);
199 \WordPressPopularPosts\Activation\Activator::track_new_site();
200 // switch back to current blog
201 restore_current_blog();
202 }
203
204 /**
205 * Fired when a blog is deleted on WP Multisite.
206 *
207 * @since 4.0.0
208 * @param array $tables
209 * @param int $blog_id
210 * @return array
211 */
212 public function delete_site_data(array $tables, int $blog_id)
213 {
214 global $wpdb;
215
216 $tables[] = $wpdb->prefix . 'popularpostsdata';
217 $tables[] = $wpdb->prefix . 'popularpostssummary';
218
219 return $tables;
220 }
221
222 /**
223 * Display some statistics at the "At a Glance" box from the Dashboard.
224 *
225 * @since 4.1.0
226 */
227 public function at_a_glance_stats()
228 {
229 global $wpdb;
230
231 $glances = [];
232 $args = ['post', 'page'];
233 $post_type_placeholders = '%s, %s';
234
235 if (
236 isset($this->config['stats']['post_type'])
237 && ! empty($this->config['stats']['post_type'])
238 ) {
239 $args = array_map('trim', explode(',', $this->config['stats']['post_type']));
240 $post_type_placeholders = implode(', ', array_fill(0, count($args), '%s'));
241 }
242
243 $args[] = Helper::now();
244
245 $posts_table = "{$wpdb->prefix}posts";
246 $summary_table = "{$wpdb->prefix}popularpostssummary";
247
248 //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $post_type_placeholder is safe to use
249 $query = $wpdb->prepare(
250 "SELECT SUM(pageviews) AS total
251 FROM %i v LEFT JOIN %i p ON v.postid = p.ID
252 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);",
253 [$summary_table, $posts_table, ...$args]
254 );
255 //phpcs:enable
256
257 $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
258 $total_views = (float) $total_views;
259
260 $pageviews = sprintf(
261 _n('%s view in the last hour', '%s views in the last hour', $total_views, 'wordpress-popular-posts'),
262 number_format_i18n($total_views)
263 );
264
265 if ( current_user_can('edit_published_posts') ) {
266 $glances[] = '<a class="wpp-views-count" href="' . admin_url('options-general.php?page=wordpress-popular-posts') . '">' . $pageviews . '</a>';
267 }
268 else {
269 $glances[] = '<span class="wpp-views-count">' . $pageviews . '</a>';
270 }
271
272 return $glances;
273 }
274
275 /**
276 * Add custom inline CSS styles for At a Glance stats.
277 *
278 * @since 4.1.0
279 */
280 public function at_a_glance_stats_css()
281 {
282 echo '<style>#dashboard_right_now a.wpp-views-count:before, #dashboard_right_now span.wpp-views-count:before {content: "\f177";}</style>';
283 }
284
285 /**
286 * Adds a widget to the dashboard.
287 *
288 * @since 5.0.0
289 */
290 public function add_dashboard_widgets()
291 {
292 if ( current_user_can('edit_published_posts') ) {
293 wp_add_dashboard_widget(
294 'wpp_trending_dashboard_widget',
295 __('Trending now', 'wordpress-popular-posts'),
296 [$this, 'trending_dashboard_widget']
297 );
298 }
299 }
300
301 /**
302 * Outputs the contents of our Trending Dashboard Widget.
303 *
304 * @since 5.0.0
305 */
306 public function trending_dashboard_widget()
307 {
308 ?>
309 <style>
310 #wpp_trending_dashboard_widget .inside {
311 overflow: hidden;
312 position: relative;
313 min-height: 150px;
314 padding-bottom: 22px;
315 }
316
317 #wpp_trending_dashboard_widget .inside::after {
318 position: absolute;
319 top: 0;
320 left: 0;
321 opacity: 0.2;
322 display: block;
323 content: '';
324 width: 100%;
325 height: 100%;
326 z-index: 1;
327 background-image: url('<?php echo esc_url(plugin_dir_url(dirname(dirname(__FILE__)))) . 'assets/images/flame.png'; ?>');
328 background-position: right bottom;
329 background-repeat: no-repeat;
330 background-size: 34% auto;
331 }
332
333 #wpp_trending_dashboard_widget .inside .no-data {
334 position: absolute;
335 top: calc(50% - 11px);
336 left: 50%;
337 z-index: 2;
338 margin: 0;
339 padding: 0;
340 width: 96%;
341 transform: translate(-50.0001%, -50.0001%);
342 }
343
344 #wpp_trending_dashboard_widget .inside .popular-posts-list,
345 #wpp_trending_dashboard_widget .inside p#wpp_read_more {
346 position: relative;
347 z-index: 2;
348 }
349
350 #wpp_trending_dashboard_widget .inside .popular-posts-list {
351 margin: 1em 0;
352 }
353
354 #wpp_trending_dashboard_widget .inside p#wpp_read_more {
355 position: absolute;
356 left: 0;
357 bottom: 0;
358 width: 100%;
359 text-align: center;
360 }
361 </style>
362 <?php
363 $args = [
364 'range' => 'custom',
365 'time_quantity' => 1,
366 'time_unit' => 'HOUR',
367 'post_type' => $this->config['stats']['post_type'],
368 'limit' => 5,
369 'stats_tag' => [
370 'views' => 1,
371 'comment_count' => 1
372 ]
373 ];
374 $options = apply_filters('wpp_trending_dashboard_widget_args', []);
375
376 if ( is_array($options) && ! empty($options) ) {
377 $args = Helper::merge_array_r($args, $options);
378 }
379
380 $query = new Query($args);
381 $posts = $query->get_posts();
382
383 $this->render_list($posts, 'trending');
384 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>';
385
386 }
387
388 /**
389 * Enqueues admin facing assets.
390 *
391 * @since 5.0.0
392 */
393 public function enqueue_assets()
394 {
395 $screen = get_current_screen();
396
397 if ( isset($screen->id) ) {
398 if ( $screen->id == $this->screen_hook_suffix ) {
399 wp_enqueue_style('wpp-datepicker-theme', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/datepicker.css', [], WPP_VERSION, 'all');
400
401 wp_enqueue_media();
402 wp_enqueue_script('jquery-ui-datepicker');
403 wp_enqueue_script('chartjs', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/vendor/chart.3.8.0.min.js', [], WPP_VERSION);
404
405 wp_register_script('wpp-chart', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/chart.js', ['chartjs'], WPP_VERSION);
406 wp_localize_script('wpp-chart', 'wpp_chart_params', [
407 'colors' => $this->get_admin_color_scheme()
408 ]);
409 wp_enqueue_script('wpp-chart');
410
411 wp_register_script('wordpress-popular-posts-admin-script', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/admin.js', ['jquery'], WPP_VERSION, true); /** @TODO Drop jQuery datepicker dep */
412 wp_localize_script('wordpress-popular-posts-admin-script', 'wpp_admin_params', [
413 'label_media_upload_button' => __('Use this image', 'wordpress-popular-posts'),
414 'nonce' => wp_create_nonce('wpp_admin_nonce'),
415 'nonce_reset_thumbnails' => wp_create_nonce('wpp_nonce_reset_thumbnails'),
416 'text_confirm_image_cache_reset' => __('This operation will delete all cached thumbnails and cannot be undone.', 'wordpress-popular-posts'),
417 'text_image_cache_cleared' => __('Success! All files have been deleted!', 'wordpress-popular-posts'),
418 'text_image_cache_already_empty' => __('The thumbnail cache is already empty!', 'wordpress-popular-posts'),
419 'text_continue' => __('Do you want to continue?', 'wordpress-popular-posts'),
420 'text_insufficient_permissions' => __('Sorry, you do not have enough permissions to do this. Please contact the site administrator for support.', 'wordpress-popular-posts'),
421 'text_invalid_action' => __('Invalid action.', 'wordpress-popular-posts')
422 ]);
423 wp_enqueue_script('wordpress-popular-posts-admin-script');
424 }
425
426 if ( $screen->id == $this->screen_hook_suffix || 'dashboard' == $screen->id ) {
427 // Fontello icons
428 wp_enqueue_style('wpp-fontello', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/fontello.css', [], WPP_VERSION, 'all');
429 wp_enqueue_style('wordpress-popular-posts-admin-styles', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/admin.css', [], WPP_VERSION, 'all');
430 }
431 }
432
433 $performance_nag = get_option('wpp_performance_nag');
434
435 if (
436 isset($performance_nag['status'])
437 && 3 != $performance_nag['status'] // 0 = inactive, 1 = active, 2 = remind me later, 3 = dismissed
438 ) {
439 $now = Helper::timestamp();
440
441 // How much time has passed since the notice was last displayed?
442 $last_checked = isset($performance_nag['last_checked']) ? $performance_nag['last_checked'] : 0;
443
444 if ( $last_checked ) {
445 $last_checked = ($now - $last_checked) / (60 * 60);
446 }
447
448 if (
449 1 == $performance_nag['status']
450 || ( 2 == $performance_nag['status'] && $last_checked && $last_checked >= 24 )
451 ) {
452 wp_register_script('wpp-admin-notices', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/admin-notices.js', [], WPP_VERSION);
453 wp_localize_script('wpp-admin-notices', 'wpp_admin_notices_params', [
454 'nonce_performance_nag' => wp_create_nonce('wpp_nonce_performance_nag')
455 ]);
456 wp_enqueue_script('wpp-admin-notices');
457 }
458 }
459 }
460
461 /**
462 * Register the administration menu for this plugin into the WordPress Dashboard menu.
463 *
464 * @since 1.0.0
465 */
466 public function add_plugin_admin_menu()
467 {
468 $this->screen_hook_suffix = add_options_page(
469 'WP Popular Posts',
470 'WP Popular Posts',
471 'edit_published_posts',
472 'wordpress-popular-posts',
473 [$this, 'display_plugin_admin_page']
474 );
475 }
476
477 /**
478 * Render the settings page for this plugin.
479 *
480 * @since 1.0.0
481 */
482 public function display_plugin_admin_page()
483 {
484 include_once plugin_dir_path(__FILE__) . 'admin-page.php';
485 }
486
487 /**
488 * Adds contextual help menu.
489 *
490 * @since 4.0.0
491 */
492 public function add_contextual_help()
493 {
494 $screen = get_current_screen();
495
496 if ( isset($screen->id) && $screen->id == $this->screen_hook_suffix ){
497 $screen->add_help_tab(
498 [
499 'id' => 'wpp_help_overview',
500 'title' => __('Overview', 'wordpress-popular-posts'),
501 '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>'
502 ]
503 );
504 $screen->add_help_tab(
505 [
506 'id' => 'wpp_help_donate',
507 'title' => __('Like this plugin?', 'wordpress-popular-posts'),
508 'content' => '
509 <p style="text-align: center;">' . __('Each donation motivates me to keep releasing free stuff for the WordPress community!', 'wordpress-popular-posts') . '</p>
510 <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top" style="margin: 0; padding: 0; text-align: center;">
511 <input type="hidden" name="cmd" value="_s-xclick">
512 <input type="hidden" name="hosted_button_id" value="RP9SK8KVQHRKS">
513 <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;">
514 <img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1">
515 </form>
516 <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>'
517 ]
518 );
519
520 // Help sidebar
521 $screen->set_help_sidebar(
522 sprintf(
523 __('<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'),
524 'https://github.com/cabrerahector/wordpress-popular-posts/',
525 'https://wordpress.org/support/plugin/wordpress-popular-posts/'
526 )
527 );
528 }
529 }
530
531 /**
532 * Registers Settings link on plugin description.
533 *
534 * @since 2.3.3
535 * @param array $links
536 * @param string $file
537 * @return array
538 */
539 public function add_plugin_settings_link(array $links, string $file)
540 {
541 $plugin_file = 'wordpress-popular-posts/wordpress-popular-posts.php';
542
543 if (
544 is_plugin_active($plugin_file)
545 && $plugin_file == $file
546 ) {
547 array_unshift(
548 $links,
549 '<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
550 '<a href="https://wordpress.org/support/plugin/wordpress-popular-posts/">' . __('Support', 'wordpress-popular-posts') . '</a>'
551 );
552 }
553
554 return $links;
555 }
556
557 /**
558 * Gets current admin color scheme.
559 *
560 * @since 4.0.0
561 * @return array
562 */
563 private function get_admin_color_scheme()
564 {
565 global $_wp_admin_css_colors;
566
567 if (
568 is_array($_wp_admin_css_colors)
569 && count($_wp_admin_css_colors)
570 ) {
571 $current_user = wp_get_current_user();
572 $color_scheme = get_user_option('admin_color', $current_user->ID);
573
574 if (
575 empty($color_scheme)
576 || ! isset($_wp_admin_css_colors[ $color_scheme])
577 ) {
578 $color_scheme = 'fresh';
579 }
580
581 if ( isset($_wp_admin_css_colors[$color_scheme]) && isset($_wp_admin_css_colors[$color_scheme]->colors) ) {
582 return $_wp_admin_css_colors[$color_scheme]->colors;
583 }
584
585 }
586
587 // Fallback, just in case
588 return ['#333', '#999', '#881111', '#a80000'];
589 }
590
591 /**
592 * Fetches chart data.
593 *
594 * @since 4.0.0
595 * @return string
596 */
597 public function get_chart_data(string $range = 'last7days', string $time_unit = 'HOUR', int $time_quantity = 24)
598 {
599 $dates = $this->get_dates($range, $time_unit, $time_quantity);
600 $start_date = $dates[0];
601 $end_date = $dates[count($dates) - 1];
602 $date_range = Helper::get_date_range($start_date, $end_date, 'Y-m-d H:i:s');
603 $views_data = $this->get_range_item_count($start_date, $end_date, 'views');
604 $views = [];
605 $comments_data = $this->get_range_item_count($start_date, $end_date, 'comments');
606 $comments = [];
607
608 if ( 'today' != $range ) {
609 foreach($date_range as $date) {
610 $key = date('Y-m-d', strtotime($date));
611 $views[] = ( ! isset($views_data[$key]) ) ? 0 : $views_data[$key]->pageviews;
612 $comments[] = ( ! isset($comments_data[$key]) ) ? 0 : $comments_data[$key]->comments;
613 }
614 } else {
615 $key = date('Y-m-d', strtotime($dates[0]));
616 $views[] = ( ! isset($views_data[$key]) ) ? 0 : $views_data[$key]->pageviews;
617 $comments[] = ( ! isset($comments_data[$key]) ) ? 0 : $comments_data[$key]->comments;
618 }
619
620 if ( $start_date != $end_date ) {
621 $label_date_range = date_i18n('M, D d', strtotime($start_date)) . ' &mdash; ' . date_i18n('M, D d', strtotime($end_date));
622 } else {
623 $label_date_range = date_i18n('M, D d', strtotime($start_date));
624 }
625
626 $total_views = array_sum($views);
627 $total_comments = array_sum($comments);
628
629 $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>');
630
631 // Format labels
632 if ( 'today' != $range ) {
633 $date_range = array_map(function($d) {
634 return date_i18n('D d', strtotime($d));
635 }, $date_range);
636 } else {
637 $date_range = [date_i18n('D d', strtotime($date_range[0]))];
638 $comments = [array_sum($comments)];
639 $views = [array_sum($views)];
640 }
641
642 $response = [
643 'totals' => [
644 'label_summary' => $label_summary,
645 'label_date_range' => $label_date_range,
646 ],
647 'labels' => $date_range,
648 'datasets' => [
649 [
650 'label' => __('Comments', 'wordpress-popular-posts'),
651 'data' => $comments
652 ],
653 [
654 'label' => __('Views', 'wordpress-popular-posts'),
655 'data' => $views
656 ]
657 ]
658 ];
659
660 return json_encode($response);
661 }
662
663 /**
664 * Returns an array of dates.
665 *
666 * @since 5.0.0
667 * @return array|bool
668 */
669 private function get_dates(string $range = 'last7days', string $time_unit = 'HOUR', int $time_quantity = 24)
670 {
671 $valid_ranges = ['today', 'daily', 'last24hours', 'weekly', 'last7days', 'monthly', 'last30days', 'all', 'custom'];
672 $range = in_array($range, $valid_ranges) ? $range : 'last7days';
673 $now = new \DateTime(Helper::now(), wp_timezone());
674
675 // Determine time range
676 switch( $range ){
677 case 'last24hours':
678 case 'daily':
679 $end_date = $now->format('Y-m-d H:i:s');
680 $start_date = $now->modify('-1 day')->format('Y-m-d H:i:s');
681 break;
682
683 case 'today':
684 $start_date = $now->format('Y-m-d') . ' 00:00:00';
685 $end_date = $now->format('Y-m-d') . ' 23:59:59';
686 break;
687
688 case 'last7days':
689 case 'weekly':
690 $end_date = $now->format('Y-m-d') . ' 23:59:59';
691 $start_date = $now->modify('-6 day')->format('Y-m-d') . ' 00:00:00';
692 break;
693
694 case 'last30days':
695 case 'monthly':
696 $end_date = $now->format('Y-m-d') . ' 23:59:59';
697 $start_date = $now->modify('-29 day')->format('Y-m-d') . ' 00:00:00';
698 break;
699
700 case 'custom':
701 $end_date = $now->format('Y-m-d H:i:s');
702
703 if (
704 Helper::is_number($time_quantity)
705 && $time_quantity >= 1
706 ) {
707 $end_date = $now->format('Y-m-d H:i:s');
708 $time_unit = strtoupper($time_unit);
709
710 if ( 'MINUTE' == $time_unit ) {
711 $start_date = $now->sub(new \DateInterval('PT' . (60 * $time_quantity) . 'S'))->format('Y-m-d H:i:s');
712 } elseif ( 'HOUR' == $time_unit ) {
713 $start_date = $now->sub(new \DateInterval('PT' . ((60 * $time_quantity) - 1) . 'M59S'))->format('Y-m-d H:i:s');
714 } else {
715 $end_date = $now->format('Y-m-d') . ' 23:59:59';
716 $start_date = $now->sub(new \DateInterval('P' . ($time_quantity - 1) . 'D'))->format('Y-m-d') . ' 00:00:00';
717 }
718 } // fallback to last 24 hours
719 else {
720 $start_date = $now->modify('-1 day')->format('Y-m-d H:i:s');
721 }
722
723 // Check if custom date range has been requested
724 $dates = null;
725
726 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- 'dates' are date strings, and we're validating those below
727 if ( isset($_GET['dates']) ) {
728 $dates = explode(' ~ ', esc_html($_GET['dates']));
729
730 if (
731 ! is_array($dates)
732 || empty($dates)
733 || ! Helper::is_valid_date($dates[0])
734 ) {
735 $dates = null;
736 } else {
737 if (
738 ! isset($dates[1])
739 || ! Helper::is_valid_date($dates[1])
740 ) {
741 $dates[1] = $dates[0];
742 }
743
744 $start_date = $dates[0] . ' 00:00:00';
745 $end_date = $dates[1] . ' 23:59:59';
746 }
747 }
748 // phpcs:enable
749
750 break;
751
752 default:
753 $end_date = $now->format('Y-m-d') . ' 23:59:59';
754 $start_date = $now->modify('-6 day')->format('Y-m-d') . ' 00:00:00';
755 break;
756 }
757
758 return [$start_date, $end_date];
759 }
760
761 /**
762 * Returns an array of dates with views/comments count.
763 *
764 * @since 5.0.0
765 * @param string $start_date
766 * @param string $end_date
767 * @param string $item
768 * @return array
769 */
770 public function get_range_item_count(string $start_date, string $end_date, string $item = 'views')
771 {
772 global $wpdb;
773
774 $args = array_map('trim', explode(',', $this->config['stats']['post_type']));
775
776 $types = get_post_types([
777 'public' => true
778 ], 'names' );
779 $types = array_values($types);
780
781 // Let's make sure we're getting valid post types
782 $args = array_intersect($types, $args);
783
784 if ( empty($args) ) {
785 $args = ['post', 'page'];
786 }
787
788 $post_type_placeholders = array_fill(0, count($args), '%s');
789
790 if ( $this->config['stats']['freshness'] ) {
791 $args[] = $start_date;
792 }
793
794 // Append dates to arguments list
795 array_unshift($args, $start_date, $end_date);
796
797 $posts_table = "{$wpdb->posts}";
798
799 if ( $item == 'comments' ) {
800 $comments_table = "{$wpdb->comments}";
801
802 //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $post_type_placeholders is already prepared above
803 $query = $wpdb->prepare(
804 "SELECT DATE(`c`.`comment_date_gmt`) AS `c_date`, COUNT(*) AS `comments`
805 FROM %i c INNER JOIN %i p ON `c`.`comment_post_ID` = `p`.`ID`
806 WHERE (`c`.`comment_date_gmt` 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` = ''
807 " . ( $this->config['stats']['freshness'] ? ' AND `p`.`post_date` >= %s' : '' ) . '
808 GROUP BY `c_date` ORDER BY `c_date` DESC;',
809 [$comments_table, $posts_table, ...$args]
810 );
811 //phpcs:enable
812 } else {
813 $views_table = "{$wpdb->prefix}popularpostssummary";
814
815 //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $post_type_placeholders is already prepared above
816 $query = $wpdb->prepare(
817 "SELECT `v`.`view_date`, SUM(`v`.`pageviews`) AS `pageviews`
818 FROM %i v INNER JOIN %i p ON `v`.`postid` = `p`.`ID`
819 WHERE (`v`.`view_datetime` BETWEEN %s AND %s) AND `p`.`post_type` IN (" . implode(', ', $post_type_placeholders) . ") AND `p`.`post_status` = 'publish' AND `p`.`post_password` = ''
820 " . ( $this->config['stats']['freshness'] ? ' AND `p`.`post_date` >= %s' : '' ) . '
821 GROUP BY `v`.`view_date` ORDER BY `v`.`view_date` DESC;',
822 [$views_table, $posts_table, ...$args]
823 );
824 //phpcs:enable
825 }
826
827 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
828 }
829
830 /**
831 * Updates chart via AJAX.
832 *
833 * @since 4.0.0
834 */
835 public function update_chart()
836 {
837 $response = [
838 'status' => 'error'
839 ];
840 $nonce = isset($_GET['nonce']) ? $_GET['nonce'] : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a nonce
841
842 if ( wp_verify_nonce($nonce, 'wpp_admin_nonce') ) {
843
844 $valid_ranges = ['today', 'daily', 'last24hours', 'weekly', 'last7days', 'monthly', 'last30days', 'all', 'custom'];
845 $time_units = ['MINUTE', 'HOUR', 'DAY'];
846
847 $range = ( isset($_GET['range']) && in_array($_GET['range'], $valid_ranges) ) ? $_GET['range'] : 'last7days';
848 $time_quantity = ( isset($_GET['time_quantity']) && filter_var($_GET['time_quantity'], FILTER_VALIDATE_INT) ) ? $_GET['time_quantity'] : 24;
849 $time_unit = ( isset($_GET['time_unit']) && in_array(strtoupper($_GET['time_unit']), $time_units) ) ? $_GET['time_unit'] : 'hour';
850
851 $this->config['stats']['range'] = $range;
852 $this->config['stats']['time_quantity'] = $time_quantity;
853 $this->config['stats']['time_unit'] = $time_unit;
854
855 update_option('wpp_settings_config', $this->config);
856
857 $response = [
858 'status' => 'ok',
859 'data' => json_decode(
860 $this->get_chart_data($this->config['stats']['range'], $this->config['stats']['time_unit'], $this->config['stats']['time_quantity']),
861 true
862 )
863 ];
864 }
865
866 wp_send_json($response);
867 }
868
869 /**
870 * Fetches most viewed/commented/trending posts via AJAX.
871 *
872 * @since 5.0.0
873 */
874 public function get_popular_items()
875 {
876 $items = isset($_GET['items']) ? $_GET['items'] : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification happens below
877 $nonce = isset($_GET['nonce']) ? $_GET['nonce'] : null; //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a nonce
878
879 if ( wp_verify_nonce($nonce, 'wpp_admin_nonce') ) {
880 $args = [
881 'range' => $this->config['stats']['range'],
882 'time_quantity' => $this->config['stats']['time_quantity'],
883 'time_unit' => $this->config['stats']['time_unit'],
884 'post_type' => $this->config['stats']['post_type'],
885 'freshness' => $this->config['stats']['freshness'],
886 'limit' => $this->config['stats']['limit'],
887 'stats_tag' => [
888 'date' => [
889 'active' => 1
890 ]
891 ]
892 ];
893
894 if ( 'most-commented' == $items ) {
895 $args['order_by'] = 'comments';
896 $args['stats_tag']['comment_count'] = 1;
897 $args['stats_tag']['views'] = 0;
898 } elseif ( 'trending' == $items ) {
899 $args['range'] = 'custom';
900 $args['time_quantity'] = 1;
901 $args['time_unit'] = 'HOUR';
902 $args['stats_tag']['comment_count'] = 1;
903 $args['stats_tag']['views'] = 1;
904 } else {
905 $args['stats_tag']['comment_count'] = 0;
906 $args['stats_tag']['views'] = 1;
907 }
908
909 if ( 'trending' != $items ) {
910
911 add_filter('wpp_query_join', function($join, $options) use ($items) {
912 global $wpdb;
913 $dates = null;
914
915 if ( isset($_GET['dates']) ) { //phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is checked above, 'dates' is verified below
916 $dates = explode(' ~ ', esc_html($_GET['dates'])); //phpcs:ignore WordPress.Security.NonceVerification.Recommended
917
918 if (
919 ! is_array($dates)
920 || empty($dates)
921 || ! Helper::is_valid_date($dates[0])
922 ) {
923 $dates = null;
924 } else {
925 if (
926 ! isset($dates[1])
927 || ! Helper::is_valid_date($dates[1])
928 ) {
929 $dates[1] = $dates[0];
930 }
931
932 $start_date = $dates[0];
933 $end_date = $dates[1];
934 }
935
936 }
937
938 if ( $dates ) {
939 if ( 'most-commented' == $items ) {
940 return "INNER JOIN (SELECT comment_post_ID, COUNT(comment_post_ID) AS comment_count, comment_date_gmt FROM `{$wpdb->comments}` WHERE comment_date_gmt BETWEEN '{$dates[0]} 00:00:00' AND '{$dates[1]} 23:59:59' AND comment_approved = '1' GROUP BY comment_post_ID) c ON p.ID = c.comment_post_ID";
941 }
942
943 return "INNER JOIN (SELECT SUM(pageviews) AS pageviews, view_date, postid FROM `{$wpdb->prefix}popularpostssummary` WHERE view_datetime BETWEEN '{$dates[0]} 00:00:00' AND '{$dates[1]} 23:59:59' GROUP BY postid) v ON p.ID = v.postid";
944 }
945
946 $now = Helper::now();
947
948 // Determine time range
949 switch( $options['range'] ){
950 case 'last24hours':
951 case 'daily':
952 $interval = '24 HOUR';
953 break;
954
955 case 'today':
956 $hours = date('H', strtotime($now));
957 $minutes = $hours * 60 + (int) date( 'i', strtotime($now) );
958 $interval = "{$minutes} MINUTE";
959 break;
960
961 case 'last7days':
962 case 'weekly':
963 $interval = '6 DAY';
964 break;
965
966 case 'last30days':
967 case 'monthly':
968 $interval = '29 DAY';
969 break;
970
971 case 'custom':
972 $time_units = ['MINUTE', 'HOUR', 'DAY'];
973 $interval = '24 HOUR';
974
975 // Valid time unit
976 if (
977 isset($options['time_unit'])
978 && in_array(strtoupper($options['time_unit']), $time_units)
979 && isset($options['time_quantity'])
980 && filter_var($options['time_quantity'], FILTER_VALIDATE_INT)
981 && $options['time_quantity'] > 0
982 ) {
983 $interval = "{$options['time_quantity']} " . strtoupper($options['time_unit']);
984 }
985
986 break;
987
988 default:
989 $interval = '1 DAY';
990 break;
991 }
992
993 if ( 'most-commented' == $items ) {
994 return "INNER JOIN (SELECT comment_post_ID, COUNT(comment_post_ID) AS comment_count, comment_date_gmt FROM `{$wpdb->comments}` WHERE comment_date_gmt > DATE_SUB('{$now}', INTERVAL {$interval}) AND comment_approved = '1' GROUP BY comment_post_ID) c ON p.ID = c.comment_post_ID";
995 }
996
997 return "INNER JOIN (SELECT SUM(pageviews) AS pageviews, view_date, postid FROM `{$wpdb->prefix}popularpostssummary` WHERE view_datetime > DATE_SUB('{$now}', INTERVAL {$interval}) GROUP BY postid) v ON p.ID = v.postid";
998 }, 1, 2);
999
1000 }
1001
1002 $query = new Query($args);
1003 $posts = $query->get_posts();
1004
1005 if ( 'trending' != $items ) {
1006 remove_all_filters('wpp_query_join', 1);
1007 }
1008
1009 $this->render_list($posts, $items);
1010 }
1011
1012 wp_die();
1013 }
1014
1015 /**
1016 * Renders popular posts lists.
1017 *
1018 * @since 5.0.0
1019 * @param array
1020 */
1021 public function render_list(array $posts, $list = 'most-viewed')
1022 {
1023 if ( ! empty($posts) ) {
1024 ?>
1025 <ol class="popular-posts-list">
1026 <?php
1027 foreach( $posts as $post ) {
1028 $pageviews = isset($post->pageviews) ? (int) $post->pageviews : 0;
1029 $comments_count = isset($post->comment_count) ? (int) $post->comment_count : 0;
1030 ?>
1031 <li>
1032 <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>
1033 <div>
1034 <?php if ( 'most-viewed' == $list ) : ?>
1035 <span><?php printf(esc_html(_n('%s view', '%s views', $pageviews, 'wordpress-popular-posts')), esc_html(number_format_i18n($pageviews))); ?></span>
1036 <?php elseif ( 'most-commented' == $list ) : ?>
1037 <span><?php printf(esc_html(_n('%s comment', '%s comments', $comments_count, 'wordpress-popular-posts')), esc_html(number_format_i18n($comments_count))); ?></span>
1038 <?php else : ?>
1039 <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>
1040 <?php endif; ?>
1041 <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>
1042 </div>
1043 </li>
1044 <?php
1045 }
1046 ?>
1047 </ol>
1048 <?php
1049 }
1050 else {
1051 ?>
1052 <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>
1053 <?php
1054 }
1055 }
1056
1057 /**
1058 * Deletes cached (transient) data.
1059 *
1060 * @since 3.0.0
1061 * @access private
1062 */
1063 private function flush_transients()
1064 {
1065 global $wpdb;
1066 $transients_table = "{$wpdb->prefix}popularpoststransients";
1067
1068 $wpp_transients = $wpdb->get_results($wpdb->prepare("SELECT tkey FROM %i;", $transients_table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
1069
1070 if ( $wpp_transients && is_array($wpp_transients) && ! empty($wpp_transients) ) {
1071 foreach( $wpp_transients as $wpp_transient ) {
1072 try {
1073 delete_transient($wpp_transient->tkey);
1074 } catch (\Throwable $e) {
1075 if ( defined('WP_DEBUG') && WP_DEBUG ) {
1076 error_log( "Error: " . $e->getMessage() );
1077 }
1078 continue;
1079 }
1080 }
1081
1082 $wpdb->query($wpdb->prepare("TRUNCATE TABLE %i;", $transients_table));
1083 }
1084 }
1085
1086 /**
1087 * Returns WPP's default thumbnail.
1088 *
1089 * @since 6.3.4
1090 */
1091 public function get_default_thumbnail()
1092 {
1093 echo esc_url(plugins_url('assets/images/no_thumb.jpg', dirname(__FILE__, 2)));
1094 wp_die();
1095 }
1096
1097 /**
1098 * Truncates thumbnails cache on demand.
1099 *
1100 * @since 2.0.0
1101 * @global object $wpdb
1102 */
1103 public function clear_thumbnails()
1104 {
1105 $wpp_uploads_dir = $this->thumbnail->get_plugin_uploads_dir();
1106 $token = isset($_POST['token']) ? $_POST['token'] : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is a nonce
1107
1108 if (
1109 current_user_can('edit_published_posts')
1110 && wp_verify_nonce($token, 'wpp_nonce_reset_thumbnails')
1111 ) {
1112 echo $this->delete_thumbnails();
1113 } else {
1114 echo 4;
1115 }
1116
1117 wp_die();
1118 }
1119
1120 /**
1121 * Deletes WPP thumbnails from the uploads/wordpress-popular-posts folder.
1122 *
1123 * @since 7.0.0
1124 * @return int 1 on success, 2 if no thumbnails were found, 3 if WPP's folder can't be reached
1125 */
1126 private function delete_thumbnails()
1127 {
1128 $wpp_uploads_dir = $this->thumbnail->get_plugin_uploads_dir();
1129
1130 if (
1131 is_array($wpp_uploads_dir)
1132 && ! empty($wpp_uploads_dir)
1133 && is_dir($wpp_uploads_dir['basedir'])
1134 ) {
1135 $files = glob("{$wpp_uploads_dir['basedir']}/*");
1136
1137 if ( is_array($files) && ! empty($files) ) {
1138 foreach( $files as $file ) {
1139 if ( is_file($file) ) {
1140 @unlink($file); // delete file
1141 }
1142 }
1143
1144 return 1;
1145 }
1146
1147 return 2;
1148 }
1149
1150 return 3;
1151 }
1152
1153 /**
1154 * Fires immediately after deleting metadata of a post.
1155 *
1156 * @since 5.0.0
1157 *
1158 * @param int $meta_id Metadata ID.
1159 * @param int $post_id Post ID.
1160 * @param string $meta_key Meta key.
1161 * @param mixed $meta_value Meta value.
1162 */
1163 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 */
1164 {
1165 if ( '_thumbnail_id' == $meta_key ) {
1166 $this->flush_post_thumbnail($post_id);
1167 }
1168 }
1169
1170 /**
1171 * Fires immediately after deleting metadata of a post.
1172 *
1173 * @since 5.0.0
1174 *
1175 * @param array $meta_ids An array of deleted metadata entry IDs.
1176 * @param int $post_id Post ID.
1177 * @param string $meta_key Meta key.
1178 * @param mixed $meta_value Meta value.
1179 */
1180 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 */
1181 {
1182 if ( '_thumbnail_id' == $meta_key ) {
1183 $this->flush_post_thumbnail($post_id);
1184 }
1185 }
1186
1187 /**
1188 * Flushes post's cached thumbnail(s).
1189 *
1190 * @since 3.3.4
1191 *
1192 * @param integer $post_id Post ID
1193 */
1194 public function flush_post_thumbnail(int $post_id)
1195 {
1196 $wpp_uploads_dir = $this->thumbnail->get_plugin_uploads_dir();
1197
1198 if ( is_array($wpp_uploads_dir) && ! empty($wpp_uploads_dir) ) {
1199 $files = glob("{$wpp_uploads_dir['basedir']}/{$post_id}-*.*"); // get all related images
1200
1201 if ( is_array($files) && ! empty($files) ) {
1202 foreach( $files as $file ){ // iterate files
1203 if ( is_file($file) ) {
1204 @unlink($file); // delete file
1205 }
1206 }
1207 }
1208 }
1209 }
1210
1211 /**
1212 * Purges data cache when a post/page is trashed.
1213 *
1214 * @since 5.5.0
1215 */
1216 public function purge_data_cache()
1217 {
1218 $this->flush_transients();
1219 }
1220
1221 /**
1222 * Purges post from data/summary tables.
1223 *
1224 * @since 3.3.0
1225 */
1226 public function purge_post_data()
1227 {
1228 if ( current_user_can('delete_posts') ) {
1229 add_action('delete_post', [$this, 'purge_post']);
1230 }
1231 }
1232
1233 /**
1234 * Purges post from data/summary tables.
1235 *
1236 * @since 3.3.0
1237 * @param int $post_ID
1238 * @global object $wpdb
1239 */
1240 public function purge_post(int $post_ID)
1241 {
1242 global $wpdb;
1243 $data_table = "{$wpdb->prefix}popularpostsdata";
1244
1245 $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
1246
1247 if ( $post_ID_exists ) {
1248 $summary_table = "{$wpdb->prefix}popularpostssummary";
1249
1250 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
1251 // Delete from data table
1252 $wpdb->query($wpdb->prepare("DELETE FROM %i WHERE postid = %d;", $data_table, $post_ID));
1253 // Delete from summary table
1254 $wpdb->query($wpdb->prepare("DELETE FROM %i WHERE postid = %d;", $summary_table, $post_ID));
1255 // phpcs:enable
1256 }
1257
1258 // Delete cached thumbnail(s) as well
1259 $this->flush_post_thumbnail($post_ID);
1260 }
1261
1262 /**
1263 * Purges old post data from summary table.
1264 *
1265 * @since 2.0.0
1266 * @global object $wpdb
1267 */
1268 public function purge_data()
1269 {
1270 global $wpdb;
1271 $summary_table = "{$wpdb->prefix}popularpostssummary";
1272
1273 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
1274 $wpdb->query(
1275 $wpdb->prepare(
1276 "DELETE FROM %i WHERE view_date < DATE_SUB(%s, INTERVAL %d DAY);",
1277 $summary_table,
1278 Helper::curdate(),
1279 $this->config['tools']['log']['expires_after']
1280 )
1281 );
1282 //phpcs:enable
1283 }
1284
1285 /**
1286 * Displays admin notices.
1287 *
1288 * @since 5.0.2
1289 */
1290 public function notices()
1291 {
1292 /** Performance nag */
1293 $performance_nag = get_option('wpp_performance_nag');
1294
1295 if (
1296 isset($performance_nag['status'])
1297 && 3 != $performance_nag['status'] // 0 = inactive, 1 = active, 2 = remind me later, 3 = dismissed
1298 ) {
1299 $now = Helper::timestamp();
1300
1301 // How much time has passed since the notice was last displayed?
1302 $last_checked = isset($performance_nag['last_checked']) ? $performance_nag['last_checked'] : 0;
1303
1304 if ( $last_checked ) {
1305 $last_checked = ($now - $last_checked) / (60 * 60);
1306 }
1307
1308 if (
1309 1 == $performance_nag['status']
1310 || ( 2 == $performance_nag['status'] && $last_checked && $last_checked >= 24 )
1311 ) {
1312 ?>
1313 <div class="notice notice-warning">
1314 <p>
1315 <strong>WP Popular Posts:</strong>
1316 <?php
1317 printf(
1318 wp_kses(
1319 __('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'),
1320 [
1321 'a' => [
1322 'href' => []
1323 ]
1324 ]
1325 ),
1326 'https://github.com/cabrerahector/wordpress-popular-posts/wiki/7.-Performance'
1327 );
1328 ?>
1329 </p>
1330 <?php if ( current_user_can('manage_options') ) : ?>
1331 <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>
1332 <?php endif; ?>
1333 </div>
1334 <?php
1335 }
1336 }
1337
1338 $pretty_permalinks_enabled = get_option('permalink_structure');
1339
1340 if ( ! $pretty_permalinks_enabled ) {
1341 ?>
1342 <div class="notice notice-warning">
1343 <p>
1344 <strong>WP Popular Posts:</strong>
1345 <?php
1346 printf(
1347 wp_kses(
1348 /* translators: third placeholder corresponds to the I18N version of the "Plain" permalink structure option */
1349 __('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'),
1350 [
1351 'a' => [
1352 'href' => []
1353 ],
1354 'em' => []
1355 ]
1356 ),
1357 'https://wordpress.org/documentation/article/customize-permalinks/#pretty-permalinks',
1358 esc_url(admin_url('options-permalink.php')),
1359 __('Plain')
1360 );
1361 ?>
1362 </p>
1363 </div>
1364 <?php
1365 }
1366 }
1367
1368 /**
1369 * Handles performance notice click event.
1370 *
1371 * @since
1372 */
1373 public function handle_performance_notice()
1374 {
1375 $response = [
1376 'status' => 'error'
1377 ];
1378 $token = isset($_POST['token']) ? $_POST['token'] : null; // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- This is a nonce
1379 $dismiss = isset($_POST['dismiss']) ? (int) $_POST['dismiss'] : 0;
1380
1381 if (
1382 current_user_can('manage_options')
1383 && wp_verify_nonce($token, 'wpp_nonce_performance_nag')
1384 ) {
1385 $now = Helper::timestamp();
1386
1387 // User dismissed the notice
1388 if ( 1 == $dismiss ) {
1389 $performance_nag['status'] = 3;
1390 } // User asked us to remind them later
1391 else {
1392 $performance_nag['status'] = 2;
1393 }
1394
1395 $performance_nag['last_checked'] = $now;
1396
1397 update_option('wpp_performance_nag', $performance_nag);
1398
1399 $response = [
1400 'status' => 'success'
1401 ];
1402 }
1403
1404 wp_send_json($response);
1405 }
1406 }
1407