Front to Back – second iteration – 05

Handling the front-end request.

Follow up

This post is a follow up from the previous one.
I had left the work the moment the front-end required the back-end an attachment image HTML code.
The Front to Back plugin will leverage the Theme Customizer to deliver a smooth live editing experience for the user and there is only so much that JavaScript can do.

The request

Copying some JavaScript lines from the afore-mentioned post here is the code requiring the WordPress back-end the HTML markup for an attachment image starting from its source, size and attribute:

var $ = require( './../globals/jQuery.js' ),
    Backbone = require( './../globals/Backbone.js' ),
    ftbData = require( './../globals/ftbData.js' );

module.exports = Backbone.Model.extend( {

    get_attachment_image_from: function ( newSrc, size, attr ) {
        var settings = {
            beforeSend: function ( xhr ) {
                xhr.setRequestHeader( 'X-WP-NONCE', ftbData.nonce );
            },
            url: ftbData.rest_url_prefix + '/ftb/v1/markup/attachment',
            data: {
                newSrc: newSrc,
                size: size,
                attr: attr
            },
            dataType: 'json'
        };

        // return the promise to allow for client classes to handle it
        return $.get( settings );
    }

} );

An example request might be the one below:

GET /wp-json/ftb/v1/markup/attachment?newSrc=http%3A%2F%2Fwp.dev%2Fwp-content%2Fuploads%2F2016%2F03%2F150324154025-14-internet-cats-restricted-super-169.jpeg&size=&attr=class%3Dthumbnail-id HTTP/1.1
Host: wp.dev

The URL will contain the encoded size, attribute and source information.

Setting up the back-end

The Front to Back plugin uses the DI52 dependency injection container to manage its components through service providing classes.
The FTB_ServiceProviders_RestApi service provider is in charge of setting up the required addition to the REST API infrastructure introduced in WordPress 4.4:

<?php


class FTB_ServiceProviders_RestApi extends tad_DI52_ServiceProvider {

    /**
     * Binds and sets up implementations.
     */
    public function register() {
        $this->container->bind( 'FTB_Repositories_AttachmentInterface', 'FTB_Repositories_Attachment' );
        $this->container->bind( 'FTB_RestAPI_Markup_AttachmentHandlerInterface', 'FTB_RestAPI_Markup_AttachmentHandler' );

        add_action( 'rest_api_init', array( $this, 'register_rest_routes' ) );
    }

    /**
     * Binds and sets up implementations at boot time.
     */
    public function boot() {
    }

    public function register_rest_routes() {
        register_rest_route( 'ftb/v1',
            '/markup/attachment',
            array(
                'methods'  => 'GET',
                'callback' => array( $this->container->make( 'FTB_RestAPI_Markup_AttachmentHandlerInterface' ), 'get_attachment_markup' ),
            ) );
    }
}

The class will bind two concrete class implementations and add the /ftb/v1/markup/attachment route along with the delegated handler; the route will be a read-only one and the handler will serve GET requests only.

Serving the request

The method in charge of handling the request is the FTB_RestAPI_Markup_AttachmentHandler::get_attachment_markup one; the relevant class code below:

<?php


class FTB_RestAPI_Markup_AttachmentHandler implements FTB_RestAPI_Markup_AttachmentHandlerInterface {

    /**
     * @var FTB_Repositories_AttachmentInterface
     */
    protected $attachment_repository;

    public function __construct( FTB_Repositories_AttachmentInterface $attachment_repository ) {
        $this->attachment_repository = $attachment_repository;
    }

    public function get_attachment_markup( WP_REST_Request $request ) {
        if ( ! current_user_can( 'edit_theme_options' ) ) {
            return new WP_REST_Response( array( 'status' => 403, 'message' => 'Current user can\'t edit theme options' ), 403 );
        }

        $request_size = $request->get_param( 'size' );

        $size = $this->get_size( $request_size );

        $request_attr = $request->get_param( 'attr' );

        $attr = $this->get_attr( $request_attr, $request_size );

        $new_src = $request->get_param( 'newSrc' );

        $html = $this->get_attachment_html( $new_src, $size, $attr, $request_size, $request_attr );

        return $html;
    }

    /**
     * @param $request_size
     *
     * @return array|mixed|string
     */
    protected function get_size( $request_size ) {
        if ( empty( $request_size ) ) {
            $size = '';

            return $size;
        } else {
            $size = array();
            parse_str( $request_size, $size );
            $size = count( $size ) === 2 ? $size : reset( $size );

            return $size;
        }
    }

    /**
     * @param $request_attr
     * @param $request_size
     *
     * @return array
     */
    protected function get_attr( $request_attr, $request_size ) {
        if ( empty( $request_attr ) ) {
            $attr = array();
        } else {
            $attr = array();
            parse_str( $request_attr, $attr );
        }

        $attr = ftb_merge_query_string_to_array( $attr, array( 'data-ftb-attr' => $request_attr, 'data-ftb-size' => $request_size ) );

        return $attr;
    }

    /**
     * @param $new_src
     * @param $size
     * @param $attr
     *
     * @return string
     */
    protected function get_attachment_html( $new_src, $size, $attr, $request_size, $request_attr ) {
        if ( empty( $new_src ) ) {
            return ftb_get_the_post_thumbnail( $request_size, $request_attr );
        } else {
            $attachment_id = $this->attachment_repository->find_by_url( $new_src );

            if ( empty( $attachment_id ) ) {
                // try again re-fetching
                $attachment_id = $this->attachment_repository->find_by_url( $new_src, true );
            }

            if ( empty( $attachment_id ) ) {
                return ftb_get_the_post_thumbnail( $request_size, $request_attr );
            }


            return wp_get_attachment_image( $attachment_id, $size, false, $attr );
        }
    }
}

Of note here is the code in charge of verifying a user’s permission to get back some markup in the first place; this check is important as the REST API endpoint handle any request and only internal calls authenticated with the WordPress login cookie should come through.

if ( ! current_user_can( 'edit_theme_options' ) ) {
    return new WP_REST_Response( array( 'status' => 403, 'message' => 'Current user can\'t edit theme options' ), 403 );
}

Was the request made by the JavaScript code missing that beforeSend header set up the WordPress current user would always resolve to 0: the unauthenticated and powerless visitor not capable of editing the theme options.
With that header set up the code wp_get_current_user will return instead the real current cookie authenticated user.
To obtain the real markup code the class will rely on the FTB_Repositories_Attachment::find_by_url method that will, in essence, try to find an attachment post ID from its source.
With the attachment ID, size and attribute the ftb_get_the_post_thumbnail function will produce the required code that will be returned, json_encoded, to the front-end by the REST API.

Next

Now that this code is in place it’s time to extend the code beyond a draft phase and tackle the planned Theme Customizer UI adjustments.