An API for open educational resources

A presentation at WPCampus 2018 in July 2018 in St. Louis, MO, USA by Ned Zimmerman

Slide 1

Slide 1

An API for Open Educational Resources

Slide 2

Slide 2

Ned Zimmerman | @greatislander Lead Developer, Pressbooks https://pressbooks.org | @pressbooksdev

Slide 3

Slide 3

Slide 4

Slide 4

Today's session

  1. Introduction to Pressbooks (and some acronyms)
  2. A deep dive into the Pressbooks REST API
  3. Practical applications, challenges, future goals

Slide 5

Slide 5

  1. Introduction

Slide 6

Slide 6

What is a book?

Slide 7

Slide 7

/**

  • A discrete collection of text (and other media)
  • that is designed by an author (or authors)
  • as an internally complete representation of an idea or set of ideas
  • and/or an emotion or set of emotions
  • and is transmitted to readers in various formats. */

Slide 8

Slide 8

/**

  • A discrete collection of text (and other media)
  • that is designed by an author (or authors)
  • as an internally complete representation of an idea or set of ideas
  • and/or an emotion or set of emotions
  • and is transmitted to readers in various formats. */

Slide 9

Slide 9

/**

  • A discrete collection of text (and other media)
  • that is designed by an author (or authors)
  • as an internally complete representation of an idea or set of ideas
  • and/or an emotion or set of emotions
  • and is transmitted to readers in various formats. */

Slide 10

Slide 10

/**

  • A discrete collection of text (and other media)
  • that is designed by an author (or authors)
  • as an internally complete representation of an idea or set of ideas
  • and/or an emotion or set of emotions
  • and is transmitted to readers in various formats. */

Slide 11

Slide 11

/**

  • A discrete collection of text (and other media)
  • that is designed by an author (or authors)
  • as an internally complete representation of an idea or set of ideas
  • and/or an emotion or set of emotions
  • and is transmitted to readers in various formats. */

Slide 12

Slide 12

