Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A real-world controller for a WP REST API endpoint
<?php
namespace LitART\LMS\REST;
use LitART\LMS\Query;
use WP_Error;
use WP_REST_Server;
class Records_Controller extends Posts_Controller {
protected static $schema_title = 'record';
public function __construct( $post_type ) {
parent::__construct( $post_type );
add_filter( 'rest_record_query', array( $this, 'filter_rest_record_query' ), 10, 2 );
}
public function register_routes() {
register_rest_field( self::$schema_title, 'parent', array(
'schema' => array(
'description' => 'Associated quiz or activity of the record.',
'type' => 'array',
'required' => true,
'context' => array( 'embed', 'view', 'edit' ),
'arg_options' => array(
'validate_callback' => function( $value, $request, $field ) {
$parent = get_post( (int) $value );
if ( $parent && in_array( $parent->post_type, array( 'activity', 'quiz' ) ) ) {
return true;
}
return false;
}
),
),
) );
$sanitize_record_data = function( $value, $fields ) {
if ( ! is_array( $value ) ) {
return array();
}
$cleaned_data = array();
foreach( $value as $id => $dirty ) {
$field = false;
foreach( $fields as $f ) {
if ( $f['id'] === $id ) {
$field = $f;
break;
}
}
if ( empty( $field ) ) {
continue;
}
$clean = '';
switch ( $field['type'] ) {
case 'true_false':
$clean = (bool) $dirty;
break;
case 'multiple_choice':
if ( in_array( $dirty, $field['multiple_choice_options'] ) ) {
$clean = $dirty;
}
break;
case 'short_answer':
$clean = sanitize_text_field( $dirty );
break;
case 'essay':
$clean = wp_filter_nohtml_kses( $dirty );
break;
}
$cleaned_data[ $id ] = $clean;
}
return $cleaned_data;
};
register_rest_field( self::$schema_title, 'data', array(
'get_callback' => function( $response, $field ) {
if ( $data = get_post_meta( $response['id'], 'data', true ) ) {
return $data;
}
return array();
},
'update_callback' => function( $value, $post, $field ) use ( $sanitize_record_data ) {
$fields = get_post_meta( $post->post_parent, 'engagement_modules', true );
update_post_meta( $post->ID, 'data', $sanitize_record_data( $value, $fields ) );
},
'schema' => array(
'description' => 'Data stored to the record',
'type' => 'array',
'context' => array( 'embed', 'view', 'edit' ),
),
) );
parent::register_routes();
}
public function get_items_permissions_check( $request ) {
if ( ! is_user_logged_in() ) {
return new WP_Error( 'rest_cannot_read', "Sorry, you aren't allowed to read records.", array( 'status' => 401 ) );
}
if ( ! $request['author'] && ! $request['organization'] && ! current_user_can( 'edit_others_records' ) ) {
return new WP_Error( 'rest_cannot_read', "Sorry, you aren't allowed to read others' records.", array( 'status' => 403 ) );
}
if ( $request['author'] && (int) $request['author'] !== get_current_user_id() && ! current_user_can( 'edit_user', $request['author'] ) ) {
return new WP_Error( 'rest_cannot_read', "Sorry, you aren't allowed to read others' records.", array( 'status' => 403 ) );
}
if ( $request['organization'] && ! Query::is_facilitator_member_of_organization( get_current_user_id(), $request['organization'] ) ) {
return new WP_Error( 'rest_cannot_read', "Sorry, you aren't allowed to read others' records.", array( 'status' => 403 ) );
}
return parent::get_items_permissions_check( $request );
}
public function filter_rest_record_query( $prepared_args, $request ) {
if ( isset( $request['organization'] ) ) {
$student_ids = Query::get_organization_student_ids( (int) $request['organization'] );
if ( ! empty( $student_ids ) ) {
$prepared_args['author__in'] = $student_ids;
} else {
$prepared_args['author__in'] = array( LITART_IMPOSSIBLY_HIGH_NUMBER );
}
}
return $prepared_args;
}
public function create_item_permissions_check( $request ) {
$ret = parent::create_item_permissions_check( $request );
if ( ! $ret || is_wp_error( $ret ) ) {
return $ret;
}
$existing = get_posts( array(
'post_parent' => (int) $request['parent'],
'post_type' => 'record',
'post_status' => 'any',
'author' => $request['author'] ? (int) $request['author'] : get_current_user_id(),
'fields' => 'ids',
) );
if ( $existing ) {
return new WP_Error( 'rest_already_exists', 'Sorry, a record already exists for this activity or quiz.', array( 'status' => 400 ) );
}
return true;
}
public function update_item_permissions_check( $request ) {
$ret = parent::update_item_permissions_check( $request );
if ( ! $ret || is_wp_error( $ret ) ) {
return $ret;
}
return true;
}
public function get_item_schema() {
$schema = parent::get_item_schema();
$schema['properties']['status']['arg_options']['default'] = 'draft';
$schema['properties']['status']['context'] = array( 'embed', 'view', 'edit' );
// Unset attributes we don't want to use.
$unused_attributes = array(
'date',
'date_gmt',
'link',
'password',
'modified',
'modified_gmt',
'slug',
);
foreach( $unused_attributes as $attribute ) {
if ( isset( $schema['properties'][ $attribute ] ) ) {
unset( $schema['properties'][ $attribute ] );
}
}
return $this->add_additional_fields_schema( $schema );
}
public function get_collection_params() {
$params = parent::get_collection_params();
$params['order']['default'] = 'asc';
$params['orderby']['default'] = 'id';
$params['status']['default'] = 'any';
$params['author']['type'] = 'integer';
unset( $params['author']['validate_callback'] );
$params['author']['sanitize_callback'] = 'absint';
$params['organization'] = array(
'type' => 'integer',
'description' => 'Limit result set to records of a specific organization.',
'validate_callback' => 'rest_validate_request_arg',
);
$unused_params = array(
'after',
'before',
'author_exclude',
'filter',
);
foreach( $unused_params as $param ) {
if ( isset( $params[ $param ] ) ) {
unset( $params[ $param ] );
}
}
return $params;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment