Roots Discourse

Custom endpoints for charts appearing inside a post

Hello,

I have the following scenario, I have created some custom tables in my database that I would like to make available as secure endpoints that return JSON (simple GET endpoints like you would easily make in Laravel) so I can consume them using Charts.js for Interactive Charts to appear inside my posts. I was thinking of just having specific JS written for each post.

I’ve come across this article (https://wordpress.stackexchange.com/questions/221808/how-would-i-add-custom-tables-endpoints-to-the-wp-rest-api) but was curious if there was a recommended approach for doing such a thing better utilising Sage’s functionality?

Many thanks

That article is pretty much what I would have done. The permission checks are done in the register_rest_route. For WP request to the REST API, you’d have to pass a nonce to the header or you can use a plugin to use passwords.

Thank you for your response, are you able to perhaps point me to a reference on the WP request to the REST API via passing a nonce to the header?

Sure, there’s several files you gotta modify and I will be using demyx as my namespace.

/web/app/themes/demyx/app/setup.php
Localizing a global array variable that can be accessible anywhere on the frontend. For this example, I’ve included the REST url endpoint and nonce; accessible via demyx.rest and demyx.nonce in your .js files.

/**
 * Theme assets
 */
add_action('wp_enqueue_scripts', function () {
    wp_enqueue_style('sage/main.css', asset_path('styles/main.css'), false, null);
    wp_enqueue_script('sage/main.js', asset_path('scripts/main.js'), ['jquery'], null, true);
    wp_localize_script('sage/main.js', 'demyx', [
        'rest'  => home_url() . '/wp-json/demyx/v1',
        'nonce' => wp_create_nonce('wp_rest'),
    ]);

    if (is_single() && comments_open() && get_option('thread_comments')) {
        wp_enqueue_script('comment-reply');
    }
}, 100);
...

/web/app/themes/demyx/.eslintrc.js
Must add your global variable, otherwise webpack will say demyx is not defined.

module.exports = {
  'root': true,
  'extends': 'eslint:recommended',
  'globals': {
    'wp': true,
    'demyx': true,
  },
...

/web/app/themes/app/rest.php
I like to put specific functionalities of WordPress into their own files.

<?php

namespace App;
use WP_Query;
use WP_Error;
/*
 *      All of WP REST access is only available to logged in users (aka you)
 */
add_filter('rest_authentication_errors', function($result) {
    if (!empty( $result )) {
        return $result;
    }
    if (! is_user_logged_in()) {
        return new WP_Error('rest_not_logged_in', 'You are not currently logged in.', array( 'status' => 401));
    }
    return $result;
});
/*
 *      REST endpoints
 */
add_action('rest_api_init', function() {
    /*
    *      Custom endpoint
    *      https://domain.tld/wp-json/demyx/v1/custom
    */
    register_rest_route('demyx/v1', '/custom', array(
        'methods' => ['GET'],
        'callback' => function() {
            // Gets data from the ajax request
            $get_custom_data    = (empty($_GET['custom_data']) ) ? null : sanitize_text_field($_GET['custom_data']);
            $data               = [];
            
            // Do your wp_query() or custom PHP query here

            return $data;
         },
        'permission_callback' => function () {
            // Permission check, returns a boolean
            return current_user_can('edit_others_posts');
        }
    ));
});
...

/web/app/themes/demyx/resources/functions.php
Include rest.php into the fold.

/**
 * Sage required files
 *
 * The mapped array determines the code library included in your theme.
 * Add or remove files to the array as needed. Supports child theme overrides.
 */
array_map(function ($file) use ($sage_error) {
    $file = "../app/{$file}.php";
    if (!locate_template($file, true, true)) {
        $sage_error(sprintf(__('Error locating <code>%s</code> for inclusion.', 'sage'), $file), 'File not found');
        }
    }, [
        'helpers', 
        'setup', 
        'filters', 
        'admin', 
        'rest'
    ]
);
...

/web/app/themes/demyx/resources/assets/scripts/routes/common.js
I’m placing this in common.js as an example. Feel free to put this any file of your choosing. Probably have to make blog.js to fire in only blog posts?

export default {
  init() {
    // JavaScript to be fired on all pages
    $.ajax({
      url: demyx.rest + '/custom',
      method: 'GET',
      data: {
        custom_data: $('input').val(),
      },
      beforeSend: function (xhr) {
        xhr.setRequestHeader('X-WP-Nonce', demyx.nonce);
      },
      success: function(data) {
        console.log(data);
      },
    });
  },
  finalize() {
    // JavaScript to be fired on all pages, after page specific JS is fired
  },
};

Let me know if I’ve missed anything.