How to create a Jetpack Site Stats Aggregator

on in Blog
Last modified on

Jetpack Stats Aggregator

I believe that Jetpack Site Stats doesn’t need an introduction any more. It provides basic but good insights on your website users. It’s actually a pretty decent alternative to the new Google Analytics GA4 for small to medium websites.

Today we are going to create a Jetpack Stats Aggregator and see all our connected website stats on one page. We are going to use the ChartJS library to generate pretty charts.

Let’s start with the initial authentication. The code below will set up a page, so we can connect with our WordPress.com account. You will need your WordPress.com client ID and client secret.

The first thing you need to do is create a new WordPress.com application. This will give you a chance to describe your application and how WordPress.com should communicate with it. You should give your app the same title as your website, as that information is used in the login form users see. Once configured, you will receive your CLIENT ID and CLIENT SECRET to identify your app.

Replace items in bold with your values.

index.php

<html>
<head>
<title>Jetpack Stats Aggregator</title>
</head>
<body>
<?php
session_start();

define('CLIENT_ID', 12345);
define('CLIENT_SECRET', 'clientsecretstringhere');
define('REDIRECT_URL', 'https://www.example.com/stats.php');
define('REQUEST_TOKEN_URL', 'https://public-api.wordpress.com/oauth2/token');
define('AUTHENTICATE_URL', 'https://public-api.wordpress.com/oauth2/authenticate');

$wpcc_state = md5(mt_rand());
$_SESSION['wpcc_state'] = $wpcc_state;

$url_to = AUTHENTICATE_URL . '?' . http_build_query([
    'response_type' => 'code',
    'scope'         => 'global',
    'client_id'     => CLIENT_ID,
    'state'         => $wpcc_state,
    'redirect_uri'  => REDIRECT_URL,
]);

echo '<a href="' . $url_to . '"><img src="https://s0.wp.com/i/wpcc-button.png" width="231" alt="Authenticate"></a>';
?>
</body>
</html>

This page will look like this:

After we successfully connect with WordPress.com, we will be redirected to the next page – ⁣stats.php.

Next, we create the actual Jetpack Stats aggregator page. See the comments in the code and, again, replace items in bold with your values.

stats.php

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Jetpack Stats Aggregator</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
    box-sizing: border-box;
}
body {
    background-color: #222f3e;
    color: #c9d1d9;

    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI Variable", "Segoe UI", system-ui, ui-sans-serif, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
    font-size: 14px;
    line-height: 1.5;
}

.cell {
    flex-basis: 48%;
    padding: 16px;
    box-shadow: 0 0 16px rgba(0, 0, 0, 0.25);
    border: 1px solid rgba(0, 0, 0, 0.5);
    margin-bottom: 24px;
}
</style>

<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
</head>
<body>

<?php
define('CLIENT_ID', 12345);
define('CLIENT_SECRET', 'clientsecretstringhere');
define('REDIRECT_URL', 'https://www.example.com/stats.php');
define('REQUEST_TOKEN_URL', 'https://public-api.wordpress.com/oauth2/token');
define('AUTHENTICATE_URL', 'https://public-api.wordpress.com/oauth2/authenticate');

/**
 * If no code has been passed as a parameter, exit, as it's not a valid request.
 */
if (!isset($_GET['code'])) {
    die('Warning! Visitor may have declined access or navigated to the page without being redirected.');
}
 
session_start();



/**
 * If the passed state parameter is not the same as the one in the previous session, exit, as it's not a valid request.
 */
if ($_GET['state'] !== $_SESSION['wpcc_state']) {
    die('Warning! State mismatch. Authentication attempt may have been compromised.');
}

/**
 * Authenticate with your WordPress.com credentials to get an access token.
 * Note that you need to disable 2FA in order to use this feature.
 */
$curl = curl_init(REQUEST_TOKEN_URL);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, [
    'client_id' => CLIENT_ID,
    'client_secret' => CLIENT_SECRET,
    'grant_type' => 'password',
    'username' => 'name@email.com',
    'password' => 'yourwordpresscompasswordhere',
]);
 
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$auth = curl_exec($curl);
$secret = json_decode($auth);

echo '<pre>';
print_r($auth);
echo '</pre>';

$access_token = $secret->access_token;

/**
 * Use the access token to get a list of all the sites with Jetpack installed and Site Stats enabled
 */ 
$curl = curl_init("https://public-api.wordpress.com/rest/v1.1/jetpack-blogs/");
curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Authorization: Bearer ' . $access_token));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($curl);
$blog_ids = json_decode($response, true);
$blog_ids = $blog_ids['blogs']['blogs'];

/**
 * Loop through all sites and generate a dropdown list
 */
echo '<select id="site-selector">';
    foreach ($blog_ids as $blog) {
        echo '<option value="' . $blog['userblog_id'] . '">' . $blog['blogname'] . ' (' . $blog['siteurl'] . ')</option>';
    }
echo '</select>';



/**
 * Loop through all sites and display pretty boxes and a nice ChartJS graph
 */
echo '<div style="display: flex; flex-wrap: wrap; justify-content: space-around;">';

foreach ($blog_ids as $blog) {
    $options = [
        'http' => [
            'ignore_errors' => true,
            'header' => [
                0 => "authorization: Bearer $access_token",
            ],
        ],
    ];

    $context  = stream_context_create($options);
    $response = file_get_contents('https://public-api.wordpress.com/rest/v1.1/sites/' . $blog['userblog_id'] . '/stats', false, $context);
    $response = (array) json_decode($response);
    $visits = (array) $response['visits'];
    $data = (array) $visits['data'];

    echo '<div class="cell">';
    echo '<h2 id="blog-' . $blog['userblog_id'] . '">' . $blog['blogname'] . '</h2>';
    echo '<p><code>' . $blog['siteurl'] . '</code></p>';

    $dayArray = [];
    $dateArray = [];
    $usersArray = [];
    foreach ($data as $day) {
        $dateArray[] = $day[0];
        $dayArray[] = $day[1];
        $usersArray[] = $day[2];
    }

    $labels = "'" . implode("','", $dateArray) . "'";

    echo '<canvas id="chart-' . $blog['userblog_id'] . '" width="500" height="200"></canvas>
    <script>
    var ctx = document.getElementById("chart-' . $blog['userblog_id'] . '").getContext("2d");
    var myChart = new Chart(ctx, {
        type: "bar",
        data: {
            labels: [' . $labels . '],
            datasets: [{
                label: "Users",
                data: [' . implode(',', $usersArray) . '],
                backgroundColor: "#f5cd79",
                borderWidth: 1
            }, {
                label: "Pageviews",
                data: [' . implode(',', $dayArray) . '],
                backgroundColor: "#546de5",
                borderWidth: 1
            }]
        },
        options: {
            responsive: true,
            scales: {
                x: {
                    stacked: true,
                },
                y: {
                    stacked: false,
                    beginAtZero: true,
                },
            },
            plugins: {
            legend: {
                display: true,
                labels: {
                    color: "#c9d1d9"
                }
            }
        }
        }
    });
    </script>';

    $dayArray = [];
    $dateArray = [];
    $usersArray = [];

    echo '</div>';
}
echo '</div>';
?>

<script>
document.getElementById("site-selector").addEventListener("change", event => {
    console.log(document.getElementById("site-selector").value);
    location.href = '#blog-' + document.getElementById("site-selector").value;
});
</script>

</body>
</html>

I have also created a small dropdown at the top of the page to quickly jump to any website down the page.

Here is the final page result:

Jetpack Site Stats Aggregator

Related posts