Skip to content

Commit 60cad18

Browse files
REST API: Add source parameter for types endpoint (#128)
Co-authored-by: Carlos Bravo <[email protected]>
1 parent e6ccffa commit 60cad18

File tree

6 files changed

+963
-3
lines changed

6 files changed

+963
-3
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,8 @@ assets/build/**/*.php
5959
/playwright/.cache/
6060
/artifacts/
6161

62+
# Claude settings
63+
**/.claude/settings.local.json
64+
6265
# Release folder
63-
release/
66+
release/

includes/rest-api.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* REST API
44
*
55
* @package Secure Custom Fields
6-
* @since ACF 6.4.0
6+
* @since SCF 6.5.0
77
*/
88

99
// Exit if accessed directly.

includes/rest-api/class-acf-rest-types-endpoint.php

Lines changed: 234 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
/**
1515
* Class SCF_Rest_Types_Endpoint
1616
*
17-
* Extends the /wp/v2/types endpoint to include SCF fields.
17+
* Extends the /wp/v2/types endpoint to include SCF fields and source filtering.
1818
*
1919
* @since SCF 6.5.0
2020
*/
@@ -27,6 +27,133 @@ class SCF_Rest_Types_Endpoint {
2727
*/
2828
public function __construct() {
2929
add_action( 'rest_api_init', array( $this, 'register_extra_fields' ) );
30+
add_action( 'rest_api_init', array( $this, 'register_parameters' ) );
31+
32+
// Add filter to process REST API requests by route
33+
add_filter( 'rest_request_before_callbacks', array( $this, 'filter_types_request' ), 10, 3 );
34+
35+
// Add filter to process each post type individually
36+
add_filter( 'rest_prepare_post_type', array( $this, 'filter_post_type' ), 10, 3 );
37+
38+
// Clean up null entries from the response
39+
add_filter( 'rest_pre_echo_response', array( $this, 'clean_types_response' ), 10, 3 );
40+
}
41+
42+
/**
43+
* Filter post types requests, fires for both collection and individual requests.
44+
* We only want to handle individual requets to ensure the post type requested matches the source.
45+
*
46+
* @since SCF 6.5.0
47+
*
48+
* @param mixed $response The current response, either response or null.
49+
* @param array $handler The handler for the route.
50+
* @param WP_REST_Request $request The request object.
51+
* @return mixed The response or null.
52+
*/
53+
public function filter_types_request( $response, $handler, $request ) {
54+
// We only want to handle individual requests
55+
$route = $request->get_route();
56+
if ( ! preg_match( '#^/wp/v2/types/([^/]+)$#', $route, $matches ) ) {
57+
return $response;
58+
}
59+
60+
$source = $request->get_param( 'source' );
61+
62+
// Only proceed if source parameter is provided and valid
63+
if ( ! $source || ! in_array( $source, array( 'core', 'scf', 'other' ), true ) ) {
64+
return $response;
65+
}
66+
67+
$source_post_types = $this->get_source_post_types( $source );
68+
69+
// Check if the requested type matches the source
70+
$requested_type = $matches[1];
71+
if ( ! in_array( $requested_type, $source_post_types, true ) ) {
72+
return new WP_Error(
73+
'rest_post_type_invalid',
74+
__( 'Invalid post type.', 'secure-custom-fields' ),
75+
array( 'status' => 404 )
76+
);
77+
}
78+
79+
return $response;
80+
}
81+
82+
/**
83+
* Filter individual post type in the response.
84+
*
85+
* @since SCF 6.5.0
86+
*
87+
* @param WP_REST_Response $response The response object.
88+
* @param WP_Post_Type $post_type The post type object.
89+
* @param WP_REST_Request $request The request object.
90+
* @return WP_REST_Response|null The filtered response or null to filter it out.
91+
*/
92+
public function filter_post_type( $response, $post_type, $request ) {
93+
$source = $request->get_param( 'source' );
94+
95+
// Only apply filtering if source parameter is provided and valid
96+
if ( ! $source || ! in_array( $source, array( 'core', 'scf', 'other' ), true ) ) {
97+
return $response;
98+
}
99+
100+
$source_post_types = $this->get_source_post_types( $source );
101+
102+
if ( ! in_array( $post_type->name, $source_post_types, true ) ) {
103+
return null;
104+
}
105+
106+
return $response;
107+
}
108+
109+
/**
110+
* Get an array of post types for each source.
111+
*
112+
* @since SCF 6.5.0
113+
*
114+
* @param string $source The source to get post types for.
115+
* @return array An array of post type names for the specified source.
116+
*/
117+
private function get_source_post_types( $source ) {
118+
119+
$core_types = array();
120+
$scf_types = array();
121+
122+
if ( 'core' === $source || 'other' === $source ) {
123+
$all_post_types = get_post_types( array( '_builtin' => true ), 'objects' );
124+
foreach ( $all_post_types as $post_type ) {
125+
$core_types[] = $post_type->name;
126+
}
127+
}
128+
129+
if ( 'scf' === $source || 'other' === $source ) {
130+
// Get SCF-managed post types
131+
if ( function_exists( 'acf_get_internal_post_type_posts' ) ) {
132+
$scf_managed_post_types = acf_get_internal_post_type_posts( 'acf-post-type' );
133+
foreach ( $scf_managed_post_types as $scf_post_type ) {
134+
$scf_types[] = $scf_post_type['post_type'];
135+
}
136+
}
137+
}
138+
139+
switch ( $source ) {
140+
case 'core':
141+
$result = $core_types;
142+
break;
143+
case 'scf':
144+
$result = $scf_types;
145+
break;
146+
case 'other':
147+
$result = array_diff(
148+
array_keys( get_post_types( array(), 'objects' ) ),
149+
array_merge( $core_types, $scf_types )
150+
);
151+
break;
152+
default:
153+
$result = array();
154+
}
155+
156+
return $result;
30157
}
31158

32159
/**
@@ -40,6 +167,7 @@ public function register_extra_fields() {
40167
if ( ! (bool) get_option( 'scf_beta_feature_editor_sidebar_enabled', false ) ) {
41168
return;
42169
}
170+
43171
register_rest_field(
44172
'type',
45173
'scf_field_groups',
@@ -123,4 +251,109 @@ private function get_field_schema() {
123251
'context' => array( 'view', 'edit', 'embed' ),
124252
);
125253
}
254+
255+
/**
256+
* Register the source parameter for the post types endpoint.
257+
*
258+
* @since SCF 6.5.0
259+
*/
260+
public function register_parameters() {
261+
if ( ! acf_get_setting( 'rest_api_enabled' ) ) {
262+
return;
263+
}
264+
265+
// Register the query parameter with the REST API
266+
add_filter( 'rest_type_collection_params', array( $this, 'add_collection_params' ) );
267+
add_filter( 'rest_types_collection_params', array( $this, 'add_collection_params' ) );
268+
269+
// Direct registration for OpenAPI documentation
270+
add_filter( 'rest_endpoints', array( $this, 'add_parameter_to_endpoints' ) );
271+
}
272+
273+
/**
274+
* Get the source parameter definition
275+
*
276+
* @since SCF 6.5.0
277+
*
278+
* @return array Parameter definition
279+
*/
280+
private function get_source_param_definition() {
281+
return array(
282+
'description' => __( 'Filter post types by their source.', 'secure-custom-fields' ),
283+
'type' => 'string',
284+
'enum' => array( 'core', 'scf', 'other' ),
285+
'required' => false,
286+
'validate_callback' => 'rest_validate_request_arg',
287+
'sanitize_callback' => 'sanitize_text_field',
288+
'default' => null,
289+
'in' => 'query',
290+
);
291+
}
292+
293+
/**
294+
* Add source parameter directly to the endpoints for proper documentation
295+
*
296+
* @since SCF 6.5.0
297+
*
298+
* @param array $endpoints The REST API endpoints.
299+
* @return array Modified endpoints
300+
*/
301+
public function add_parameter_to_endpoints( $endpoints ) {
302+
$source_param = $this->get_source_param_definition();
303+
$endpoints_to_modify = array( '/wp/v2/types', '/wp/v2/types/(?P<type>[\w-]+)' );
304+
305+
foreach ( $endpoints_to_modify as $route ) {
306+
if ( isset( $endpoints[ $route ] ) ) {
307+
foreach ( $endpoints[ $route ] as &$endpoint ) {
308+
if ( isset( $endpoint['args'] ) ) {
309+
$endpoint['args']['source'] = $source_param;
310+
}
311+
}
312+
}
313+
}
314+
315+
return $endpoints;
316+
}
317+
318+
/**
319+
* Add source parameter to the collection parameters for the types endpoint.
320+
*
321+
* @since SCF 6.5.0
322+
*
323+
* @param array $query_params JSON Schema-formatted collection parameters.
324+
* @return array Modified collection parameters.
325+
*/
326+
public function add_collection_params( $query_params ) {
327+
$query_params['source'] = $this->get_source_param_definition();
328+
return $query_params;
329+
}
330+
331+
/**
332+
* Clean up null entries from the response
333+
*
334+
* @since SCF 6.5.0
335+
*
336+
* @param array $response The response data.
337+
* @param WP_REST_Server $server The REST server instance.
338+
* @param WP_REST_Request $request The original request.
339+
* @return array The filtered response data.
340+
*/
341+
public function clean_types_response( $response, $server, $request ) {
342+
if ( ! preg_match( '#^/wp/v2/types(?:/|$)#', $request->get_route() ) ) {
343+
return $response;
344+
}
345+
346+
// Only process collection responses (not single post type responses)
347+
// Single post type responses have a 'slug' property, collections don't
348+
if ( is_array( $response ) && ! isset( $response['slug'] ) ) {
349+
$response = array_filter(
350+
$response,
351+
function ( $entry ) {
352+
return null !== $entry;
353+
}
354+
);
355+
}
356+
357+
return $response;
358+
}
126359
}

0 commit comments

Comments
 (0)