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