Writing a Post Views Count Plugin: Part 2

After creating the “Post Views Count” plugin, including a shortcode, we will now see how to improve the code adding statistics and a “Top Most Viewed Posts” widget.
I suggest you to read and understand the first part here : Writing a Post Views Count Plugin: Part 1

Writing a Post Views Count Plugin V2

Now, this part 2 will add simple statistics output via the shortcode and a great “Top Most Viewed Posts” widget, let’s see how to do this :

Edit: I added some hook filters and add a anti-bot cont lien code. Thanks to Ivan for pointing me this.

Step 1: The special plugin header

  1. /**
  2. * Plugin Name: BAW Post Views Count
  3. * Plugin URI: http://www.tutorialstag.com
  4. * Description: Count views for post and pages, shortcode [post_view] available, simple stats and widget "Most Viewed Posts" available
  5. * Version: 2.1
  6. * Author: Julio
  7. * Author URI: http://www.boiteaweb.fr
  8. * License: GPLv2
  9. **/

Then first new line:

  1. $timings = apply_filters( 'baw_count_views_timings', array( 'all'=>'', 'day'=>'Ymd', 'week'=>'YW', 'month'=>'Ym', 'year'=>'Y' ) );

This is used to set and get statistics, as you can see, the available stats are “all time”, “by day”, “by week”, “by month, “by year”. Edit: You can now add your own timings with the ‘baw_count_views_timings’ filter.

Step 2: The modified “save the data” function

  1. function baw_count_views()
  2. {
  3. if( !empty( $_SERVER['HTTP_USER_AGENT'] ) && is_singular() && !preg_match( '/Google|Slurp|MSNBot|ia_archiver|Yandex|Rambler/i', $_SERVER['HTTP_USER_AGENT'] ) ) {
  4. global $post, $timings;
  5. $IP = substr(md5( getenv( 'HTTP_X_FORWARDED_FOR' ) ? getenv( 'HTTP_X_FORWARDED_FOR' ) : getenv( 'REMOTE_ADDR' ) ), 0, 16 );
  6. if( !get_transient( 'baw_count_views-' . $IP ) ) {
  7. foreach( $timings as $time=>$date )
  8. {
  9. if( $date != '' ) $date = '-' . date( $date );
  10. $count = (int)get_post_meta( $post->ID, '_count-views_' . $time . $date, true );
  11. $count++;
  12. update_post_meta( $post->ID, '_count-views_' . $time . $date, $count );
  13. }
  14. set_transient( 'baw_count_views-' . $IP, $IP, apply_filters( 'baw_count_views_time', 60*60*12 ) ); // Default: 12 hours
  15. }
  16. }
  17. }
What’s new ?
Edit:  if( !empty( $_SERVER['HTTP_USER_AGENT'] ) && is_singular() && !preg_match( ‘/Google|Slurp|MSNBot|ia_archiver|Yandex|Rambler/i’, $_SERVER['HTTP_USER_AGENT'] ) )  = avoid search engines bots to add a count.
1) i set a $IP var, this will prevent to record more than once per 12 hours a post view.
  1. $IP = substr(md5( getenv( 'HTTP_X_FORWARDED_FOR' ) ? getenv( 'HTTP_X_FORWARDED_FOR' ) : getenv( 'REMOTE_ADDR' ) ), 0, 16 );

So i’m playing with “transient“, transient are kind of “options” but can die because we can set a life duration! So i set it to 12 hours with this line :
  1. set_transient( 'baw_count_views-' . $IP, $IP, apply_filter( 'baw_count_views_time', 60*60*12 ) ); // Default: 12 hours

Edit: you can now change the default 12 hours using the filter ‘baw_count_views_time’.
Take care, transient name length is 45 chars instead of 64 for an option!, this is why i did a substr 16
2) The foreach loop on $timings is used to update post meta for each stats value.
Remark: As you can see the meta name changed, but no worry about it now, you won’t lose you old counts.

