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