I am new to AJAX and It seems like I might be missing a quite fundamental detail. If I do this locally without using Wordpress, it works correctly. I am sending you the link:
But if I create a function with Wordpress it comes out:
Uncaught SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse
This is my code:
add_action( 'wp_head', 'conta_visite');
function conta_visite(){
$dir = get_stylesheet_directory() . "/users";
if (!file_exists($dir)) {
wp_mkdir_p( $dir );
}
if(isset($_POST['getCustomerCount']))
{
$dbfile = get_stylesheet_directory() . "/visitors.db"; // path to data file
$expire = 3; // average time in seconds to consider someone online before removing from the list
if(!file_exists($dbfile)) {
echo json_encode(['success'=> false,'error_message'=>"Error: Data file " . $dbfile . " NOT FOUND!"]);
die();
//die("Error: Data file " . $dbfile . " NOT FOUND!");
}
if(!is_writable($dbfile)) {
echo json_encode(['success'=> false,'error_message'=>"Error: Data file " . $dbfile . " is NOT writable! Please CHMOD it to 666!"]);
die();
//die("Error: Data file " . $dbfile . " is NOT writable! Please CHMOD it to 666!");
}
$count = CountVisitors($dbfile, $expire);
if(is_numeric($count)){
$out = $count; // format the result to display 3 digits with leading 0's
echo json_encode(['success'=>'true', 'customer_count'=>$out]);
}
else
{
echo json_encode(['success'=> false, 'error_message'=>"count is not numeric"]);
}
}
else{
echo $dbfile;
echo json_encode($_POST);
}
function CountVisitors() {
global $dbfile, $expire;
$cur_ip = getIP();
$cur_time = time();
$dbary_new = array();
$dbary = json_decode(file_get_contents($dbfile),true);
if(is_array($dbary)) {
foreach($dbary as $user_ip => $user_time){
if(($user_ip != $cur_ip) && (($user_time $expire) > $cur_time)) {
$dbary_new[$user_ip] = $user_time;
}
}
}
$dbary_new[$cur_ip] = $cur_time; // add record for current user
$fp = fopen($dbfile, "w");
fputs($fp, json_encode($dbary_new));
fclose($fp);
return count($dbary_new);
}
function getIP() {
if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
{
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}
elseif(isset($_SERVER['REMOTE_ADDR'])) {
$ip = $_SERVER['REMOTE_ADDR'];
}
else
{
$ip = "0.0.0.0";
}
return $ip;
}
?>
<p id="customer_count">Customers Online: <span></span></p>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
function updateCustomerCount() {
$.ajax({
type: "POST",
cache: false,
data: {getCustomerCount: true},
success: function(response) {
var data = JSON.parse(response);
//var data = response;
console.log(response);
if (data.success) {
$("#customer_count span").text(data.customer_count);
} else {
console.log(data.error_message);
}
},
error: function(response, request, status, error) {
console.log(error);
}
});
setTimeout(updateCustomerCount, 2000);
}
updateCustomerCount();
</script>
<?php
}
This is the Demo link in Wordpress: You can find the counter in the upper right corner of the header.
Can you help me? Because I'm going crazy and I can't figure out where I'm going wrong....
CodePudding user response:
Ok so this should do what you're looking for. I've rewritten your code for better WordPress integration.
First, you'll need to create a new javascript file name visitor-counter.js
and place it a js
folder in your themes directory. This will be used for our jQuery and ajax request.
/js/visitor-counter.js
( function( $ ) {
window.VisitorCounter = window?.VisitorCounter || {
/**
* @type {object} Temp storage object
*/
_temp: {},
/**
* Get a PHP variable.
*
* @param varName
* @returns {*}
*/
getVar( varName ) {
const vars = window?.VisitorCounterVars;
return vars?.[ varName ];
},
/**
* Make ajax request to server, store response in temp object.
*
* @returns {Promise<unknown>}
*/
request() {
return new Promise( ( accept, reject ) => {
const url = this.getVar( 'ajaxUrl' );
const action = this.getVar( 'ajaxAction' );
if ( url && action ) {
$.ajax( {
url: url,
data: {
action: action
},
cache: false
} ).then( response => {
this._temp.count = response.data;
accept( response );
} );
} else {
reject( 'Visitor counter ajax url or action not available.' );
}
} );
},
/**
* Get the count value.
*
* @returns {number}
*/
getCount() {
return parseInt( this._temp?.count || 0 );
},
/**
* Refresh data continuously.
*
* @param {callback} callback
* @param {number} timeout
*/
refresh( callback, timeout ) {
this._temp.lastRefreshed = Date.now();
this.request().then( () => {
const now = Date.now();
callback.apply( this );
const timeoutDiff = now - this._temp.lastRefreshed;
// If request took longer than timeout, run next refresh instantly.
if ( timeout && timeoutDiff >= timeout ) {
this.refresh( callback, timeout );
}
// Request was quicker than timeout, queue next refresh call.
else {
setTimeout( () => {
this.refresh( callback, timeout );
}, timeout - timeoutDiff );
}
} );
}
};
// Initiate refresh loop
VisitorCounter.refresh( function() {
$( '#customer_count span' ).text( VisitorCounter.getCount() );
}, 2000 );
} )( jQuery );
Next add the below to your themes functions.php file...
/functions.php
class VisitorCounter {
private static $instance;
/**
* @var string $file Relative path to the tracking file.
*/
public $file = '/users/visitors.db';
/**
* @var int $expires Automatically expire vistor after X amount of seconds.
*/
public $expires = 3;
/**
* @return $this
*/
public static function init() {
if ( !static::$instance ) {
static::$instance = new static( ...func_get_args() );
}
return static::$instance;
}
/**
* Get the full database file path and create file if not exists.
*
* @return string|false
*/
public function getPath() {
$path = get_stylesheet_directory() . '/' . ltrim( $this->file, '/' );
$exists = file_exists( $path );
if ( !$exists ) {
wp_mkdir_p( dirname( $path ) );
$exists = touch( $path );
}
return $exists ? $path : false;
}
/**
* Read the contents of the visitors database file as a JSON.
*
* @return array
*/
public function getData() {
if ( $path = $this->getPath() ) {
if ( $contents = file_get_contents( $path ) ) {
if ( $json = @json_decode( $contents, true ) ) {
return $this->cleanData( $json );
}
}
}
return [];
}
/**
* Clean the visitor data by removing expired entries.
*
* @param array $input
* @return array
*/
private function cleanData( array $input ) {
$now = time();
foreach ( $input as $ip => $timestamp ) {
if ( $timestamp $this->expires < $now ) {
unset( $input[ $ip ] );
}
}
return $input;
}
/**
* Add visitor count data.
*
* @param string $ip
* @param int $timestamp
* @return array
*/
public function logUser() {
// Get current data.
$data = $this->getData();
// Add new entry.
if ( $ip = $this->getUserIp() ) {
$data[ $ip ] = time();
}
// Clean data before saving.
$data = $this->cleanData( $data );
// Encode and save data.
file_put_contents( $this->getPath(), wp_json_encode( $data ) );
// Return data.
return $data;
}
/**
* Get the current users IP address.
*
* @return string|null
*/
public function getUserIp() {
// In order of preference, with the best ones for this purpose first.
$address_headers = [
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR'
];
$client_ip = null;
foreach ( $address_headers as $header ) {
if ( !empty( $_SERVER[ $header ] ) ) {
/*
* HTTP_X_FORWARDED_FOR can contain a chain of comma-separated
* addresses. The first one is the original client. It can't be
* trusted for authenticity, but we don't need to for this purpose.
*/
$address_chain = explode( ',', $_SERVER[ $header ] );
$client_ip = trim( $address_chain[ 0 ] );
break;
}
}
return $client_ip;
}
}
/**
* Helper function for visitor counter class.
*
* @return VisitorCounter
*/
function visitor_counter() {
return VisitorCounter::init();
}
/**
* Register an ajax request handler.
*/
add_action( 'wp_ajax_active_visitor', 'handle_visitor_activity' );
add_action( 'wp_ajax_nopriv_active_visitor', 'handle_visitor_activity' );
function handle_visitor_activity() {
$controller = visitor_counter();
$controller->logUser();
wp_send_json_success( count( $controller->getData() ) );
}
/**
* Load our javascript file on the frontend data.
*/
add_action( 'wp_enqueue_scripts', function() {
// Load the counter javascript file after `jquery` in the footer.
wp_enqueue_script( 'visitor-counter', get_template_directory_uri() . '/js/visitor-counter.js', [ 'jquery' ], '1.0.0', true );
// Load php data that can be accessed in javascript.
wp_localize_script( 'visitor-counter', 'VisitorCounterVars', [
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'ajaxAction' => 'active_visitor'
] );
} );
Things to lookup...
WordPress ajax handlers (add_action( 'wp_ajax_' )
and add_action( 'wp_ajax_nopriv_' )
) documentation can be found here:
- https://developer.wordpress.org/plugins/javascript/ajax/
- https://developer.wordpress.org/reference/hooks/wp_ajax_action/
- https://developer.wordpress.org/reference/hooks/wp_ajax_nopriv__requestaction/
WordPress JSON responses wp_send_json_error()
and wp_send_json_success()
:
- https://developer.wordpress.org/reference/functions/wp_send_json_error/
- https://developer.wordpress.org/reference/functions/wp_send_json_success/
Loading javascript on the frontend: