Home > Net >  Uncaught SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse - Wordpress
Uncaught SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse - Wordpress

Time:06-01

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:

WordPress JSON responses wp_send_json_error() and wp_send_json_success():

Loading javascript on the frontend:

  • Related