Start Extending the WordPress API

A quick run down about extending the API (see notes inside, there aren’t many right now, I need to add more, that’s my bad).

This covers several scenarios and items that you may encounter yourself.

  • Next Post
  • Previous Post
  • Getting the Featured Image
  • Title
  • Link
  • ID
  • Get the posts first image
  • Get Next/Previous posts first image
  • Getting custom fields
<?php
if( class_exists( 'WP_REST_Posts_Controller' ) ){
  Class WP_REST_DT_Posts_Controller extends WP_REST_Posts_Controller {
    /**
     * WP_REST_DT_Posts_Controller constructor.
     */
    public function __construct() {
      parent::__construct( 'post' );
      $this->namespace = 'deep-thoughts/v2';
    }
    /**
     * Register our custom route
     *
     * We
     */
    public function register_routes() {
      register_rest_route( $this->namespace, '/posts', array(
        array(
          'methods'             => WP_REST_Server::READABLE,
          'callback'            => array( $this, 'get_items' ),
          'permission_callback' => array( $this, 'get_items_permissions_check' ),
          'args'                => $this->get_collection_params(),
        ),
        'schema' => array( $this, 'get_public_item_schema' ),
      ) );
    }
    /**
     * Get items to return in API call
     *
     * This method is used to override the parent get_items method so we can format,
     * add custom fields, and return the data the way we want.
     *
     * @param \WP_REST_Request $request
     *
     * @return \WP_Error|\WP_REST_Response
     */
    public function get_items( $request ) {
      /**
       * Use parent get_items to pull all post data and retain support
       * for all arguments
       */
      $posts = parent::get_items( $request );

      /**
       * As long as data is returned, we can now loop through it and customize the output,
       * add custom fields, etc.
       */

      

      if ( $posts instanceof WP_REST_Response && isset( $posts->data ) && ! empty( $posts->data ) ) {
        $new_post_data = [];

        foreach ( $posts->data as $post_index => $post_data ) {
          $new_post = [
            'id'    => $this->extract_post_data( 'id', $post_data ),
            'title' => $this->extract_post_data( 'title', $post_data ),
            'link'  => $this->extract_post_data( 'link', $post_data ),
          ];

          $featured_image = wp_get_attachment_url( $this->extract_post_data( 'featured_media', $post_data ) );
          if ( $featured_image ) {
            $new_post['featured_image'] = $featured_image;
          }

          // Current post image
          $image_src = catch_post_image( $this->extract_post_data( 'content', $post_data ) );
          if ( $image_src ) {
            $new_post['image_src'] = $image_src;
          }



          // Next post image
          if ( isset( $posts->data[ $post_index + 1 ] ) ) {
            $next_post_data = $posts->data[ $post_index + 1 ];
            $image_src = catch_post_image( $this->extract_post_data( 'content', $next_post_data ) );
            if ( $image_src ) {
              $new_post['Next_Post_Obj']['next_image_src'] = $image_src;
            }
          }

          // Prev post image
          if ( isset( $posts->data[ $post_index - 1 ] ) ) {
            $prev_post_data = $posts->data[ $post_index - 1 ];
            $image_src = catch_post_image( $this->extract_post_data( 'content', $prev_post_data ) );
            if ( $image_src ) {
              $new_post['Prev_Post_Obj']['prev_image_src'] = $image_src;
            }
          }

          $new_post_data[ $post_index ] = $new_post;

          $next_post = $posts->data[$post_index + 1];
          $prev_post = $posts->data[$post_index - 1];
          
          if ( ! empty( $next_post ) ) {
              $new_post_data[ $post_index ]['Next_Post_Obj']['Next_ID'] = $next_post['id'];
              $new_post_data[ $post_index ]['Next_Post_Obj']['Next_Title'] = $next_post['title'];
              $new_post_data[ $post_index ]['Next_Post_Obj']['Next_Link'] = $next_post['link'];
          }
          if ( ! empty( $prev_post ) ) {
              $new_post_data[ $post_index ]['Prev_Post_Obj']['Prev_ID'] = $prev_post['id'];
          }

          if ( function_exists( 'get_fields' ) ) {
            /**
             * Get specific ACF field
             * Last argument in get_field set to FALSE to return unformatted value
             *
             * @see https://www.advancedcustomfields.com/resources/get_field/
             */
            $new_post_data[ $post_index ]['_edit_lock'] = get_field( '_edit_lock', $post_data['id'], false );
            /**
             * Get all ACF custom fields and add to post data
             *
             * @see https://www.advancedcustomfields.com/resources/get_fields/
             */
            $custom_fields = get_fields( $post_data['id'] );
            // As long as there are custom fields, loop through each adding it to the new data to return
            if ( ! empty( $custom_fields ) ) {
              foreach ( $custom_fields as $custom_field_key => $custom_field_value ) {
                $new_post_data[ $post_index ][ $custom_field_key ] = $custom_field_value;
              }
            }
          }
          /**
           * You can also add any custom fields you want by using the core WordPress function.
           * We surround the get_post_meta() function with maybe_unserialize() in case the value is a serialized array
           */
          $new_post_data[ $post_index ]['_edit_lock_core'] = maybe_unserialize( get_post_meta( $post_data['id'], '_edit_lock', true ) );
        }

        $posts->data = $new_post_data;
      }

      return $posts;
    }

    /**
     * Return value for specific key from passed data
     *
     * This method is used to return a value (if found in post data), or return
     * an empty string if not found.
     *
     * @param $key
     * @param $data
     *
     * @return mixed|string
     */
    public function extract_post_data( $key, $data ) {
      // If passed data is not an array, it's empty, or the requested key does not exist, return empty string
      if ( ! is_array( $data ) || empty( $data ) || ! isset( $data[ $key ] ) ) return '';
      // If value is not an array, go ahead and return the value
      if ( ! is_array( $data[ $key ] ) ) {
        return $data[ $key ];
      } elseif ( array_key_exists( 'rendered', $data[ $key ] ) ) {
        // If value is an array, chances are the value is under the 'rendered' key, if so return that value
        return $data[ $key ][ 'rendered' ];
      }
      // All else fails, return empty string.
      return '';
    }
  }

  function catch_post_image( $post_content ) {
    preg_match_all( '/<img.+src=[\'"]([^\'"]+)[\'"].*>/i', $post_content, $matches );
    $first_img = $matches [1] [0];

    // no image found, return false
    if ( empty( $first_img ) ) {
      return false;
    }

    return $first_img;
  }
  
  /**
   * Since we're extending the existing post API class, we need to initialize our
   * class on the rest_api_init action
   */
  add_action( 'rest_api_init', 'init_deep_thoughts');
  /**
   * Custom function to initialize our extending class. Used a function to make sure
   * code is compatible with older versions of PHP (instead of using anonymous function)
   */
  function init_deep_thoughts(){
    $deep_thoughts = new WP_REST_DT_Posts_Controller();
    $deep_thoughts->register_routes();
  }
}