<?php /**
* Post Views Counter with Cache Support
*
* This implementation uses AJAX to bypass caching mechanisms and
* provides accurate view counting even in heavily cached environments.
*/
/**
* Register post views counter assets and AJAX handlers
*/
function custom_post_views_init() {
// Register and enqueue the JavaScript for AJAX view counting
wp_register_script(
'custom-post-views',
get_template_directory_uri() . '/inc/js/post-views.js',
array('jquery'),
'1.0.0',
true
);
// Pass data to our script
wp_localize_script(
'custom-post-views',
'customPostViews',
array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('custom_post_views_nonce')
)
);
// Only enqueue on single posts
if (is_single()) {
wp_enqueue_script('custom-post-views');
}
}
add_action('wp_enqueue_scripts', 'custom_post_views_init');
// Register AJAX handlers
add_action('wp_ajax_custom_increment_post_views', 'custom_increment_post_views');
add_action('wp_ajax_nopriv_custom_increment_post_views', 'custom_increment_post_views');
/**
* AJAX handler to increment post views
*/
function custom_increment_post_views() {
// Verify security nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'custom_post_views_nonce')) {
wp_send_json_error('Security check failed');
}
// Get post ID
$post_id = isset($_POST['post_id']) ? intval($_POST['post_id']) : 0;
if ($post_id <= 0) {
wp_send_json_error('Invalid post ID');
}
// Use transients to prevent multiple counts from same user in a session
$visitor_ip = custom_get_visitor_ip();
$user_viewed_key = 'post_viewed_' . $post_id . '_' . $visitor_ip;
if (get_transient($user_viewed_key)) {
// User already viewed this post recently
wp_send_json_success(array(
'message' => 'View already counted',
'views' => custom_get_post_views($post_id)
));
return;
}
// Set transient to prevent duplicate counts (expires after 24 hours)
set_transient($user_viewed_key, 1, 24 * HOUR_IN_SECONDS);
// Increment the post view count
$views = custom_get_post_views($post_id);
$views++;
update_post_meta($post_id, 'custom_post_views', $views);
// Return success with updated view count
wp_send_json_success(array(
'message' => 'View counted successfully',
'views' => $views
));
}
/**
* Get visitor IP (with privacy considerations)
*/
function custom_get_visitor_ip() {
$ip = '';
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
// Hash the IP for privacy
return md5($ip);
}
/**
* Get post views count
*/
function custom_get_post_views($post_id = null) {
if (!$post_id) {
$post_id = get_the_ID();
}
$views = get_post_meta($post_id, 'custom_post_views', true);
// Return views or 0 if none found
return empty($views) ? 0 : intval($views);
}
/**
* Display post views in your theme
*/
function custom_display_post_views($post_id = null, $suffix = ' views') {
if (!$post_id) {
$post_id = get_the_ID();
}
$views = custom_get_post_views($post_id);
// Format the number for better readability
if ($views > 999 && $views < 1000000) {
$formatted = number_format($views / 1000, 1) . 'K';
} elseif ($views > 999999) {
$formatted = number_format($views / 1000000, 1) . 'M';
} else {
$formatted = $views;
}
echo '<span class="post-views"><i class="fa fa-eye" aria-hidden="true"></i> ' . esc_html($formatted . $suffix) . '</span>';
}
/**
* Add views column to admin posts list
*/
function custom_add_views_column($columns) {
$columns['post_views'] = 'Views';
return $columns;
}
add_filter('manage_posts_columns', 'custom_add_views_column');
/**
* Display views count in admin column
*/
function custom_display_views_column($column, $post_id) {
if ($column === 'post_views') {
echo esc_html(custom_get_post_views($post_id));
}
}
add_action('manage_posts_custom_column', 'custom_display_views_column', 10, 2);
/**
* Make views column sortable in admin
*/
function custom_sortable_views_column($columns) {
$columns['post_views'] = 'post_views';
return $columns;
}
add_filter('manage_edit-post_sortable_columns', 'custom_sortable_views_column');
/**
* Handle admin sorting by views count
*/
function custom_views_column_orderby($query) {
if (!is_admin() || !$query->is_main_query()) {
return;
}
if ($query->get('orderby') === 'post_views') {
$query->set('meta_key', 'custom_post_views');
$query->set('orderby', 'meta_value_num');
}
}
add_action('pre_get_posts', 'custom_views_column_orderby');
/**
* Add function to quickly add post views display to theme templates
*/
function custom_the_post_views($suffix = ' views') {
custom_display_post_views(get_the_ID(), $suffix);
}
Post-views.js
/**
* JavaScript for post views counter
* This file handles the AJAX request to increment post views
* Save this as post-views.js in your theme's js directory
*/
jQuery(document).ready(function($) {
// Only run on single posts that have an ID
if ($('body').hasClass('single') && $('article').length > 0) {
var postId = $('article').attr('id');
// Extract the post ID from the article ID (usually in format post-123)
if (postId) {
postId = postId.replace('post-', '');
} else {
// Fallback: try to get from body class
var bodyClasses = $('body').attr('class').split(' ');
$.each(bodyClasses, function(index, className) {
if (className.indexOf('postid-') === 0) {
postId = className.replace('postid-', '');
return false;
}
});
}
if (postId && !isNaN(postId)) {
// Add a small delay to ensure the page has fully loaded
setTimeout(function() {
// Send AJAX request to count the view
$.ajax({
type: 'POST',
url: customPostViews.ajaxurl,
data: {
action: 'custom_increment_post_views',
post_id: postId,
nonce: customPostViews.nonce
},
success: function(response) {
if (response.success) {
// Update view count on page if needed
$('.post-views-count').text(response.data.views);
}
},
error: function() {
console.log('Failed to register post view');
}
});
}, 1500); // 1.5 second delay
}
}
});