WORDPRESS REST API AND
ANGULARJS
FTW!
Roy Sivan and Josh Pollock
CalderaLearn.com
CalderaLabs.org
Join our Slack
LoopConf Slack: #workshop-wpapi
We will be posting links, taking
questions, and communicating
throughout the day via Slack
CalderaLabs.org
Hi I'm Josh
Lead Developer: Caldera Labs
I make WordPress plugins
I teach about WordPress
I wrote a book about the WordPress REST API
I am a core contributor to WordPress
I am a member of The WP Crowd
CalderaLearn.com
Hi, I'm Roy
Senior Software Engineer:
The Walt Disney Company
I am a member of The WP Crowd
I blog on roysivan.com
I teach on Lynda.com & CalderaLearn
People say Hi to me a lot
What We're Covering Today
REST API 101
Building Custom REST APIs
Unit Testing Custom REST APIs
LUNCH BREAK
AngularJS (1.x) Basics
Building Decoupled Front-ends
Building Plugin Admin Screens
Educational Philosophy
All code is or is based on real world projects
We will show different ways of doing the same
thing.
Please ask why it's different
◇ we may give you a good answer
Stop us and ask questions
Structure For Today
foreach ( $sections as $section ) :
Concepts / Lecture
Example Code Walkthrough (you will be cloning locally)
Hands-on
DIY Group
Walk Through Code Group
endforeach;
WARNING
This workshop is to help you understand the basics
and some advanced technologies. Nothing will be
production ready code.
What you need for today
IDE or text editor (PHPStorm, Sublime, etc.)
Local WP install (VVV, DesktopServer, etc.)
npm
PHPUnit (optional, included in VVV)
Composer (optional)
AngularJS Batarang Chrome Extension
https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehid
hipcmcojjgiloacoafjmpfk?hl=en
CalderaLabs.org
0.
WordPress REST API?
Do we even need it?
CalderaLabs.org
NO
But you can build cool stuff with it.
It’s all about the Data
Data!
Data!
Data!
“
CalderaLearn.com
The API allows you to take WP data and put it in a
bucket. What you do with that bucket is up to you.
-- Morten Rand Hendriksen
@mor10
CalderaLabs.org
What can we
build with it?
Facebooks? Myspaces?
API Powered Stuff
Things you can build that are powered by the API
Phone apps
Custom UI widgets on your site
Why use feed of another site, when you can API
Custom Dashboards in the admin
Custom WP Dashboard (YAS!)
User role based dashboard
CalderaLabs.org
1.
Custom REST APIs
With The WordPress REST API
CalderaLabs.org
Modify Default Routes
CalderaLabs.org
WordPress Gives Us
Routes
Which We Can Extend
CalderaLabs.org
Create Your Own Routes
CalderaLabs.org
Or We Can Make Our Own
With The Same Tools
How The REST API Works
WP_REST_Server ??WP_REST_Request WP_REST_Response
How The REST API Works
WP_REST_Server
Route
Callback
WP_REST_Request WP_REST_Response
ERROR!
ERROR!
CalderaLabs.org
Let's Talk About The
Orange Box
The Route Callback
Customizing The
Defaults
CalderaLabs.org
Extending Defaults
Adding Post Type
Support
add_action( 'init', 'my_book_cpt' );
function my_book_cpt() {
$labels = array(...);
$args = array(
...
'rest_controller_class' => 'WP_REST_Posts_Controller',
'show_in_rest' => true,
'rest_base' => 'books-api',
);
register_post_type( 'book', $args );
}
Add REST API Support To Post Type Registration
add_action( 'init', 'my_custom_post_type_rest_support', 25 );
function my_custom_post_type_rest_support() {
global $wp_post_types;
$post_type_name = 'book';
if( isset( $wp_post_types[ $post_type_name ] ) ) {
$wp_post_types[$post_type_name]->show_in_rest = true;
$wp_post_types[$post_type_name]->rest_base = $post_type_name;
$wp_post_types[$post_type_name]->rest_controller_class =
'WP_REST_Posts_Controller';
}
}
Add REST API Support To An Existing Post Type
CalderaLabs.org
Extending Defaults
Adding A Custom Field
CalderaLabs.org
Custom Field Is Any Data
Not just meta fields
function slug_get_meta_field( $object, $field_name, $request ) {
return get_post_meta( $object[ 'id' ], $field_name );
}
function slug_update_meta( $value, $object, $field_name ) {
if ( ! $value || ! is_string( $value ) ) {
return;
}
return update_post_meta( $object->ID, $field_name, strip_tags(
$value ) );
}
Adding Custom Fields To A Response: Callbacks
add_action( 'rest_api_init', 'slug_register_spaceship' );
function slug_register_spaceship() {
register_api_field( 'post',
'starship',
array(
'get_callback' => 'slug_get_meta_field',
'update_callback' => 'slug_update_meta_field',
)
);
}
Adding Custom Fields To A Response: Registration
CalderaLabs.org
Making Custom REST
APIs
The Basics
How It Works
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/author/(?P<id>d+)', array(
'methods' => 'GET',
'callback' => 'route_callback',
) );
} );
Registering A Route
CalderaLabs.org
Creating Callback Class
Register Route(s)
class my_simple_route {
public function register_routes(){
$namespace = 'my-api/v1';
register_rest_route( $namespace, '/items', [
'methods' => 'GET',
'callback' => [ $this, 'get_items' ],
'permissions_callback' => [ $this, 'get_items_permissions' ]
] );
...
}
}
Registering Route(s)
class my_simple_route {
public function register_routes(){
...
register_rest_route( $namespace, '/items/(?P<id>d+)', [
'methods' => 'GET',
'callback' => [ $this, 'get_item' ],
'permissions_callback' => [ $this,
'get_item_permissions' ]
] );
}
}
Registering Route(s)
CalderaLabs.org
Creating Callback Class
Defining Route Fields
register_rest_route( $this->namespace, '/items/(?P<id>d+)', [
...
'args' => [
'type' => [
'required' => true,
'validate_callback' => [ $this, 'validate_type' ]
],
'number' => [
'default' => 5,
'sanitize_callback' => 'absint'
]
]
] );
Registering Route(s) : Defining Fields
Registering Route(s) : Field Sanitization Callback
Use to ensure data is safe.
Defined using a callable.
Change data to a safe value.
Return prepared value
Registering Route(s) : Field Validation Callback
Use to ensure data is correct.
Defined using a callable.
Used to reject invalid requests
Return true or false
Registering Route(s) : Field Validation Callback
public function validate_type( $value ){
if( !in_array( $value, [ 'big', 'small',
'very-small' ] ) ){
return false;
}
return true;
}
CalderaLabs.org
Creating Callback Class
Permissions Callback
Registering Route(s) : Permissions Callback
Determine if user is authorized.
Return true or false
Registering Route(s) : Permissions Callback
Example: Make require login.
public function get_items_permissions(){
if( is_user_logged_in() ){
return true;
}
return false;
}
Registering Route(s) : Permissions Callback
Example: Limit To Admins
public function get_items_permissions(){
if( current_user_can( 'manage_options' ) ){
return true;
}
return false;
}
Registering Route(s) : Permissions Callback
Example: Allow Always
public function get_items_permissions(){
return true;
}
CalderaLabs.org
Creating Callback Class
Route Callback Function
Registering Route(s) : Callback
Do something with the request
Gets an object of WP_REST_Request
Should return WP_REST_Response or WP_Error
WP_Rest_Response
Represents current request
Contains:
◇ Parameters
◇ Headers
No need to access $_GET, $_POST, $_REQUEST
Implements Arrayaccess
$request->param( 'field_name' );
$request[ 'field_name' ];
Registering Route(s) : Callback
public function get_item( WP_REST_Request $request ){
$id = $request[ 'id' ];
$item = slug_crud_get( $id );
if( ! empty( $item ) && ! is_wp_error( $item ) ){
return rest_ensure_response( $item );
}elseif ( is_wp_error( $item ) ){
return $item;
}else{
$response = new WP_REST_Response( 'No items found', 404 );
return $response;
}
}
CalderaLabs.org
Initializing The Class
The rest_api_init action
CalderaLabs.org
rest_api_init
Use To Load Code Only
Needed In REST API Requests
Registering Route(s) : Initializing
add_action( 'rest_api_init', function(){
$route = new my_simple_route();
$route->register_routes();
});
CalderaLabs.org
2.
Unit Testing Custom
REST APIs
Subtitle
CalderaLearn.com
Goal
Only Test Our Endpoints
CalderaLabs.org
PHPUnit +
WP_UnitTestCase
CalderaLabs.org
Setting Up
Prepare To Test
Unit Testing 101
Make Sure Things Return What They Should Return
$this->assertEquals( 42, function_that_returns_42() );
$this->assertSame( '42', function_that_returns_42() );
$this->assertArrayHasKey( 'roy', array( 'roy' => 'hi' ) );
Google: "Pippin Williamson Unit Tests for WordPress Plugins"
Install PHPUnit
wget https://phar.phpunit.de/phpunit.phar
chmod +x phpunit.phar
mv phpunit.phar /usr/local/bin/phpunit
wget https://phar.phpunit.de/phpunit.phar
php phpunit.phar
Install WP CLI
curl -O
https://raw.githubusercontent.com/wp-cli/builds/gh-pages/
phar/wp-cli.phar
php wp-cli.phar --info
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp
Initialize Unit Tests
wp scaffold plugin-tests
CalderaLabs.org
Writing Tests
Testing!
CalderaLearn.com
Mock
Requests
An Example
public function test_get_items_author_query() {
$this->factory->post->create( array( 'post_author' => 4 ) );
$this->factory->post->create( array( 'post_author' => 4 ) );
$this->factory->post->create( array( 'post_author' => 2 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 3, count( $response->get_data() ) );
}
An Example
public function test_get_items_author_query() {
$this->factory->post->create( array( 'post_author' => 4 ) );
$this->factory->post->create( array( 'post_author' => 4 ) );
$this->factory->post->create( array( 'post_author' => 2 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts' );
$request->set_param( 'author', 4 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 2, count( $response->get_data() ) );
}
CalderaLearn.com
Test Case
Unit Test Case For REST API Endpoints
Create a reusable class that:
Create Instance of WP_REST_Server
Boot routes
Make sure route is booted
Test Case Outline
class Test_API extends WP_UnitTestCase {
/** @var WP_REST_Server*/
protected $server;
protected $namespaced_route = 'caldera-forms/v1';
public function setUp() {}
public function test_register_route() {}
public function test_endpoints() {}
}
Test Case: Set Up
protected $server;
public function setUp() {
parent::setUp();
/** @var WP_REST_Server $wp_rest_server */
global $wp_rest_server;
$this->server = $wp_rest_server = new WP_REST_Server;
do_action( 'rest_api_init' );
}
Test Case: Test Route Is Registered
public function test_register_route() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( $this->namespaced_route,
$routes );
}
Test Case: Test Endpoints Exist
public function test_endpoints() {
$the_route = $this->namespaced_route;
$routes = $this->server->get_routes();
foreach( $routes as $route => $route_config ) {
if( 0 === strpos( $the_route, $route ) ) {
$this->assertTrue( is_array( $route_config ) );
foreach( $route_config as $i => $endpoint ) {
$this->assertArrayHasKey( 'callback', $endpoint );
$this->assertArrayHasKey( 0, $endpoint[ 'callback' ], get_class( $this ) );
$this->assertArrayHasKey( 1, $endpoint[ 'callback' ], get_class( $this ) );
$this->assertTrue( is_callable( array( $endpoint[ 'callback' ][0], $endpoint[ 'callbac
][1] ) ) );
}
}
}
CalderaLearn.com
Testing
Custom Routes
Test Your API Only!!!
Test internal logic elsewhere
Test control of that logic
Test response format
Trust core
Example Route Test
class Test_Hi extends Test_API {
protected $namespace = '/hi-api/v1/names';
public function test_list() {
$request = new WP_REST_Request( 'GET', $this->namespace );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertArrayHasKey( 'name', $data[0] );
$this->assertEquals( 'shawn', $data[0][ 'name' ] );
$this->assertArrayHasKey( 'name', $data[1] );
$this->assertEquals( 'roy', $data[1][ 'name' ] );
}
}
Example Route Test
class Test_Hi extends Test_API {
protected $namespace = '/hi-api/v1/names';
public function test_single(){
$request = new WP_REST_Request( 'GET', $this->namespace . '/roy' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertArrayHasKey( 'name', $data );
$this->assertEquals( 'roy', $data[ 'name' ] );
}
}
CalderaLabs.org
3.
Getting Started With
AngularJS 1.x
Controllers, Templates, Services & Factories
CalderaLabs.org
3 con’t
Why Angular?
You know it’s in the title of the workshop, right?
“
CalderaLearn.com
AngularJS is the best JavaScript Framework.
-- Roy Sivan
Senior WordPress Engineer at Disney (so you know he is legit)
Not WordPress
To get you through the basics of
AngularJS we will be using
sample data, not WP
Our First Project
No npm or gulp
We aren’t using any build tools,
this is a pure sample
https://github.com/caldera-learn/angularjs-intro
Quicker & Easier
Quicker to get going to show
overall concepts.
Honest Truth
We already have a few projects
built in it that are ready to go
Why 1.x? 2 is in RC!
Roy is lazy
I am not lazy, but didn’t have
time to learn it deeply enough
yet to give a full workshop on it.
Josh Switched Teams
He used to be team NG1.
Now he is team VueJS.
HTML powered JavaScript
With Angular 1.x you can use
HTML to do most things reserved
for PHP.
Get the data using JS, template
with HTML. No PHP needed.
CalderaLearn.com
Step 1
Setup the APP
Setting up the app
All functionality lives within 1 app
We use ng-app to encapsulate the app in the DOM, it can be used on
any element including HTML.
Using it on the HTML encapsulates the whole DOM
Sample Data JSON
data.json
This file is going to be a sample of data, that we are going to use to build out
a simple Angular App, we will then replace it with the WordPress REST API
Injectables
Injectables are objects which can be injected and used in
controllers, directives, etc.
We will be creating our own later...
AngularJS Controllers
Controller is all about $scope
A Controller is defined by a JavaScript constructor function that is used to
augment the AngularJS Scope. When a Controller is attached to the DOM via
the ng-controller directive, AngularJS will instantiate a new Controller
object, using the specified Controller's constructor function
All new data to be used must be stored within $scope.your_key
ng-controller
All Together - JS
wpNG = {};
wpNG.app = ( function( app ){
console.log( 'initializing..' );
// define our app
app = angular.module('wpAngularApp', [])
.controller( 'listView', ['$scope', '$http', function( $scope, $http ) {
…
}]);
return app;
}(wpNG.app || {});
All Together - HTML
<div ng-app="wpAngularApp" id="app-container">
<div ng-controller="listView">
<!-- List View -->
</div>
</div>
CalderaLearn.com
Step 2
HTML! Looping through the data
First we must get data
$http is a jQuery AJAX wrapper & returns a promise
$http({method: ‘’, url: ‘’})
$http.get(url)
$http.post(url)
Because it returns a promise we use .then()
Say hello to JSON
JSON is the JavaScript Object Notation.
We display JSON with curly brackets
We work with JSON similar to PHP arrays
{{Object.key}} displays that key value
In PHP that would be $object[‘key’];
The loop HTML
<div ng-controller="listView">
<h2>Posts</h2>
<!-- Loop through $scope.posts -->
<article ng-repeat="post in posts">
<h2>{{post.title}}</h2>
</article>
</div>
Add more data
<article ng-repeat="post in posts">
<img ng-src="{{post.image}}" />
<h2>{{post.title}}</h2>
<div
class="post-content"
ng-bind-html="post.content | to_trusted">
</div>
</article>
CalderaLabs.org
4.
Decoupled Front-ends
With AngularJS
A site has no WordPress
Decoupled Front End
Decouple front end is a front end app that doesn’t live within
WordPress at all.
Your website: myawesomesite.com
Decoupled: myawesomeapp.com - but running on the same
data!
How cool is that!
Decoupled Use Cases
Phone App
Piece of functionality that communicates with WP data
Advanced, Unique UI
Combine with other APIs
Different Stack (decoupled app doesn’t need to be PHP)
Our decoupled App
npm
gulp
REST url that is publicly accessible (we will give you one)
Tacos, you always need tacos.
What you will need:
https://github.com/caldera-learn/decoupled-app
CalderaLabs.org
What About CORS?
Cross-Origin Resource Sharing
CORS
Allows browsers to make requests across
domains.
IS NOT SECURITY!!
By default WordPress REST API is same-origin
only.
Set at rest_pre_serve_request hook
Allow All Domains For All Methods
add_action( 'rest_api_init', function() {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function( $value ) {
header( 'Access-Control-Allow-Origin: *' );
header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT,
DELETE' );
header( 'Access-Control-Allow-Credentials: true' );
return $value;
});
}, 15 );
Only Allow GET Requests
add_action( 'rest_api_init', function() {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function( $value ) {
$origin = get_http_origin();
if ( $origin ) {
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
}
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( site_url() ) );
header( 'Access-Control-Allow-Methods: GET' );
return $value;
});
}, 15 );
Allows From Certain Origins
add_action( 'rest_api_init', function() {
remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
add_filter( 'rest_pre_serve_request', function( $value ) {
$origin = get_http_origin();
if ( $origin && in_array( $origin, array(
'https://hiroy.club'
) ) ) {
header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) );
header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
header( 'Access-Control-Allow-Credentials: true' );
}
return $value;
});
}, 15 );
CalderaLabs.org
Factories, Storage,
UI-Router, and
Templates
All the things!
AngularJS Factories
Factories allow you to create injectable objects.
Inject $resource into the factory it to create an object that can automatically handle REST calls.
app.factory('Posts',function($resource){
return $resource( ngWP.config.api + ‘/:post_type/:ID?per_page=:per_page’, {
post_type: 'posts',
ID:'@id',
per_page: ngWP.config.posts_per_page,
});
})
AngularJS Factories cont’d
Posts.get()
Posts.query() - like get, but expects array
Posts.save()
Posts.delete()
Posts.remove()
Unlike $http these do not return promises
Local Storage
https://github.com/grevory/angular-local-storage
An AngularJS module that gives you access to the browser’s local storage with
cookie fallback.
Local storage is a key/value store which we call on. In our example the regular posts (blog) will check for
local storage first, before hitting the API.
UI-Router
https://github.com/angular-ui/ui-router
The Angular-UI project adds modules and functionality to Angular (think WP plugins for WP)
State Driven Routing
& a lot of other functionality we won’t need.
Shows its true power when you have 1 view within another (post.detail within post.list)
Templates
Separating out templates into templates directory
Cleaner code
Separation of views
1 controller can have 1 template
1 template can be powered by multiple controllers
Pure HTML (with JSON)
CalderaLabs.org
Now Entering
The Danger Zone
First thing is first
Clone the repo
You will need to run npm install and gulp
The config file
Create a file called config.js in /assets/js
var ngWP = ngWP || {};
ngWP.config = {
api: 'https://calderaforms.com/wp-json/',
posts_per_page: 5
// Not needed for our menu: 'app'
};
Stepping through the code
Main APP file
UI Router Definitions
Small controllers live here
Templates
Blog/Author List
Blog Detail
Product List
CalderaLabs.org
5.
Plugin Admin Screens
With AngularJS
The UI Router
Making Mini-Apps To Make WordPress Better
Very Similar To
Regular NG App
Basic
Use add_admin_menu() to print basic HTML
Use ui-router to switch routes inside admin
page.
Use wp_localize_script() for config
Questions?
Use PHP or HTML files for templates?
How to handle translations?
wp_localize_script()
PHP templates
Let's Look At
Some Code!
What’s Next?
Angular 2 Theme currently in development
github.com/royboy789/Angular-Wordpress-Theme/tree/v7
Vue.JS - The new simple JS framework
ReactJS - What everyone else wants you to learn
Admin Theme Boilerplate
github.com/WordPress-Admin-JavaScript-Boilerplate/ReactJS-Boilerplate
Want more Roy & Josh teachings?
Caldera Learn teaches through 4-week live classroom
style webinars. Teachers are Josh and/or Roy with future
guest teachers
CalderaLabs.org
Josh Pollock
JoshPress.net
CalderaForms.com
CalderaLearn.com
CalderaLabs.org
@Josh412
CalderaLabs.org
Roy Sivan
theWPCrowd.com
RoySivan.com
Lynda.com
CaldraLearn.com
@royboy789
CalderaLabs.org
Want To Learn More?
Coming Very Soon
CalderaLearn.com

