&sub=&search= * -> { subs: Option[], values: Option[] } options for the conditions dialog: the `sub` select of a target * (post types / taxonomies) and its dependent value select (posts, terms, authors; searchable) * GET /templates/{id}/conditions -> { conditions, summary } * PUT /templates/{id}/conditions body { conditions: Rule[] } -> { conditions, summary } * * All routes require edit_theme_options. */ final class RestTemplates { public const NS = 'zadex/v1'; public const LIMIT = 20; public function register(): void { add_action('rest_api_init', [$this, 'routes']); } public function routes(): void { register_rest_route(self::NS, '/templates/options', [ 'methods' => 'GET', 'callback' => [$this, 'options'], 'permission_callback' => [$this, 'can_manage'], ]); register_rest_route(self::NS, '/templates/(?P\d+)/conditions', [ [ 'methods' => 'GET', 'callback' => [$this, 'get_conditions'], 'permission_callback' => [$this, 'can_manage'], ], [ 'methods' => 'PUT, POST', 'callback' => [$this, 'save_conditions'], 'permission_callback' => [$this, 'can_manage'], ], ]); } public function can_manage(): bool { return current_user_can(TemplatePost::CAPABILITY); } public function options(WP_REST_Request $request): WP_REST_Response { $target = sanitize_key((string) ($request['target'] ?? '')); $sub = sanitize_key((string) ($request['sub'] ?? '')); $search = sanitize_text_field((string) ($request['search'] ?? '')); return new WP_REST_Response(['subs' => self::subs($target), 'values' => self::values($target, $sub, $search)]); } /** Options of the `sub` select for a target. */ public static function subs(string $target): array { if (in_array($target, ['post_type', 'singular', 'specific', 'archive'], true)) { $out = []; foreach (get_post_types(['public' => true], 'objects') as $name => $type) { if ($name === 'attachment' && $target !== 'singular' && $target !== 'specific') { continue; } if ($target === 'archive' && $name !== 'post' && empty($type->has_archive)) { continue; } $out[] = ['value' => (string) $name, 'label' => (string) $type->labels->name]; } return $out; } if (in_array($target, ['taxonomy', 'term'], true)) { $out = []; foreach (get_taxonomies(['public' => true], 'objects') as $name => $tax) { if ($name === 'post_format') { continue; } $out[] = ['value' => (string) $name, 'label' => (string) $tax->labels->name]; } return $out; } return []; } /** Options of the value select (posts of a type, terms of a taxonomy, authors). */ public static function values(string $target, string $sub, string $search = ''): array { $out = []; if ($target === 'specific' && $sub !== '' && post_type_exists($sub)) { $posts = get_posts([ 'post_type' => $sub, 'post_status' => ['publish', 'private', 'draft', 'future'], 'numberposts' => self::LIMIT, 's' => $search, 'orderby' => $search !== '' ? 'relevance' : 'date', 'order' => 'DESC', ]); foreach ($posts as $post) { $out[] = ['value' => (int) $post->ID, 'label' => ($post->post_title !== '' ? $post->post_title : __('(no title)', 'zadex')) . ' (#' . (int) $post->ID . ')']; } } elseif ($target === 'term' && $sub !== '' && taxonomy_exists($sub)) { $terms = get_terms(['taxonomy' => $sub, 'hide_empty' => false, 'number' => self::LIMIT, 'search' => $search]); if (is_array($terms)) { foreach ($terms as $term) { $out[] = ['value' => (int) $term->term_id, 'label' => (string) $term->name]; } } } elseif ($target === 'author') { $users = get_users(['number' => self::LIMIT, 'search' => $search !== '' ? '*' . $search . '*' : '', 'fields' => ['ID', 'display_name'], 'capability' => ['edit_posts']]); foreach ($users as $user) { $out[] = ['value' => (int) $user->ID, 'label' => (string) $user->display_name]; } } elseif (!in_array($target, Conditions::TARGETS, true)) { // Targets added by integrations (WooCommerce: products, product categories / tags). $extra = apply_filters('zadex/theme/condition_values', [], $target, $sub, $search); $out = is_array($extra) ? array_values($extra) : []; } return $out; } public function get_conditions(WP_REST_Request $request) { $id = (int) $request['id']; if (!TemplatePost::is_template($id)) { return new WP_Error('zadex_not_template', __('Not a theme template.', 'zadex'), ['status' => 404]); } $rules = TemplatePost::conditions($id); return new WP_REST_Response(['conditions' => $rules, 'summary' => Conditions::summary($rules)]); } public function save_conditions(WP_REST_Request $request) { $id = (int) $request['id']; if (!TemplatePost::is_template($id)) { return new WP_Error('zadex_not_template', __('Not a theme template.', 'zadex'), ['status' => 404]); } $body = $request->get_json_params(); if (!is_array($body) || !isset($body['conditions']) || !is_array($body['conditions'])) { return new WP_Error('zadex_bad_request', __('Missing conditions.', 'zadex'), ['status' => 400]); } $rules = TemplatePost::set_conditions($id, $body['conditions']); Resolver::reset(); return new WP_REST_Response(['conditions' => $rules, 'summary' => Conditions::summary($rules)]); } }