1<?php
2/**
3 * Templates registry functions.
4 *
5 * @package WordPress
6 * @since 6.7.0
7 */
8
9/**
10 * Core class used for interacting with templates.
11 *
12 * @since 6.7.0
13 */
14final class WP_Block_Templates_Registry {
15 /**
16 * Registered templates, as `$name => $instance` pairs.
17 *
18 * @since 6.7.0
19 * @var WP_Block_Template[] $registered_block_templates Registered templates.
20 */
21 private $registered_templates = array();
22
23 /**
24 * Container for the main instance of the class.
25 *
26 * @since 6.7.0
27 * @var WP_Block_Templates_Registry|null
28 */
29 private static $instance = null;
30
31 /**
32 * Registers a template.
33 *
34 * @since 6.7.0
35 *
36 * @param string $template_name Template name including namespace.
37 * @param array $args Optional. Array of template arguments.
38 * @return WP_Block_Template|WP_Error The registered template on success, or WP_Error on failure.
39 */
40 public function register( $template_name, $args = array() ) {
41
42 $template = null;
43
44 $error_message = '';
45 $error_code = '';
46
47 if ( ! is_string( $template_name ) ) {
48 $error_message = __( 'Template names must be strings.' );
49 $error_code = 'template_name_no_string';
50 } elseif ( preg_match( '/[A-Z]+/', $template_name ) ) {
51 $error_message = __( 'Template names must not contain uppercase characters.' );
52 $error_code = 'template_name_no_uppercase';
53 } elseif ( ! preg_match( '/^[a-z0-9_\-]+\/\/[a-z0-9_\-]+$/', $template_name ) ) {
54 $error_message = __( 'Template names must contain a namespace prefix. Example: my-plugin//my-custom-template' );
55 $error_code = 'template_no_prefix';
56 } elseif ( $this->is_registered( $template_name ) ) {
57 /* translators: %s: Template name. */
58 $error_message = sprintf( __( 'Template "%s" is already registered.' ), $template_name );
59 $error_code = 'template_already_registered';
60 }
61
62 if ( $error_message ) {
63 _doing_it_wrong( __METHOD__, $error_message, '6.7.0' );
64
65 return new WP_Error( $error_code, $error_message );
66 }
67
68 if ( ! $template ) {
69 $theme_name = get_stylesheet();
70 list( $plugin, $slug ) = explode( '//', $template_name );
71 $default_template_types = get_default_block_template_types();
72
73 $template = new WP_Block_Template();
74 $template->id = $theme_name . '//' . $slug;
75 $template->theme = $theme_name;
76 $template->plugin = $plugin;
77 $template->author = null;
78 $template->content = isset( $args['content'] ) ? $args['content'] : '';
79 $template->source = 'plugin';
80 $template->slug = $slug;
81 $template->type = 'wp_template';
82 $template->title = isset( $args['title'] ) ? $args['title'] : $template_name;
83 $template->description = isset( $args['description'] ) ? $args['description'] : '';
84 $template->status = 'publish';
85 $template->origin = 'plugin';
86 $template->is_custom = ! isset( $default_template_types[ $template_name ] );
87 $template->post_types = isset( $args['post_types'] ) ? $args['post_types'] : array();
88 }
89
90 $this->registered_templates[ $template_name ] = $template;
91
92 return $template;
93 }
94
95 /**
96 * Retrieves all registered templates.
97 *
98 * @since 6.7.0
99 *
100 * @return WP_Block_Template[] Associative array of `$template_name => $template` pairs.
101 */
102 public function get_all_registered() {
103 return $this->registered_templates;
104 }
105
106 /**
107 * Retrieves a registered template by its name.
108 *
109 * @since 6.7.0
110 *
111 * @param string $template_name Template name including namespace.
112 * @return WP_Block_Template|null The registered template, or null if it is not registered.
113 */
114 public function get_registered( $template_name ) {
115 if ( ! $this->is_registered( $template_name ) ) {
116 return null;
117 }
118
119 return $this->registered_templates[ $template_name ];
120 }
121
122 /**
123 * Retrieves a registered template by its slug.
124 *
125 * @since 6.7.0
126 *
127 * @param string $template_slug Slug of the template.
128 * @return WP_Block_Template|null The registered template, or null if it is not registered.
129 */
130 public function get_by_slug( $template_slug ) {
131 $all_templates = $this->get_all_registered();
132
133 if ( ! $all_templates ) {
134 return null;
135 }
136
137 foreach ( $all_templates as $template ) {
138 if ( $template->slug === $template_slug ) {
139 return $template;
140 }
141 }
142
143 return null;
144 }
145
146 /**
147 * Retrieves registered templates matching a query.
148 *
149 * @since 6.7.0
150 *
151 * @param array $query {
152 * Arguments to retrieve templates. Optional, empty by default.
153 *
154 * @type string[] $slug__in List of slugs to include.
155 * @type string[] $slug__not_in List of slugs to skip.
156 * @type string $post_type Post type to get the templates for.
157 * }
158 * @return WP_Block_Template[] Associative array of `$template_name => $template` pairs.
159 */
160 public function get_by_query( $query = array() ) {
161 $all_templates = $this->get_all_registered();
162
163 if ( ! $all_templates ) {
164 return array();
165 }
166
167 $query = wp_parse_args(
168 $query,
169 array(
170 'slug__in' => array(),
171 'slug__not_in' => array(),
172 'post_type' => '',
173 )
174 );
175 $slugs_to_include = $query['slug__in'];
176 $slugs_to_skip = $query['slug__not_in'];
177 $post_type = $query['post_type'];
178
179 $matching_templates = array();
180 foreach ( $all_templates as $template_name => $template ) {
181 if ( $slugs_to_include && ! in_array( $template->slug, $slugs_to_include, true ) ) {
182 continue;
183 }
184
185 if ( $slugs_to_skip && in_array( $template->slug, $slugs_to_skip, true ) ) {
186 continue;
187 }
188
189 if ( $post_type && ! in_array( $post_type, $template->post_types, true ) ) {
190 continue;
191 }
192
193 $matching_templates[ $template_name ] = $template;
194 }
195
196 return $matching_templates;
197 }
198
199 /**
200 * Checks if a template is registered.
201 *
202 * @since 6.7.0
203 *
204 * @param string|null $template_name Template name.
205 * @return bool True if the template is registered, false otherwise.
206 */
207 public function is_registered( $template_name ) {
208 return isset( $template_name, $this->registered_templates[ $template_name ] );
209 }
210
211 /**
212 * Unregisters a template.
213 *
214 * @since 6.7.0
215 *
216 * @param string $template_name Template name including namespace.
217 * @return WP_Block_Template|WP_Error The unregistered template on success, or WP_Error on failure.
218 */
219 public function unregister( $template_name ) {
220 if ( ! $this->is_registered( $template_name ) ) {
221 /* translators: %s: Template name. */
222 $error_message = sprintf( __( 'Template "%s" is not registered.' ), $template_name );
223
224 _doing_it_wrong( __METHOD__, $error_message, '6.7.0' );
225
226 return new WP_Error( 'template_not_registered', $error_message );
227 }
228
229 $unregistered_template = $this->registered_templates[ $template_name ];
230 unset( $this->registered_templates[ $template_name ] );
231
232 return $unregistered_template;
233 }
234
235 /**
236 * Utility method to retrieve the main instance of the class.
237 *
238 * The instance will be created if it does not exist yet.
239 *
240 * @since 6.7.0
241 *
242 * @return WP_Block_Templates_Registry The main instance.
243 */
244 public static function get_instance() {
245 if ( null === self::$instance ) {
246 self::$instance = new self();
247 }
248
249 return self::$instance;
250 }
251}
252