Caldera Learn - LoopConf WP API + Angular FTW Workshop

  • 1.
    WORDPRESS REST APIAND ANGULARJS FTW! Roy Sivan and Josh Pollock CalderaLearn.com
  • 2.
    CalderaLabs.org Join our Slack LoopConfSlack: #workshop-wpapi We will be posting links, taking questions, and communicating throughout the day via Slack
  • 3.
    CalderaLabs.org Hi I'm Josh LeadDeveloper: Caldera Labs I make WordPress plugins I teach about WordPress I wrote a book about the WordPress REST API I am a core contributor to WordPress I am a member of The WP Crowd
  • 4.
    CalderaLearn.com Hi, I'm Roy SeniorSoftware Engineer: The Walt Disney Company I am a member of The WP Crowd I blog on roysivan.com I teach on Lynda.com & CalderaLearn People say Hi to me a lot
  • 5.
    What We're CoveringToday REST API 101 Building Custom REST APIs Unit Testing Custom REST APIs LUNCH BREAK AngularJS (1.x) Basics Building Decoupled Front-ends Building Plugin Admin Screens
  • 6.
    Educational Philosophy All codeis or is based on real world projects We will show different ways of doing the same thing. Please ask why it's different ◇ we may give you a good answer Stop us and ask questions
  • 7.
    Structure For Today foreach( $sections as $section ) : Concepts / Lecture Example Code Walkthrough (you will be cloning locally) Hands-on DIY Group Walk Through Code Group endforeach;
  • 8.
    WARNING This workshop isto help you understand the basics and some advanced technologies. Nothing will be production ready code.
  • 9.
    What you needfor today IDE or text editor (PHPStorm, Sublime, etc.) Local WP install (VVV, DesktopServer, etc.) npm PHPUnit (optional, included in VVV) Composer (optional) AngularJS Batarang Chrome Extension https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehid hipcmcojjgiloacoafjmpfk?hl=en
  • 10.
  • 11.
    CalderaLabs.org NO But you canbuild cool stuff with it.
  • 12.
    It’s all aboutthe Data Data! Data! Data!
  • 13.
    “ CalderaLearn.com The API allowsyou to take WP data and put it in a bucket. What you do with that bucket is up to you. -- Morten Rand Hendriksen @mor10
  • 14.
    CalderaLabs.org What can we buildwith it? Facebooks? Myspaces?
  • 15.
    API Powered Stuff Thingsyou can build that are powered by the API Phone apps Custom UI widgets on your site Why use feed of another site, when you can API Custom Dashboards in the admin Custom WP Dashboard (YAS!) User role based dashboard
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
    CalderaLabs.org Or We CanMake Our Own With The Same Tools
  • 21.
    How The RESTAPI Works WP_REST_Server ??WP_REST_Request WP_REST_Response
  • 22.
    How The RESTAPI Works WP_REST_Server Route Callback WP_REST_Request WP_REST_Response ERROR! ERROR!
  • 23.
    CalderaLabs.org Let's Talk AboutThe Orange Box The Route Callback
  • 24.
  • 25.
  • 26.
    add_action( 'init', 'my_book_cpt'); function my_book_cpt() { $labels = array(...); $args = array( ... 'rest_controller_class' => 'WP_REST_Posts_Controller', 'show_in_rest' => true, 'rest_base' => 'books-api', ); register_post_type( 'book', $args ); } Add REST API Support To Post Type Registration
  • 27.
    add_action( 'init', 'my_custom_post_type_rest_support',25 ); function my_custom_post_type_rest_support() { global $wp_post_types; $post_type_name = 'book'; if( isset( $wp_post_types[ $post_type_name ] ) ) { $wp_post_types[$post_type_name]->show_in_rest = true; $wp_post_types[$post_type_name]->rest_base = $post_type_name; $wp_post_types[$post_type_name]->rest_controller_class = 'WP_REST_Posts_Controller'; } } Add REST API Support To An Existing Post Type
  • 28.
  • 29.
    CalderaLabs.org Custom Field IsAny Data Not just meta fields
  • 30.
    function slug_get_meta_field( $object,$field_name, $request ) { return get_post_meta( $object[ 'id' ], $field_name ); } function slug_update_meta( $value, $object, $field_name ) { if ( ! $value || ! is_string( $value ) ) { return; } return update_post_meta( $object->ID, $field_name, strip_tags( $value ) ); } Adding Custom Fields To A Response: Callbacks
  • 31.
    add_action( 'rest_api_init', 'slug_register_spaceship'); function slug_register_spaceship() { register_api_field( 'post', 'starship', array( 'get_callback' => 'slug_get_meta_field', 'update_callback' => 'slug_update_meta_field', ) ); } Adding Custom Fields To A Response: Registration
  • 32.
  • 33.
    add_action( 'rest_api_init', function() { register_rest_route( 'myplugin/v1', '/author/(?P<id>d+)', array( 'methods' => 'GET', 'callback' => 'route_callback', ) ); } ); Registering A Route
  • 34.
  • 35.
    class my_simple_route { publicfunction register_routes(){ $namespace = 'my-api/v1'; register_rest_route( $namespace, '/items', [ 'methods' => 'GET', 'callback' => [ $this, 'get_items' ], 'permissions_callback' => [ $this, 'get_items_permissions' ] ] ); ... } } Registering Route(s)
  • 36.
    class my_simple_route { publicfunction register_routes(){ ... register_rest_route( $namespace, '/items/(?P<id>d+)', [ 'methods' => 'GET', 'callback' => [ $this, 'get_item' ], 'permissions_callback' => [ $this, 'get_item_permissions' ] ] ); } } Registering Route(s)
  • 37.
  • 38.
    register_rest_route( $this->namespace, '/items/(?P<id>d+)',[ ... 'args' => [ 'type' => [ 'required' => true, 'validate_callback' => [ $this, 'validate_type' ] ], 'number' => [ 'default' => 5, 'sanitize_callback' => 'absint' ] ] ] ); Registering Route(s) : Defining Fields
  • 39.
    Registering Route(s) :Field Sanitization Callback Use to ensure data is safe. Defined using a callable. Change data to a safe value. Return prepared value
  • 40.
    Registering Route(s) :Field Validation Callback Use to ensure data is correct. Defined using a callable. Used to reject invalid requests Return true or false
  • 41.
    Registering Route(s) :Field Validation Callback public function validate_type( $value ){ if( !in_array( $value, [ 'big', 'small', 'very-small' ] ) ){ return false; } return true; }
  • 42.
  • 43.
    Registering Route(s) :Permissions Callback Determine if user is authorized. Return true or false
  • 44.
    Registering Route(s) :Permissions Callback Example: Make require login. public function get_items_permissions(){ if( is_user_logged_in() ){ return true; } return false; }
  • 45.
    Registering Route(s) :Permissions Callback Example: Limit To Admins public function get_items_permissions(){ if( current_user_can( 'manage_options' ) ){ return true; } return false; }
  • 46.
    Registering Route(s) :Permissions Callback Example: Allow Always public function get_items_permissions(){ return true; }
  • 47.
  • 48.
    Registering Route(s) :Callback Do something with the request Gets an object of WP_REST_Request Should return WP_REST_Response or WP_Error
  • 49.
    WP_Rest_Response Represents current request Contains: ◇Parameters ◇ Headers No need to access $_GET, $_POST, $_REQUEST Implements Arrayaccess $request->param( 'field_name' ); $request[ 'field_name' ];
  • 50.
    Registering Route(s) :Callback public function get_item( WP_REST_Request $request ){ $id = $request[ 'id' ]; $item = slug_crud_get( $id ); if( ! empty( $item ) && ! is_wp_error( $item ) ){ return rest_ensure_response( $item ); }elseif ( is_wp_error( $item ) ){ return $item; }else{ $response = new WP_REST_Response( 'No items found', 404 ); return $response; } }
  • 51.
  • 52.
    CalderaLabs.org rest_api_init Use To LoadCode Only Needed In REST API Requests
  • 53.
    Registering Route(s) :Initializing add_action( 'rest_api_init', function(){ $route = new my_simple_route(); $route->register_routes(); });
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
    Unit Testing 101 MakeSure Things Return What They Should Return $this->assertEquals( 42, function_that_returns_42() ); $this->assertSame( '42', function_that_returns_42() ); $this->assertArrayHasKey( 'roy', array( 'roy' => 'hi' ) ); Google: "Pippin Williamson Unit Tests for WordPress Plugins"
  • 59.
    Install PHPUnit wget https://phar.phpunit.de/phpunit.phar chmod+x phpunit.phar mv phpunit.phar /usr/local/bin/phpunit wget https://phar.phpunit.de/phpunit.phar php phpunit.phar
  • 60.
    Install WP CLI curl-O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/ phar/wp-cli.phar php wp-cli.phar --info chmod +x wp-cli.phar sudo mv wp-cli.phar /usr/local/bin/wp
  • 61.
    Initialize Unit Tests wpscaffold plugin-tests
  • 62.
  • 63.
  • 64.
    An Example public functiontest_get_items_author_query() { $this->factory->post->create( array( 'post_author' => 4 ) ); $this->factory->post->create( array( 'post_author' => 4 ) ); $this->factory->post->create( array( 'post_author' => 2 ); $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); $response = $this->server->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 3, count( $response->get_data() ) ); }
  • 65.
    An Example public functiontest_get_items_author_query() { $this->factory->post->create( array( 'post_author' => 4 ) ); $this->factory->post->create( array( 'post_author' => 4 ) ); $this->factory->post->create( array( 'post_author' => 2 ); $request = new WP_REST_Request( 'GET', '/wp/v2/posts' ); $request->set_param( 'author', 4 ); $response = $this->server->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 2, count( $response->get_data() ) ); }
  • 66.
  • 67.
    Unit Test CaseFor REST API Endpoints Create a reusable class that: Create Instance of WP_REST_Server Boot routes Make sure route is booted
  • 68.
    Test Case Outline classTest_API extends WP_UnitTestCase { /** @var WP_REST_Server*/ protected $server; protected $namespaced_route = 'caldera-forms/v1'; public function setUp() {} public function test_register_route() {} public function test_endpoints() {} }
  • 69.
    Test Case: SetUp protected $server; public function setUp() { parent::setUp(); /** @var WP_REST_Server $wp_rest_server */ global $wp_rest_server; $this->server = $wp_rest_server = new WP_REST_Server; do_action( 'rest_api_init' ); }
  • 70.
    Test Case: TestRoute Is Registered public function test_register_route() { $routes = $this->server->get_routes(); $this->assertArrayHasKey( $this->namespaced_route, $routes ); }
  • 71.
    Test Case: TestEndpoints Exist public function test_endpoints() { $the_route = $this->namespaced_route; $routes = $this->server->get_routes(); foreach( $routes as $route => $route_config ) { if( 0 === strpos( $the_route, $route ) ) { $this->assertTrue( is_array( $route_config ) ); foreach( $route_config as $i => $endpoint ) { $this->assertArrayHasKey( 'callback', $endpoint ); $this->assertArrayHasKey( 0, $endpoint[ 'callback' ], get_class( $this ) ); $this->assertArrayHasKey( 1, $endpoint[ 'callback' ], get_class( $this ) ); $this->assertTrue( is_callable( array( $endpoint[ 'callback' ][0], $endpoint[ 'callbac ][1] ) ) ); } } }
  • 72.
  • 73.
    Test Your APIOnly!!! Test internal logic elsewhere Test control of that logic Test response format Trust core
  • 74.
    Example Route Test classTest_Hi extends Test_API { protected $namespace = '/hi-api/v1/names'; public function test_list() { $request = new WP_REST_Request( 'GET', $this->namespace ); $response = $this->server->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $data = $response->get_data(); $this->assertArrayHasKey( 'name', $data[0] ); $this->assertEquals( 'shawn', $data[0][ 'name' ] ); $this->assertArrayHasKey( 'name', $data[1] ); $this->assertEquals( 'roy', $data[1][ 'name' ] ); } }
  • 75.
    Example Route Test classTest_Hi extends Test_API { protected $namespace = '/hi-api/v1/names'; public function test_single(){ $request = new WP_REST_Request( 'GET', $this->namespace . '/roy' ); $response = $this->server->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); $data = $response->get_data(); $this->assertArrayHasKey( 'name', $data ); $this->assertEquals( 'roy', $data[ 'name' ] ); } }
  • 76.
    CalderaLabs.org 3. Getting Started With AngularJS1.x Controllers, Templates, Services & Factories
  • 77.
    CalderaLabs.org 3 con’t Why Angular? Youknow it’s in the title of the workshop, right?
  • 78.
    “ CalderaLearn.com AngularJS is thebest JavaScript Framework. -- Roy Sivan Senior WordPress Engineer at Disney (so you know he is legit)
  • 79.
    Not WordPress To getyou through the basics of AngularJS we will be using sample data, not WP Our First Project No npm or gulp We aren’t using any build tools, this is a pure sample https://github.com/caldera-learn/angularjs-intro
  • 80.
    Quicker & Easier Quickerto get going to show overall concepts. Honest Truth We already have a few projects built in it that are ready to go Why 1.x? 2 is in RC! Roy is lazy I am not lazy, but didn’t have time to learn it deeply enough yet to give a full workshop on it. Josh Switched Teams He used to be team NG1. Now he is team VueJS.
  • 81.
    HTML powered JavaScript WithAngular 1.x you can use HTML to do most things reserved for PHP. Get the data using JS, template with HTML. No PHP needed.
  • 82.
  • 83.
    Setting up theapp All functionality lives within 1 app We use ng-app to encapsulate the app in the DOM, it can be used on any element including HTML. Using it on the HTML encapsulates the whole DOM
  • 84.
    Sample Data JSON data.json Thisfile is going to be a sample of data, that we are going to use to build out a simple Angular App, we will then replace it with the WordPress REST API
  • 85.
    Injectables Injectables are objectswhich can be injected and used in controllers, directives, etc. We will be creating our own later...
  • 86.
    AngularJS Controllers Controller isall about $scope A Controller is defined by a JavaScript constructor function that is used to augment the AngularJS Scope. When a Controller is attached to the DOM via the ng-controller directive, AngularJS will instantiate a new Controller object, using the specified Controller's constructor function All new data to be used must be stored within $scope.your_key ng-controller
  • 87.
    All Together -JS wpNG = {}; wpNG.app = ( function( app ){ console.log( 'initializing..' ); // define our app app = angular.module('wpAngularApp', []) .controller( 'listView', ['$scope', '$http', function( $scope, $http ) { … }]); return app; }(wpNG.app || {});
  • 88.
    All Together -HTML <div ng-app="wpAngularApp" id="app-container"> <div ng-controller="listView"> <!-- List View --> </div> </div>
  • 89.
  • 90.
    First we mustget data $http is a jQuery AJAX wrapper & returns a promise $http({method: ‘’, url: ‘’}) $http.get(url) $http.post(url) Because it returns a promise we use .then()
  • 91.
    Say hello toJSON JSON is the JavaScript Object Notation. We display JSON with curly brackets We work with JSON similar to PHP arrays {{Object.key}} displays that key value In PHP that would be $object[‘key’];
  • 92.
    The loop HTML <divng-controller="listView"> <h2>Posts</h2> <!-- Loop through $scope.posts --> <article ng-repeat="post in posts"> <h2>{{post.title}}</h2> </article> </div>
  • 93.
    Add more data <articleng-repeat="post in posts"> <img ng-src="{{post.image}}" /> <h2>{{post.title}}</h2> <div class="post-content" ng-bind-html="post.content | to_trusted"> </div> </article>
  • 94.
  • 95.
    Decoupled Front End Decouplefront end is a front end app that doesn’t live within WordPress at all. Your website: myawesomesite.com Decoupled: myawesomeapp.com - but running on the same data! How cool is that!
  • 96.
    Decoupled Use Cases PhoneApp Piece of functionality that communicates with WP data Advanced, Unique UI Combine with other APIs Different Stack (decoupled app doesn’t need to be PHP)
  • 97.
    Our decoupled App npm gulp RESTurl that is publicly accessible (we will give you one) Tacos, you always need tacos. What you will need: https://github.com/caldera-learn/decoupled-app
  • 98.
  • 99.
    CORS Allows browsers tomake requests across domains. IS NOT SECURITY!! By default WordPress REST API is same-origin only. Set at rest_pre_serve_request hook
  • 100.
    Allow All DomainsFor All Methods add_action( 'rest_api_init', function() { remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); add_filter( 'rest_pre_serve_request', function( $value ) { header( 'Access-Control-Allow-Origin: *' ); header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' ); header( 'Access-Control-Allow-Credentials: true' ); return $value; }); }, 15 );
  • 101.
    Only Allow GETRequests add_action( 'rest_api_init', function() { remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); add_filter( 'rest_pre_serve_request', function( $value ) { $origin = get_http_origin(); if ( $origin ) { header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) ); } header( 'Access-Control-Allow-Origin: ' . esc_url_raw( site_url() ) ); header( 'Access-Control-Allow-Methods: GET' ); return $value; }); }, 15 );
  • 102.
    Allows From CertainOrigins add_action( 'rest_api_init', function() { remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' ); add_filter( 'rest_pre_serve_request', function( $value ) { $origin = get_http_origin(); if ( $origin && in_array( $origin, array( 'https://hiroy.club' ) ) ) { header( 'Access-Control-Allow-Origin: ' . esc_url_raw( $origin ) ); header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' ); header( 'Access-Control-Allow-Credentials: true' ); } return $value; }); }, 15 );
  • 103.
  • 104.
    AngularJS Factories Factories allowyou to create injectable objects. Inject $resource into the factory it to create an object that can automatically handle REST calls. app.factory('Posts',function($resource){ return $resource( ngWP.config.api + ‘/:post_type/:ID?per_page=:per_page’, { post_type: 'posts', ID:'@id', per_page: ngWP.config.posts_per_page, }); })
  • 105.
    AngularJS Factories cont’d Posts.get() Posts.query()- like get, but expects array Posts.save() Posts.delete() Posts.remove() Unlike $http these do not return promises
  • 106.
    Local Storage https://github.com/grevory/angular-local-storage An AngularJSmodule that gives you access to the browser’s local storage with cookie fallback. Local storage is a key/value store which we call on. In our example the regular posts (blog) will check for local storage first, before hitting the API.
  • 107.
    UI-Router https://github.com/angular-ui/ui-router The Angular-UI projectadds modules and functionality to Angular (think WP plugins for WP) State Driven Routing & a lot of other functionality we won’t need. Shows its true power when you have 1 view within another (post.detail within post.list)
  • 108.
    Templates Separating out templatesinto templates directory Cleaner code Separation of views 1 controller can have 1 template 1 template can be powered by multiple controllers Pure HTML (with JSON)
  • 109.
  • 110.
    First thing isfirst Clone the repo You will need to run npm install and gulp
  • 111.
    The config file Createa file called config.js in /assets/js var ngWP = ngWP || {}; ngWP.config = { api: 'https://calderaforms.com/wp-json/', posts_per_page: 5 // Not needed for our menu: 'app' };
  • 112.
    Stepping through thecode Main APP file UI Router Definitions Small controllers live here Templates Blog/Author List Blog Detail Product List
  • 113.
    CalderaLabs.org 5. Plugin Admin Screens WithAngularJS The UI Router Making Mini-Apps To Make WordPress Better
  • 114.
  • 115.
    Basic Use add_admin_menu() toprint basic HTML Use ui-router to switch routes inside admin page. Use wp_localize_script() for config
  • 116.
    Questions? Use PHP orHTML files for templates? How to handle translations? wp_localize_script() PHP templates
  • 117.
  • 118.
    What’s Next? Angular 2Theme currently in development github.com/royboy789/Angular-Wordpress-Theme/tree/v7 Vue.JS - The new simple JS framework ReactJS - What everyone else wants you to learn Admin Theme Boilerplate github.com/WordPress-Admin-JavaScript-Boilerplate/ReactJS-Boilerplate
  • 119.
    Want more Roy& Josh teachings? Caldera Learn teaches through 4-week live classroom style webinars. Teachers are Josh and/or Roy with future guest teachers
  • 120.
  • 121.
  • 122.
    CalderaLabs.org Want To LearnMore? Coming Very Soon CalderaLearn.com