/**

  • A discrete collection of text (and other media)
  • that is designed by an author (or authors)
  • as an internally complete representation of an idea or set of ideas
  • and/or an emotion or set of emotions
  • and is transmitted to readers in various formats. */ (source: https://github.com/pressbooks/ pressbooks/blob/dev/inc/class-book.php )

Slide 13

Slide 13

What’s Pressbooks?

Slide 14

Slide 14

An open source plugin for WordPress

Slide 15

Slide 15

An open source plugin for WordPress Multisite

Slide 16

Slide 16

WordPress includes the ability to create a network of sites by using the multisite feature. — WordPress Codex

Slide 17

Slide 17

Sites become books

Slide 18

Slide 18

A (fairly) familiar interface

Slide 19

Slide 19

Slide 20

Slide 20

Slide 21

Slide 21

Slide 22

Slide 22

Slide 23

Slide 23

Five custom post types

Slide 24

Slide 24

— Front Matter (Preface, Introduction, &c.) — Parts — Chapters — Back Matter (Afterword, Appendices, &c.)

Slide 25

Slide 25

and — Book Information

Slide 26

Slide 26

Slide 27

Slide 27

Custom Theme Structure

Slide 28

Slide 28

Slide 29

Slide 29

Books on the Web (Webbooks)

Slide 30

Slide 30

https://press.rebus.community/financialstrategy/

Slide 31

Slide 31

Export Formats

Slide 32

Slide 32

Slide 33

Slide 33

— PDF for print on demand & digital distribution — EPUB 2 & 3 — Kindle (MOBI) — XHTML — HTMLBook (uno ffi cial draft) — OpenDocument (ODT) — WordPress eXtended RSS (WXR) — Common Cartridge

Slide 34

Slide 34

The First Five Years

Slide 35

Slide 35

Slide 36

Slide 36

Slide 37

Slide 37

Slide 38

Slide 38

Slide 39

Slide 39

Slide 40

Slide 40

Slide 41

Slide 41

What are Open Educational Resources (OERs)?

Slide 42

Slide 42

Teaching, learning and research materials in any medium, digital or otherwise, that reside in the public domain or have been released under an open license that permits no-cost access, use, adaptation and redistribution by others with no or limited restrictions. (source: 2012 Paris OER Declaration, UNESCO )

Slide 43

Slide 43

The 5R Activities (As defined by David Wiley.)

Slide 44

Slide 44

  1. Retain - the right to make, own, and control copies of the content (e.g., download, duplicate, store, and manage)

Slide 45

Slide 45

  1. Reuse - the right to use the content in a wide range of ways (e.g., in a class, in a study group, on a website, in a video)

Slide 46

Slide 46

  1. Revise - the right to adapt, adjust, modify, or alter the content itself (e.g., translate the content into another language)

Slide 47

Slide 47

  1. Remix - the right to combine the original or revised content with other material to create something new (e.g., incorporate the content into a mashup)

Slide 48

Slide 48

  1. Redistribute - the right to share copies of the original content, your revisions, or your remixes with others (e.g., give a copy of the content to a friend)

Slide 49

Slide 49

! This material is based on original writing by David Wiley, which was published freely under a Creative Commons Attribution 4.0 license at http://opencontent.org/definition/ .

Slide 50

Slide 50

A publisher’s job is to provide good APIs for their books. — Hugh McGuire

Slide 51

Slide 51

What is an Application Programming Interface (API)?

Slide 52

Slide 52

Slide 53

Slide 53

Slide 54

Slide 54

Representational State Transfer (REST)

Slide 55

Slide 55

  1. Deep Dive: v1 and v2 REST APIs

Slide 56

Slide 56

v1 REST API

Slide 57

Slide 57

Props to Brad Payne @ BCcampus !

Slide 58

Slide 58

v1 Endpoints: A Collection of Books — GET /api/v1/books — Gets information about a collection of books in a Pressbooks network. (Docs: https://opentextbc.ca/api/v1/docs/ )

Slide 59

Slide 59

Slide 60

Slide 60

v1 Endpoints: A Book — GET /api/v1/books/{book_id} — Gets information about a specific book. If parameters are passed, it returns information about chapters from that book. (Docs: https://opentextbc.ca/api/v1/docs/ )

Slide 61

Slide 61

Slide 62

Slide 62

v1 Book Endpoint https://press.rebus.community/api/v1/books/16

Slide 63

Slide 63

The WordPress REST API

Slide 64

Slide 64

Slide 65

Slide 65

v2 REST API

Slide 66

Slide 66

Thanks to Ryerson University and eCampus Ontario!

Slide 67

Slide 67

Key Goals

Slide 68

Slide 68

  1. Leverage the work of the WP REST API team

Slide 69

Slide 69

  1. Add a book metadata endpoint, conforming to an interoperable standard

Slide 70

Slide 70

Schema.org !

Slide 71

Slide 71

Pressbooks v1 REST API {

"pb_title" : "Moby Dick" ,

"pb_authors" : "Herman Melville" }

Slide 72

Slide 72

schema.org/Book {

"@context" : "http://schema.org" ,

"@type" : "Book" ,

"name" : "Moby Dick" ,

"author" : [ {

"@type" : "Person" ,

"name" : "Herman Melville" } ] }

Slide 73

Slide 73

  1. Support full-content Create, Read, Update, & Destroy (CRUD) operations for all custom post types

Slide 74

Slide 74

Slide 75

Slide 75

Slide 76

Slide 76

❎ Create* ✅ Read ❎ Update* ❎ Destroy* *Requires additional configuration.

Slide 77

Slide 77

Planning our Endpoints

Slide 78

Slide 78

Books: GET /wp-json/pressbooks/v2/books(/<id>)

Slide 79

Slide 79

Table of Contents: GET /wp-json/pressbooks/v2/toc Metadata: GET /wp-json/pressbooks/v2/metadata Content: — GET /wp-json/pressbooks/v2/front-matter(/<id>) — GET /wp-json/pressbooks/v2/parts(/<id>) — GET /wp-json/pressbooks/v2/chapters(/<id>) — GET /wp-json/pressbooks/v2/back-matter(/<id>)

Slide 80

Slide 80

Technical Approach

Slide 81

Slide 81

Human Made Coding Standards

Slide 82

Slide 82

Slide 83

Slide 83

File Layout

Slide 84

Slide 84

inc/api !"" endpoints

  

$"" controller

  

!"" class-books.php

  

!"" class-metadata.php

  

!"" class-posts.php

  

!"" class-revisions.php

  

!"" class-search.php

  

!"" class-sectionmetadata.php

  

!"" class-terms.php

  

$"" class-toc.php $"" namespace.php

Slide 85

Slide 85

inc/api !"" endpoints

  

$"" controller

  

!"" class-books.php

  

!"" class-metadata.php

  

!"" class-posts.php

  

!"" class-revisions.php

  

!"" class-search.php

  

!"" class-sectionmetadata.php

  

!"" class-terms.php

  

$"" class-toc.php $"" namespace.php

Slide 86

Slide 86

Controller Classes When you start go down the path of inheritance, it is important to understand that if the parent classes ever have to change at any point and your subclasses are dependent on them, you will have a major headache. — REST API Handbook

Slide 87

Slide 87

v2 Posts Endpoint

Slide 88

Slide 88

GET /wp-json/pressbooks/v2/front-matter(/<id>) GET /wp-json/pressbooks/v2/parts(/<id>) GET /wp-json/pressbooks/v2/chapters(/<id>) GET /wp-json/pressbooks/v2/back-matter(/<id>)

Slide 89

Slide 89

namespace

Pressbooks
Api
Endpoints
Controller ; class Posts extends \WP_REST_Posts_Controller {

/** * @param string $post_type Post type. */

public

function __construct ( $post_type )

{

parent ::__construct( $post_type );

$this ->namespace = 'pressbooks/v2' ;

$this ->overrideUsingFilterAndActions(); }

Slide 90

Slide 90

namespace

Pressbooks
Api
Endpoints
Controller ; class Posts extends \WP_REST_Posts_Controller {

/** * @param string $post_type Post type. */

public

function __construct ( $post_type )

{

parent ::__construct( $post_type );

$this ->namespace = 'pressbooks/v2' ;

$this ->overrideUsingFilterAndActions(); }

Slide 91

Slide 91

/**

  • Use object inheritance as little as possible to future-proof against WP API changes
  • With the exception of the abstract \WP_REST_Controller class, the WordPress API documentation
  • strongly suggests not overriding controllers. Instead we are encouraged to create an entirely separate
  • controller class for each end point.
  • Hooks, actions, and register_rest_field are fair game.

@see https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/#overview-the-future */

protected

function overrideUsingFilterAndActions ()

{

// The post type must have custom-fields support otherwise the meta fields will not appear in the REST API. add_post_type_support( $this ->post_type, 'custom-fields' ); add_filter( "rest_{$this->post_type}query" , [ $this , 'overrideQueryArgs' ] ); add_filter( "rest_prepare{$this->post_type}" , [ $this , 'overrideResponse' ], 10 , 3 ); add_filter( "rest_{$this->post_type}_trashable" , [ $this , 'overrideTrashable' ], 10 , 2 ); }

Slide 92

Slide 92

/**

  • Use object inheritance as little as possible to future-proof against WP API changes
  • With the exception of the abstract \WP_REST_Controller class, the WordPress API documentation
  • strongly suggests not overriding controllers. Instead we are encouraged to create an entirely separate
  • controller class for each end point.
  • Hooks, actions, and register_rest_field are fair game.

@see https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/#overview-the-future */

protected

function overrideUsingFilterAndActions ()

{

// The post type must have custom-fields support otherwise the meta fields will not appear in the REST API. add_post_type_support( $this ->post_type, 'custom-fields' ); add_filter( "rest_{$this->post_type}query" , [ $this , 'overrideQueryArgs' ] ); add_filter( "rest_prepare{$this->post_type}" , [ $this , 'overrideResponse' ], 10 , 3 ); add_filter( "rest_{$this->post_type}_trashable" , [ $this , 'overrideTrashable' ], 10 , 2 ); }

Slide 93

Slide 93

/**

  • Override the order the posts are displayed

@param array $args * * @return array */

public

function overrideQueryArgs ( $args )

{

// TODO: $args come from \Pressbooks\Book::getBookStructure, we should consolidate this somewhere? $args[ 'post_status' ] = 'any' ; $args[ 'orderby' ] = 'menu_order' ; $args[ 'order' ] = 'ASC' ;

return $args; }

Slide 94

Slide 94

/**

  • Override the order the posts are displayed

@param array $args * * @return array */

public

function overrideQueryArgs ( $args )

{

// TODO: $args come from \Pressbooks\Book::getBookStructure, we should consolidate this somewhere? $args[ 'post_status' ] = 'any' ; $args[ 'orderby' ] = 'menu_order' ; $args[ 'order' ] = 'ASC' ;

return $args; }

Slide 95

Slide 95

/**

  • Use object inheritance as little as possible to future-proof against WP API changes
  • With the exception of the abstract \WP_REST_Controller class, the WordPress API documentation
  • strongly suggests not overriding controllers. Instead we are encouraged to create an entirely separate
  • controller class for each end point.
  • Hooks, actions, and register_rest_field are fair game.

@see https://developer.wordpress.org/rest-api/extending-the-rest-api/controller-classes/#overview-the-future */

protected

function overrideUsingFilterAndActions ()

{

// The post type must have custom-fields support otherwise the meta fields will not appear in the REST API. add_post_type_support( $this ->post_type, 'custom-fields' ); add_filter( "rest_{$this->post_type}query" , [ $this , 'overrideQueryArgs' ] ); add_filter( "rest_prepare{$this->post_type}" , [ $this , 'overrideResponse' ], 10 , 3 ); add_filter( "rest_{$this->post_type}_trashable" , [ $this , 'overrideTrashable' ], 10 , 2 ); }

Slide 96

Slide 96

/**

  • Override the response object

@param \WP_REST_Response $response * @param \WP_Post $post * @param \WP_REST_Request $request * * @return mixed */

public

function overrideResponse ( $response, $post, $request )

{

if ( $post->post_type === 'chapter' ) {

// Add rest link to associated part $response->add_link(

'part' , trailingslashit( rest_url( sprintf( '%s/%s' , $this ->namespace, 'parts' ) ) ) . $post->post_parent ); }

if ( in_array( $post->post_type, [ 'front-matter' , 'chapter' , 'back-matter' ], true ) ) {

// Add rest link to metadata $response->add_link(

'metadata' , trailingslashit( rest_url( sprintf( '%s/%s/%d/metadata' , $this ->namespace, $this ->rest_base, $post->ID ) ) ) ); }

Slide 97

Slide 97

/**

  • Override the response object

@param \WP_REST_Response $response * @param \WP_Post $post * @param \WP_REST_Request $request * * @return mixed */

public

function overrideResponse ( $response, $post, $request )

{

if ( $post->post_type === 'chapter' ) {

// Add rest link to associated part $response->add_link(

'part' , trailingslashit( rest_url( sprintf( '%s/%s' , $this ->namespace, 'parts' ) ) ) . $post->post_parent ); }

if ( in_array( $post->post_type, [ 'front-matter' , 'chapter' , 'back-matter' ], true ) ) {

// Add rest link to metadata $response->add_link(

'metadata' , trailingslashit( rest_url( sprintf( '%s/%s/%d/metadata' , $this ->namespace, $this ->rest_base, $post->ID ) ) ) ); }

Slide 98

Slide 98

v2 Front Matter Endpoint https://press.rebus.community/financialstrategy/ wp-json/pressbooks/v2/front-matter/157

Slide 99

Slide 99

v2 Table of Contents Endpoint

Slide 100

Slide 100

inc/api !"" endpoints

  

$"" controller

  

!"" class-books.php

  

!"" class-metadata.php

  

!"" class-posts.php

  

!"" class-revisions.php

  

!"" class-search.php

  

!"" class-sectionmetadata.php

  

!"" class-terms.php

  

$"" class-toc.php $"" namespace.php

Slide 101

Slide 101

https://press.rebus.community/financialstrategy/ wp-json/pressbooks/v2/toc

Slide 102

Slide 102

v2 Metadata Endpoint

Slide 103

Slide 103

inc/api !"" endpoints

  

$"" controller

  

!"" class-books.php

  

!"" class-metadata.php

  

!"" class-posts.php

  

!"" class-revisions.php

  

!"" class-search.php

  

!"" class-sectionmetadata.php

  

!"" class-terms.php

  

$"" class-toc.php $"" namespace.php

Slide 104

Slide 104

https://press.rebus.community/financialstrategy/ wp-json/pressbooks/v2/metadata

Slide 105

Slide 105

Putting it all together

Slide 106

Slide 106

GET /wp-json/pressbooks/v2/books/{book_id}

Slide 107

Slide 107

[ {

"id" : "2" ,

"link" : "https://pressbooks.test/mobydick/" ,

"metadata" : {...},

"toc" : {

"front-matter" : [...],

"parts" : [ { ...,

"chapters" : [...] } ],

"back-matter" : [...] },

"_links" : {...} } ]

Slide 108

Slide 108

[ {

"id" : "2" ,

"link" : "https://pressbooks.test/mobydick/" ,

"metadata" : {...},

"toc" : {

"front-matter" : [...],

"parts" : [ { ...,

"chapters" : [...] } ],

"back-matter" : [...] },

"_links" : {...} } ]

Slide 109

Slide 109

[ {

"id" : "2" ,

"link" : "https://pressbooks.test/mobydick/" ,

"metadata" : {...},

"toc" : {

"front-matter" : [...],

"parts" : [ { ...,

"chapters" : [...] } ],

"back-matter" : [...] },

"_links" : {...} } ]

Slide 110

Slide 110

[ {

"id" : "2" ,

"link" : "https://pressbooks.test/mobydick/" ,

"metadata" : {...},

"toc" : {

"front-matter" : [...],

"parts" : [ { ...,

"chapters" : [...] } ],

"back-matter" : [...] },

"_links" : {...} } ]

Slide 111

Slide 111

Books Book TOC Metadata Chapters Front Matter Back Matter Parts Metadata Metadata Metadata

Slide 112

Slide 112

  1. Practical applications, challenges, and future goals

Slide 113

Slide 113

5R Activities: Reuse & Remix

Slide 114

Slide 114

Cloning!

Slide 115

Slide 115

Slide 116

Slide 116

5R Activities: Retain

Slide 117

Slide 117

Catalogs!

Slide 118

Slide 118

Challenges in working with the WordPress REST API

Slide 119

Slide 119

  1. No built-in authentication system

Slide 120

Slide 120

  1. Context switching in multisite is EXPENSIVE

Slide 121

Slide 121

Slide 122

Slide 122

Sites Endpoint: coming soon!

Slide 123

Slide 123

  1. Namespaced REST routes + Gutenberg = !

Slide 124

Slide 124

Future Plans

Slide 125

Slide 125

Enhancements to Cloning: — Attachment metadata (e.g. licenses/attribution) — Better fidelity — Support for third-party content (e.g. H5P activities)

Slide 126

Slide 126

Enhancements to Catalogs: — Improved performance — Live searching and filtering from full network collection

Slide 127

Slide 127

Content Discovery / Aggregation

Slide 128

Slide 128

Thank you!

Slide 129

Slide 129

Questions?