\WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\\get_post_types', 'permission_callback' => __NAMESPACE__ . '\\can_read_catalog', ]); register_rest_route('filter-query-block-pro/v4', '/catalog', [ 'methods' => \WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\\get_catalog', 'permission_callback' => __NAMESPACE__ . '\\can_read_catalog', 'args' => [ 'post_types' => [ 'required' => false, 'type' => 'array', 'items' => ['type' => 'string'], 'sanitize_callback' => __NAMESPACE__ . '\\sanitize_post_types', ], ], ]); register_rest_route('filter-query-block-pro/v4', '/catalog/tracking', [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => __NAMESPACE__ . '\\update_tracking', 'permission_callback' => __NAMESPACE__ . '\\can_manage_catalog', 'args' => [ 'post_types' => [ 'required' => true, 'type' => 'array', 'items' => ['type' => 'string'], 'sanitize_callback' => __NAMESPACE__ . '\\sanitize_post_types', ], 'rebuild' => [ 'required' => false, 'type' => 'boolean', 'default' => true, ], ], ]); register_rest_route('filter-query-block-pro/v4', '/catalog/prepare', [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => __NAMESPACE__ . '\\prepare_post_types', 'permission_callback' => __NAMESPACE__ . '\\can_read_catalog', 'args' => ['post_types' => [ 'required' => true, 'type' => 'array', 'minItems' => 1, 'maxItems' => 20, 'items' => ['type' => 'string'], 'sanitize_callback' => __NAMESPACE__ . '\\sanitize_post_types', ]], ]); register_rest_route('filter-query-block-pro/v4', '/catalog/rebuild', [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => __NAMESPACE__ . '\\rebuild_catalog', 'permission_callback' => __NAMESPACE__ . '\\can_manage_catalog', ]); } /** * Editors may consume catalog options in block controls. * * @return bool */ function can_read_catalog() { return current_user_can('edit_posts'); } /** * Administrators may change catalog configuration. * * @return bool */ function can_manage_catalog() { return current_user_can('manage_options'); } /** * Sanitize a REST array of post-type slugs. * * @param mixed $value Request value. * @return string[] */ function sanitize_post_types($value) { return array_values(array_unique(array_filter(array_map('sanitize_key', (array) $value)))); } /** * Return live public post-type options. * * @return \WP_REST_Response */ function get_post_types($request = null) { return \Fqbpro\Plugin\Compatibility\Languages\with_language(\Fqbpro\Plugin\Compatibility\Languages\request_language($request), static fn() => new \WP_REST_Response(['post_types' => Service\get_post_type_options()], 200)); } /** * Return the persisted catalog. * * @param \WP_REST_Request $request REST request. * @return \WP_REST_Response */ function get_catalog($request) { return \Fqbpro\Plugin\Compatibility\Languages\with_language(\Fqbpro\Plugin\Compatibility\Languages\request_language($request), static fn() => get_catalog_for_language($request)); } function get_catalog_for_language($request) { $catalog = Service\get_catalog($request->get_param('post_types') ?: []); foreach (array_intersect($catalog['tracked_post_types'] ?? [], $request->get_param('post_types') ?: []) as $type) { if (empty($catalog['post_types'][$type])) { $data = Service\get_post_type_catalog($type); if (!is_wp_error($data)) $catalog['post_types'][$type] = $data; } } return new \WP_REST_Response(prepare_catalog_response($catalog), 200); } /** Apply offered-source policy and response metadata without persisting either. */ function prepare_catalog_response($catalog) { $catalog = array_merge(Exclusions\filter_catalog($catalog), fqbp_edition_info()); $catalog['catalogExclusions'] = Exclusions\get_exclusions_settings(); foreach (array_keys($catalog['post_types'] ?? []) as $post_type) { $catalog['post_types'][$post_type] = array_merge($catalog['post_types'][$post_type], fqbp_edition_info()); if (!defined('FQBPRO_EDITION') || FQBPRO_EDITION !== 'free') { $catalog['post_types'][$post_type]['postTypeChoices'] = FilterHeader\get_post_type_source($post_type); $catalog['post_types'][$post_type]['authors'] = FilterHeader\get_author_source($post_type); } $catalog['post_types'][$post_type]['nativeCapabilities'] = FilterHeader\native_source_capabilities( Exclusions\filter_sources(FilterHeader\get_catalog_sources($post_type)) ); } return $catalog; } /** * Update tracked types and optionally rebuild. * * @param \WP_REST_Request $request REST request. * @return \WP_REST_Response */ function update_tracking($request) { $tracked = Service\update_tracking($request->get_param('post_types')); if (is_wp_error($tracked)) { return $tracked; } $data = ['tracked_post_types' => $tracked, 'catalogExclusions' => Exclusions\get_exclusions_settings()]; if ($request->get_param('rebuild')) { $catalog = Service\rebuild(); if (is_wp_error($catalog)) { return $catalog; } $data['catalog'] = prepare_catalog_response($catalog); } return new \WP_REST_Response($data, 200); } /** * Rebuild the persisted catalog. * * @return \WP_REST_Response */ function rebuild_catalog() { $catalog = Service\rebuild(); return is_wp_error($catalog) ? $catalog : new \WP_REST_Response(prepare_catalog_response($catalog), 200); } /** Add editor-selected public types without replacing administrator tracking. */ function prepare_post_types($request) { $types = $request->get_param('post_types'); $available = array_column(Service\get_post_type_options(), 'slug'); foreach ($types as $type) { $object = get_post_type_object($type); if (!in_array($type, $available, true) || !$object || !current_user_can($object->cap->edit_posts)) { return new \WP_Error('fqbpro_post_type_unavailable', __('You cannot select this post type.', 'accomplice-query-loop-filters'), ['status' => 403]); } } $result = Service\update_tracking($types, true); if (is_wp_error($result)) return $result; return get_catalog($request); }