2026-03-27 15:53:29 +00:00

500 lines
13 KiB
PHP

<?php
/*
* don't extend TD_API_Model
* instead extend TD_DB_Model
*/
abstract class TD_API_Model extends TD_Model {
protected $plugin;
protected $api_enabled = true;
protected $api_namespace;
protected $api_version = '1';
protected $api_base;
public function __construct( $plugin ) {
parent::__construct( $plugin );
$this->plugin = $plugin;
$this->api_hooks();
}
public function api_hooks() {
if ( empty( $this->api_enabled ) ) {
return;
}
if ( empty( $this->api_namespace ) ) {
die( 'define $this->api_namespace in ' . get_class( $this ) ); // phpcs:ignore
}
add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
}
public function rest_api_init() {
$this->register_routes();
}
public function get_api_base() {
if ( !empty( $this->api_base ) ) {
return $this->api_base;
}
$this->api_base = $this->get_pluralized_slug();
return $this->api_base;
}
/**
* Register the routes for the objects of the controller.
*/
public function register_routes() {
$namespace = $this->api_namespace.'/v' . $this->api_version;
$base = $this->get_api_base();
register_rest_route( $namespace, '/' . $base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => array(
),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array(),
),
) );
register_rest_route( $namespace, '/' . $base . '/bulk', array(
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_items' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array(),
),
) );
register_rest_route( $namespace, '/' . $base . '/(?P<id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => array(
'default' => 'view',
),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => array(),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
),
),
),
) );
// Some servers have DELETE method disabled for some reason.
// It's common enough that this workaround is nice to have as an option.
register_rest_route( $namespace, '/' . $base . '/(?P<id>[\d]+)/delete', array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
),
),
),
) );
$this->register_custom_routes();
// register_rest_route( $namespace, '/' . $base . '/schema', array(
// 'methods' => WP_REST_Server::READABLE,
// 'callback' => array( $this, 'get_public_item_schema' ),
// ) );
}
public function register_custom_routes() {
}
/**
* Get a collection of items
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$params = $request->get_params();
$schema = $this->get_schema();
$data = $this->query( $params );
$data = $this->prepare_collection_for_api_response( $data );
$response = array(
'response_code' => 200,
'error' => '',
'data' => $data,
);
return new WP_REST_Response( $response, 200 );
}
/**
* Get one item from the collection
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
$params = $request->get_params();
$recursive = ( !empty( $params['recursive'] ) ) ? $params['recursive'] : 0;
$data = $this->get( $params['id'], $recursive );
$data = $this->prepare_item_for_api_response( $data );
$response = array(
'response_code' => 200,
'error' => '',
'data' => $data,
);
return new WP_REST_Response( $response, 200 );
}
public function prepare_item_for_api_response( $item, $recursive=0 ) {
$keys_to_redact = array();
foreach ($item as $key => $value) {
if ( empty( $this->schema[$key]['required_capability'] ) ) {
continue;
}
if ( current_user_can( $this->schema[$key]['required_capability'] ) ) {
continue;
}
$keys_to_redact[$key] = $key;
}
$item = array_diff_key( $item, $keys_to_redact );
return $item;
}
public function prepare_collection_for_api_response( $items, $recursive=0 ) {
$prepared_items = array();
foreach ($items as $key => $item) {
$prepared_items[$key] = $this->prepare_item_for_api_response( $item );
}
return $prepared_items;
}
/**
* Create one item from the collection
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Request
*/
public function create_item( $request ) {
$params = $request->get_params();
$insert_id = $this->insert( $params );
if ( empty( $insert_id ) ) {
$response = array(
'response_code' => '500',
'error' => 'Not created',
'data' => array(),
);
} elseif( is_wp_error( $insert_id ) ) {
$response = array(
'response_code' => '500',
'error' => true,
'data' => $insert_id,
);
} elseif( is_array( $insert_id ) && ! empty( $insert_id['error']['code'] ) ) {
$response = array(
'response_code' => $insert_id['error']['code'],
'error' => $insert_id['error']['message'],
'data' => $insert_id['error']['data'],
);
} else {
$response = array(
'response_code' => 200,
'error' => '',
'data' => $this->get( $insert_id ),
);
}
return new WP_REST_Response( $response, 200 );
}
/**
* Update one item from the collection
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Request
*/
public function update_item( $request ) {
$item_id = $request['id'];
$params = $request->get_params();
// To remove afte testing (this should happen in the db abstraction level)
// $schema = $this->get_schema();
// $default_item = $this->get_field_defaults();
// if ( isset( $default_item[$this->primary_key] ) ) {
// unset( $default_item[$this->primary_key] );
// }
// $updated_item = array();
// foreach ($default_item as $key => $value) {
// if ( isset( $params[$key] ) ) {
// $updated_item[$key] = $params[$key];
// }
// }
// if ( !empty( $schema['date_modified'] ) && empty( $updated_item['date_modified'] ) ) {
// $updated_item['date_modified'] = date('Y-m-d H:i:s' );
// }
$response = $this->update( $item_id, $params );
// Note that $this->update will not update the db if an error key is detected
// It just returns the data back to frontend
if ( ! empty( $response['error'] ) ) {
$error = $response['error'];
unset($response['error']);
$response = array(
'response_code' => 500,
'error' => $error,
'data' => $response,
);
} else {
$response = array(
'response_code' => 200,
'error' => '',
'data' => $this->get( $item_id ),
);
}
return new WP_REST_Response( $response, 200 );
}
/**
* Update one item from the collection
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Request
*/
public function update_items( $request ) {
$item_id = $request['id'];
$params = $request->get_params();
if ( empty( $params['items']['0']['id'] ) ) {
return array(
'response_code' => 500,
'error' => 'Please specify data: { items: [{ id: 1, title: ...}, {id: 2, title: ...} ...]',
'data' => '',
);
}
$updated = array();
foreach ($params['items'] as $key => $item) {
$item_id = $item['id'];
unset( $item['id'] );
$this->update( $item_id, $item );
}
// Return all appointment types
$schema = $this->get_schema();
$data = $this->query( $params );
$response = array(
'response_code' => 200,
'error' => '',
'data' => $data,
);
return new WP_REST_Response( $response, 200 );
}
/**
* Delete one item from the collection
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Request
*/
public function delete_item( $request ) {
if ( empty( $request['id'] ) && $request['id'] !== 0 ) {
return false;
}
$response = array(
'response_code' => 200,
'error' => '',
'data' => $this->delete( $request['id'] ),
);
return new WP_REST_Response( $response, 200 );
}
public static function logged_in_permissions_check( $request ) {
if ( is_user_logged_in() ) {
return true;
}
return false;
}
public static function nonce_permissions_check( $request ) {
if ( empty( $request->get_headers()['x_wp_nonce']['0'] ) ) {
return false;
}
$nonce = $request->get_headers()['x_wp_nonce']['0'];
$is_valid = wp_verify_nonce( $nonce, 'wp_rest' );
if ( $is_valid ) {
return $is_valid;
}
if ( empty( $request->get_headers()['x_public_nonce']['0'] ) ) {
return false;
}
$nonce = $request->get_headers()['x_public_nonce']['0'];
$is_valid = self::verify_nonce( $nonce, 'wp_rest' );
return $is_valid;
}
/**
* Check if a given request has access to get items
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function get_items_permissions_check( $request ) {
if ( current_user_can( 'ssa_manage_site_settings' ) ) {
return true;
}
return false;
}
/**
* Check if a given request has access to get a specific item
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function get_item_permissions_check( $request ) {
if ( current_user_can( 'ssa_manage_site_settings' ) ) {
return true;
}
return false;
}
/**
* Check if a given request has access to create items
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function create_item_permissions_check( $request ) {
if ( current_user_can( 'ssa_manage_site_settings' ) ) {
return true;
}
return false;
}
/**
* Check if a given request has access to update a specific item
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function update_item_permissions_check( $request ) {
return $this->create_item_permissions_check( $request );
}
/**
* Check if a given request has access to delete a specific item
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|bool
*/
public function delete_item_permissions_check( $request ) {
return $this->create_item_permissions_check( $request );
}
/**
* Get the query params for collections
*
* @return array
*/
public function get_collection_params() {
return array(
'page' => array(
'description' => 'Current page of the collection.',
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
),
'per_page' => array(
'description' => 'Maximum number of items to be returned in result set.',
'type' => 'integer',
'default' => 10,
'sanitize_callback' => 'absint',
),
'search' => array(
'description' => 'Limit results to those matching a string.',
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
),
);
}
public static function create_nonce( $action = -1, $user_id = false ) {
if ( $user_id !== false ) {
if ( empty( $user_id ) ) {
$user_id = get_current_user_id();
}
}
return substr( wp_hash( $action . '|' . $user_id, 'nonce' ), -12, 10 );
}
public static function verify_nonce( $nonce, $action = -1, $user_id = false ) {
$nonce = (string) $nonce;
if ( empty( $nonce ) ) {
return false;
}
$expected = self::create_nonce( $action, $user_id );
$token = wp_get_session_token();
$i = wp_nonce_tick();
if ( hash_equals( $expected, $nonce ) ) {
return 1;
}
return false;
}
}