Step 3: The modified shortcode function

  1. function baw_count_views_sc( $atts, $content = null )
  2. {
  3. global $post;
  4. extract(shortcode_atts(array(
  5. "id" => isset( $post->ID ) ? (int)$post->ID : 0,
  6. "time" => 'all',
  7. "date" => '' //YYYYMMDD format
  8. ), $atts));
  9. if( $id > 0 ) {
  10. global $timings;
  11. // These 2 lines are here to load the default date and add the "-" if needed
  12. $date = $date != '' ? $date : date( $timings[$time] );
  13. $date = $time == 'all' ? '' : '-' . $date;
  14. $count = (int)get_post_meta( $id, '_count-views_' . $time . $date, true );
  15. $count = apply_filters( 'baw_count_views_count', $count );
  16. return $count;
  17. }
  18. return '';
  19. }
What’s new ?
Edit: You can now change the $count format output with the ‘baw_count_views_count’ filter.
Edit2: By default a filter on this hook exists (see below) and add a “K” if count > 1000. You can remove it with “remove_filter( ‘baw_count_views_count’, ‘baw_count_views_count_filter’ );”
1) “time” can be filled with “all”, “day”, “week”, “month”, “year”
2) “date” can be filled with a specific date, format YYYYMMDD or YYYYWW or YYYYMM or YYYY depending on “time” setting
3) Now we load the correct meta needed. If you do not fill “date”, the default one is loaded : now!

  1. function baw_count_views_count_filter( $count )
  2. {
  3. return $count > 1000 ? $count / 1000 . 'K' : $count;
  4. }
  5. add_filter( 'baw_count_views_count', 'baw_count_views_count_filter' );
This is the default added filter. It adds a “K” if count > 1000. You can remove it with “remove_filter( ‘baw_count_views_count’, ‘baw_count_views_count_filter’ );” in your functions.php for example (or hack the code and remove the function ;p)

Step 4: The upgrade managment

  1. function baw_count_views_activation()
  2. {
  3. global $wpdb; // upgrade from 1.0 to 2.0
  4. $wpdb->update( $wpdb->postmeta, array( 'meta_key' => "_count-views_all" ), array( 'meta_key' => "_count-views" ) );
  5. }
  6. register_activation_hook( __FILE__, 'baw_count_views_activation' );
On plugin activation i switched the old _count-views in _count-views_all to keep old data and avoid a count reset. So i use a simple “update” query from WP core.
Then i used a __FILE__ to hook the plugin’s activation.

Step 5: The uninstallation script

  1. function baw_count_views_uninstaller()
  2. {
  3. global $wpdb;
  4. $wpdb->query( 'DELETE FROM ' . $wpdb->postmeta . ' WHERE meta_key LIKE "_count-views%"' );
  5. }
  6. register_uninstall_hook( __FILE__, 'baw_count_views_uninstaller' );
When you deactivate the plugin, nothing happens, but if you delete it from the plugin’s page on dashboard, this script will be fired. Again, a simple query, a “delete” one this time, i deleted all meta_key from postmeta table starting with _count-views.

Step 6: The Widget!

This code is based on the WP Core widget “Recent Posts” from /wp-includes/default-widgets.php (search for “Recent_Posts widget class”). So i won’t explain all code, just the modified one.

  1. class WP_Widget_Most_Viewed_Posts extends WP_Widget {
  2.  
  3. function __construct() {
  4. $widget_ops = array('classname' => 'widget_most_viewed_entries', 'description' => __( "The most viewed posts on your site") );
  5. parent::__construct('most-viewed-posts', __('Most Viewed Posts'), $widget_ops);
  6. $this->alt_option_name = 'widget_most_viewed_entries';
  7.  
  8. add_action( 'save_post', array(&$this, 'flush_widget_cache') );
  9. add_action( 'deleted_post', array(&$this, 'flush_widget_cache') );
  10. add_action( 'switch_theme', array(&$this, 'flush_widget_cache') );
  11. }
  12.  
  13. function widget($args, $instance) {
  14. $cache = wp_cache_get('widget_most_viewed_entries', 'widget');
  15. if ( !is_array($cache) )
  16. $cache = array();
  17.  
  18. if ( ! isset( $args['widget_id'] ) )
  19. $args['widget_id'] = $this->id;
  20.  
  21. if ( isset( $cache[ $args['widget_id'] ] ) ) {
  22. echo $cache[ $args['widget_id'] ];
  23. return;
  24. }
  25.  
  26. ob_start();
  27. extract($args);
  28.  
  29. $title = apply_filters('widget_title', empty($instance['title']) ? __('Most Viewed Posts') : $instance['title'], $instance, $this->id_base);
  30. if ( empty( $instance['number'] ) || ! $number = absint( $instance['number'] ) )
  31. $number = 10;
  32. global $timings;
  33. $date = $instance['date'] != '' ? $instance['date'] : date( $timings[$instance['time']] );
  34. $date = $instance['time'] == 'all' ? '' : '-' . $date;
  35. $time = $instance['time'];
  36.  
  37. $r = new WP_Query(array('posts_per_page' => $number, 'no_found_rows' => true, 'post_status' => 'publish', 'ignore_sticky_posts' => true, 'meta_key' => '_count-views_' . $time . $date, 'meta_value_num' => '0', 'meta_compare' => '>', 'orderby'=>'meta_value_num', 'order'=>'DESC'));
  38. if ($r->have_posts()) :
  39. ?>
  40. <?php echo $before_widget; ?>
  41. <?php if ( $title ) echo $before_title . $title . $after_title; ?>
  42. <ul>
  43. <?php while ($r->have_posts()) : $r->the_post(); ?>
  44. <?php $count = $instance['show'] ? ' (' . (int)get_post_meta( get_the_ID(), '_count-views_' . $time . $date, true ) . ')' : ''; ?>
  45. <li><a href="<?php the_permalink() ?>" title="<?php echo esc_attr(get_the_title() ? get_the_title() : get_the_ID()); ?>"><?php if ( get_the_title() ) the_title(); else the_ID(); echo $count; ?></a></li>
  46. <?php endwhile; ?>
  47. </ul>
  48. <?php echo $after_widget; ?>
  49. <?php
  50. // Reset the global $the_post as this query will have stomped on it
  51. wp_reset_postdata();
  52.  
  53. endif;
  54.  
  55. $cache[$args['widget_id']] = ob_get_flush();
  56. wp_cache_set('widget_most_viewed_entries', $cache, 'widget');
  57. }
  58.  
  59. function update( $new_instance, $old_instance ) {
  60. $instance = $old_instance;
  61. $instance['title'] = strip_tags($new_instance['title']);
  62. $instance['time'] = $new_instance['time'];
  63. $instance['date'] = $new_instance['date'];
  64. $instance['number'] = (int) $new_instance['number'];
  65. $instance['show'] = (bool)$new_instance['show'];
  66.  
  67. $this->flush_widget_cache();
  68.  
  69. $alloptions = wp_cache_get( 'alloptions', 'options' );
  70. if ( isset($alloptions['widget_most_viewed_entries']) )
  71. delete_option('widget_most_viewed_entries');
  72.  
  73. return $instance;
  74. }
  75.  
  76. function flush_widget_cache() {
  77. wp_cache_delete('widget_most_viewed_entries', 'widget');
  78. }
  79.  
  80. function form( $instance ) {
  81. $title = isset($instance['title']) ? esc_attr($instance['title']) : '';
  82. $number = isset($instance['number']) ? absint($instance['number']) : 5;
  83. $time = isset($instance['time']) ? ($instance['time']) : 'all';
  84. $date = isset($instance['date']) ? ($instance['date']) : '';
  85. $show = isset($instance['show']) ? $instance['show'] == 'on' : true;
  86.  
  87. ?>
  88. <p><label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:'); ?></label>
  89. <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></p>
  90.  
  91. <p><label for="<?php echo $this->get_field_id('number'); ?>"><?php _e('Number of posts to show:'); ?></label>
  92. <input id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo $number; ?>" size="3" /></p>
  93.  
  94. <p><label for="<?php echo $this->get_field_id('time'); ?>"><?php _e('What top do you want:'); ?></label>
  95. <select id="<?php echo $this->get_field_id('time'); ?>" name="<?php echo $this->get_field_name('time'); ?>">
  96. <?php global $timings;
  97. foreach( $timings as $timing=>$dummy ) { ?>
  98. <option value="<?php echo esc_attr( $timing ); ?>" <?php selected( $timing, $time ); ?>><?php echo ucwords( esc_html( $timing ) ); ?></option>
  99. <?php } ?>
  100. </select>
  101.  
  102. <p><label for="<?php echo $this->get_field_id('date'); ?>"><?php _e('Date format'); ?> <code>YYYYMMAA</code></label>
  103. <input id="<?php echo $this->get_field_id('date'); ?>" name="<?php echo $this->get_field_name('date'); ?>" type="text" value="<?php echo esc_attr( $date ); ?>" size="6" maxlength="8" /><br />
  104. <code><?php _e( 'If you leave blank the actual time will be used.' ); ?></code></p>
  105.  
  106. <p><label for="<?php echo $this->get_field_id('show'); ?>"><?php _e('Show posts count:'); ?></label>
  107. <input id="<?php echo $this->get_field_id('show'); ?>" name="<?php echo $this->get_field_name('show'); ?>" type="checkbox" <?php checked( $show == true, true ); ?> /></p>
  108. <?php
  109. }
  110. }

