-
Notifications
You must be signed in to change notification settings - Fork 31
REST API: Add source
parameter for types endpoint
#128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from 32 commits
6453b6c
dda11f7
ff4c6b0
5e704c1
9f5576e
4c27770
f61d429
bad53df
1e128a3
9b29cdb
a4fcb14
4386792
a3b901a
bc62377
1b30485
4938c71
08c005c
53f3531
1ccf496
cebf109
978e331
2b76395
7382fcf
b75c136
2a2dfe1
70133ce
2263520
f5b72e5
cbcc576
c0b5198
3dc5034
602ffa4
bbd12f5
49c2460
28d571e
a6671a7
17003e9
525b240
221cec4
e5588b7
9003531
8f37ea8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,19 +14,174 @@ | |
/** | ||
* Class SCF_Rest_Types_Endpoint | ||
* | ||
* Extends the /wp/v2/types endpoint to include SCF fields. | ||
* Extends the /wp/v2/types endpoint to include SCF fields and source filtering. | ||
* | ||
* @since SCF 6.5.0 | ||
*/ | ||
class SCF_Rest_Types_Endpoint { | ||
|
||
/** | ||
* Cached post types for the current request | ||
* | ||
* @var array|null | ||
*/ | ||
private $cached_post_types = null; | ||
|
||
/** | ||
* Initialize the class. | ||
* | ||
* @since SCF 6.5.0 | ||
*/ | ||
public function __construct() { | ||
add_action( 'rest_api_init', array( $this, 'register_extra_fields' ) ); | ||
add_action( 'rest_api_init', array( $this, 'register_parameters' ) ); | ||
|
||
// Add filter to process REST API requests by route | ||
add_filter( 'rest_request_before_callbacks', array( $this, 'filter_types_request' ), 10, 3 ); | ||
|
||
// Add filter to process each post type individually | ||
add_filter( 'rest_prepare_post_type', array( $this, 'filter_post_type' ), 10, 3 ); | ||
|
||
// Clean up null entries from the response | ||
add_filter( 'rest_pre_echo_response', array( $this, 'clean_types_response' ), 10, 3 ); | ||
} | ||
|
||
/** | ||
* Filter post types requests (both collection and individual) | ||
* | ||
* @since SCF 6.5.0 | ||
* | ||
* @param mixed $response The current response, either response or null. | ||
* @param array $handler The handler for the route. | ||
* @param WP_REST_Request $request The request object. | ||
* @return mixed The response or null. | ||
*/ | ||
public function filter_types_request( $response, $handler, $request ) { | ||
// Check if this is a types endpoint request | ||
$route = $request->get_route(); | ||
$is_collection = '/wp/v2/types' === $route; | ||
$is_single_type = preg_match( '#^/wp/v2/types/([^/]+)$#', $route, $matches ); | ||
|
||
if ( ! $is_collection && ! $is_single_type ) { | ||
return $response; | ||
} | ||
|
||
// Get the source parameter | ||
$source = $request->get_param( 'source' ); | ||
|
||
// Only proceed if source parameter is provided and valid | ||
if ( ! $source || ! in_array( $source, array( 'core', 'scf', 'other' ), true ) ) { | ||
return $response; | ||
} | ||
|
||
// Get post types, calculating once and reusing for the entire request | ||
if ( null === $this->cached_post_types ) { | ||
$this->cached_post_types = $this->get_source_post_types( $source ); | ||
} | ||
$source_post_types = $this->cached_post_types; | ||
Comment on lines
+78
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In practice, the way the REST API works is that one request corresponds to one fresh execution of the WP stack, including running through Is there any scenario (e.g. request batching or sequential $source_post_types = $this->cached_post_types[ $source ]; … but ideally its lifetime should be that of a request. No? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right! Instead of source-indexing it, which was something I considered from the start and wanted to avoid, I've opted for a cleaner solution in 555217b: clear the cache at the end of each request, so that it is truly stateless. |
||
|
||
// For single post type requests, check if it matches the source | ||
if ( $is_single_type && isset( $matches[1] ) ) { | ||
$requested_type = $matches[1]; | ||
|
||
// If the requested type doesn't match the source, return 404 | ||
if ( ! in_array( $requested_type, $source_post_types, true ) ) { | ||
return new WP_Error( | ||
'rest_post_type_invalid', | ||
__( 'Invalid post type.', 'secure-custom-fields' ), | ||
array( 'status' => 404 ) | ||
); | ||
} | ||
} | ||
// For collection requests, we don't need to add any filter here | ||
// as clean_types_response will handle removing null values from the response | ||
// and filter_post_type will handle individual filtering | ||
priethor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Filter individual post type in the response. | ||
* | ||
* @since SCF 6.5.0 | ||
* | ||
* @param WP_REST_Response $response The response object. | ||
* @param WP_Post_Type $post_type The post type object. | ||
* @param WP_REST_Request $request The request object. | ||
* @return WP_REST_Response|null The filtered response or null to filter it out. | ||
*/ | ||
public function filter_post_type( $response, $post_type, $request ) { | ||
$source = $request->get_param( 'source' ); | ||
|
||
// Only apply filtering if source parameter is provided and valid | ||
if ( ! $source || ! in_array( $source, array( 'core', 'scf', 'other' ), true ) ) { | ||
return $response; | ||
} | ||
|
||
if ( null === $this->cached_post_types ) { | ||
$this->cached_post_types = $this->get_source_post_types( $source ); | ||
} | ||
$source_post_types = $this->cached_post_types; | ||
|
||
if ( ! in_array( $post_type->name, $source_post_types, true ) ) { | ||
return null; | ||
} | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Get an array of post types for each source. | ||
* | ||
* @since SCF 6.5.0 | ||
* | ||
* @param string $source The source to get post types for. | ||
* @return array An array of post type names for the specified source. | ||
*/ | ||
private function get_source_post_types( $source ) { | ||
|
||
$core_types = array(); | ||
$scf_types = array(); | ||
|
||
if ( 'core' === $source || 'other' === $source ) { | ||
$all_post_types = get_post_types( array( '_builtin' => true ), 'objects' ); | ||
foreach ( $all_post_types as $post_type ) { | ||
$core_types[] = $post_type->name; | ||
} | ||
} | ||
|
||
if ( 'scf' === $source || 'other' === $source ) { | ||
$scf_types = array(); | ||
priethor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
// Get SCF-managed post types | ||
if ( function_exists( 'acf_get_internal_post_type_posts' ) ) { | ||
$scf_managed_post_types = acf_get_internal_post_type_posts( 'acf-post-type' ); | ||
foreach ( $scf_managed_post_types as $scf_post_type ) { | ||
if ( isset( $scf_post_type['post_type'] ) ) { | ||
$scf_types[] = $scf_post_type['post_type']; | ||
} | ||
} | ||
} | ||
} | ||
|
||
switch ( $source ) { | ||
case 'core': | ||
$result = $core_types; | ||
break; | ||
case 'scf': | ||
$result = $scf_types; | ||
break; | ||
case 'other': | ||
$result = array_diff( | ||
array_keys( get_post_types( array(), 'objects' ) ), | ||
array_merge( $core_types, $scf_types ) | ||
); | ||
break; | ||
default: | ||
$result = array(); | ||
} | ||
|
||
return $result; | ||
priethor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
|
@@ -40,6 +195,7 @@ public function register_extra_fields() { | |
if ( ! (bool) get_option( 'scf_beta_feature_editor_sidebar_enabled', false ) ) { | ||
return; | ||
} | ||
|
||
register_rest_field( | ||
'type', | ||
'scf_field_groups', | ||
|
@@ -123,4 +279,116 @@ private function get_field_schema() { | |
'context' => array( 'view', 'edit', 'embed' ), | ||
); | ||
} | ||
|
||
/** | ||
* Register the source parameter for the post types endpoint. | ||
* | ||
* @since SCF 6.5.0 | ||
*/ | ||
public function register_parameters() { | ||
if ( ! acf_get_setting( 'rest_api_enabled' ) ) { | ||
return; | ||
} | ||
|
||
// Register the query parameter with the REST API | ||
add_filter( 'rest_type_collection_params', array( $this, 'add_collection_params' ) ); | ||
add_filter( 'rest_types_collection_params', array( $this, 'add_collection_params' ) ); | ||
|
||
// Direct registration for OpenAPI documentation | ||
add_filter( 'rest_endpoints', array( $this, 'add_parameter_to_endpoints' ) ); | ||
} | ||
|
||
/** | ||
* Get the source parameter definition | ||
* | ||
* @since SCF 6.5.0 | ||
* | ||
* @param bool $include_validation Whether to include validation callbacks. | ||
* @return array Parameter definition | ||
*/ | ||
private function get_source_param_definition( $include_validation = false ) { | ||
$param = array( | ||
'description' => __( 'Filter post types by their source.', 'secure-custom-fields' ), | ||
'type' => 'string', | ||
'enum' => array( 'core', 'scf', 'other' ), | ||
'required' => false, | ||
); | ||
|
||
// Not needed for OpenAPI documentation | ||
if ( $include_validation ) { | ||
$param['validate_callback'] = 'rest_validate_request_arg'; | ||
$param['sanitize_callback'] = 'sanitize_text_field'; | ||
$param['default'] = null; | ||
$param['in'] = 'query'; | ||
} | ||
|
||
return $param; | ||
} | ||
|
||
/** | ||
* Add source parameter directly to the endpoints for proper documentation | ||
* | ||
* @since SCF 6.5.0 | ||
* | ||
* @param array $endpoints The REST API endpoints. | ||
* @return array Modified endpoints | ||
*/ | ||
public function add_parameter_to_endpoints( $endpoints ) { | ||
$source_param = $this->get_source_param_definition(); | ||
$endpoints_to_modify = array( '/wp/v2/types', '/wp/v2/types/(?P<type>[\w-]+)' ); | ||
|
||
foreach ( $endpoints_to_modify as $route ) { | ||
if ( isset( $endpoints[ $route ] ) ) { | ||
foreach ( $endpoints[ $route ] as &$endpoint ) { | ||
if ( isset( $endpoint['args'] ) ) { | ||
$endpoint['args']['source'] = $source_param; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return $endpoints; | ||
} | ||
|
||
/** | ||
* Add source parameter to the collection parameters for the types endpoint. | ||
* | ||
* @since SCF 6.5.0 | ||
* | ||
* @param array $query_params JSON Schema-formatted collection parameters. | ||
* @return array Modified collection parameters. | ||
*/ | ||
public function add_collection_params( $query_params ) { | ||
$query_params['source'] = $this->get_source_param_definition( true ); | ||
return $query_params; | ||
} | ||
|
||
/** | ||
* Clean up null entries from the response | ||
* | ||
* @since SCF 6.5.0 | ||
* | ||
* @param array $response The response data. | ||
* @param WP_REST_Server $server The REST server instance. | ||
* @param WP_REST_Request $request The original request. | ||
* @return array The filtered response data. | ||
*/ | ||
public function clean_types_response( $response, $server, $request ) { | ||
if ( strpos( $request->get_route(), '/wp/v2/types' ) !== 0 ) { | ||
return $response; | ||
} | ||
|
||
// Only process collection responses (not single post type responses) | ||
// Single post type responses have a 'slug' property, collections don't | ||
if ( is_array( $response ) && ! isset( $response['slug'] ) ) { | ||
$response = array_filter( | ||
$response, | ||
function ( $entry ) { | ||
return null !== $entry; | ||
} | ||
); | ||
} | ||
|
||
return $response; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.