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