The widget is now a WP_Widget_Most_Viewed_Posts widget class, this name is arbitrary.

The query:

  1. $r = new WP_Query(array('posts_per_page' => $number, 'no_found_rows' => true, 'post_status' => 'publish', 'ignore_sticky_posts' => true, 'meta_key' => '_count-views_' . $time . $date, 'meta_value_num' => '0', 'meta_compare' => '>', 'orderby'=>'meta_value_num', 'order'=>'DESC'));
I added the meta_key selection and grab all posts with more than 0 views using ‘meta_value_num and meta_compare, also reverse the order to get the most viewed first.

Display the count number or not:

  1. $count = $instance['show'] ? ' (' . (int)get_post_meta( get_the_ID(), '_count-views_' . $time . $date, true ) . ')' : '';
I checked the “show” option and display or not the count views into parentheses.

Add the selectbox in widget options:

  1. global $timings;
  2. foreach( $timings as $timing=>$dummy ) { ?>
  3. <option value="" >
  4. <?php }
Again i used a foreach loop to display all time settings possibilities. Note the esc_attr() and esc_html() usage to sanitize data before printing it. I used the selected() WP function to autoselect the recorded value.

What else:
I also added 2 more options field named “date” and “show”, nothing to explain here.
Here and there you can find few codes lines, just variables declarations.

Step 7: Register the new widget

  1. function baw_count_views_widgets_init()
  2. {
  3. register_widget( 'WP_Widget_Most_Viewed_Posts' );
  4. }
  5. add_action( 'init', 'baw_count_views_widgets_init', 1 );
On WordPress init, i trigger this function to register the new widget class, the WP_Widget_Most_Viewed_Posts is my own class.

Step 8: How to use it now

a) [post_view] will display the counter for the actual post, custom post or page.
b) [post_view id="123"] will display the counter for the post/page ID123“.
c) [post_view time="day"] will display the count view of the day. So same for “week” will display count view for this week.
d) [post_view time="day" date="20120213"] will display the count view for this particular day: 13th, feb 2012. “date” can not be use alone, always use “time” in the same time.
e) [post_view time="month" date="201102"] will display the count view for the last year february.
This mean you can play with date in your template, kind of :
f)

  1. echo do_shortcode( '[post_views time="month" date="' . date( 'Ym', mktime( 0, 0, 0, date("n")-1, date("j"), date("Y") ) . '"]' );
This will always display the count views of the month of last year.

Step 9: How to display it

Same as before, no thing changed, so you do not have to update your templates or posts ;)

548 Downloads