PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 2.5.5
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v2.5.5
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / Editors / BlockEditor / TemplatesController.php

TemplatesController.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 2.5.5, at includes/Editors/BlockEditor/TemplatesController.php

380 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Editors\BlockEditor;
4 use WPDeveloper\BetterDocs\Utils\Base;
5 use WPDeveloper\BetterDocs\Utils\BlockTemplateUtils;
6
7 /**
8 * BlockTypesController class.
9 *
10 * @internal
11 */
12 class TemplatesController extends Base {
13
14 /**
15 * Holds the path for the directory where the block templates will be kept.
16 *
17 * @var string
18 */
19 private $templates_directory;
20
21 /**
22 * Constructor.
23 */
24 public function __construct() {
25 $this->templates_directory = BETTERDOCS_FSE_TEMPLATES_PATH . DIRECTORY_SEPARATOR;
26 $this->init();
27 }
28
29 /**
30 * Initialization method.
31 */
32 protected function init() {
33 add_filter( 'pre_get_block_template', [ $this, 'get_block_template_fallback' ], 10, 3 );
34 add_filter( 'pre_get_block_file_template', [ $this, 'get_block_file_template' ], 10, 3 );
35 add_filter( 'get_block_templates', [ $this, 'add_block_templates' ], 10, 3 );
36 add_filter( 'taxonomy_template_hierarchy', [ $this, 'add_doc_archive_to_eligible_for_fallback_templates' ], 10, 1 );
37 }
38
39 /**
40 * This function is used on the `pre_get_block_template` hook to return the fallback template from the db in case
41 * the template is eligible for it.
42 *
43 * @param \WP_Block_Template|null $template Block template object to short-circuit the default query,
44 * or null to allow WP to run its normal queries.
45 * @param string $id Template unique identifier (example: theme_slug//template_slug).
46 * @param string $template_type wp_template or wp_template_part.
47 *
48 * @return object|null
49 */
50 public function get_block_template_fallback( $template, $id, $template_type ) {
51 $template_name_parts = explode( '//', $id );
52 list( $theme, $slug ) = $template_name_parts;
53
54 if ( ! BlockTemplateUtils::template_is_eligible_for_docs_archive_fallback( $slug ) ) {
55 return null;
56 }
57
58 $wp_query_args = [
59 'post_name__in' => [ 'archive-docs', $slug ],
60 'post_type' => $template_type,
61 'post_status' => [ 'auto-draft', 'draft', 'publish', 'trash' ],
62 'no_found_rows' => true,
63 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
64 [
65 'taxonomy' => 'wp_theme',
66 'field' => 'name',
67 'terms' => $theme
68 ]
69 ]
70 ];
71 $template_query = new \WP_Query( $wp_query_args );
72 $posts = $template_query->posts;
73
74 // If we have more than one result from the query, it means that the current template is present in the db (has
75 // been customized by the user) and we should not return the `archive-docs` template.
76 if ( count( $posts ) > 1 ) {
77 return null;
78 }
79
80 if ( count( $posts ) > 0 ) {
81 $template = _build_block_template_result_from_post( $posts[0] );
82
83 if ( ! is_wp_error( $template ) ) {
84 $template->id = $theme . '//' . $slug;
85 $template->slug = $slug;
86 $template->title = BlockTemplateUtils::get_block_template_title( $slug );
87 $template->description = BlockTemplateUtils::get_block_template_description( $slug );
88 unset( $template->source );
89
90 return $template;
91 }
92 }
93
94 return $template;
95 }
96
97 /**
98 * Adds the `archive-docs` template to the `taxonomy-doc_category`, `taxonomy-doc_tag`
99 * templates to be able to fall back to it.
100 *
101 * @param array $template_hierarchy A list of template candidates, in descending order of priority.
102 */
103 public function add_doc_archive_to_eligible_for_fallback_templates( $template_hierarchy ) {
104 $template_slugs = array_map(
105 '_strip_template_file_suffix',
106 $template_hierarchy
107 );
108
109 $templates_eligible_for_fallback = array_filter(
110 $template_slugs,
111 [ BlockTemplateUtils::class, 'template_is_eligible_for_docs_archive_fallback' ]
112 );
113
114 if ( count( $templates_eligible_for_fallback ) > 0 ) {
115 $template_hierarchy[] = 'archive-docs';
116 }
117
118 return $template_hierarchy;
119 }
120
121 /**
122 * This function checks if there's a block template file in `betterdocs/includes/blocks/templates/`
123 * to return to pre_get_posts short-circuiting the query in Gutenberg.
124 *
125 * @param \WP_Block_Template|null $template Return a block template object to short-circuit the default query,
126 * or null to allow WP to run its normal queries.
127 * @param string $id Template unique identifier (example: theme_slug//template_slug).
128 * @param string $template_type wp_template or wp_template_part.
129 *
130 * @return mixed|\WP_Block_Template|\WP_Error
131 */
132 public function get_block_file_template( $template, $id, $template_type ) {
133 $template_name_parts = explode( '//', $id );
134
135 if ( count( $template_name_parts ) < 2 ) {
136 return $template;
137 }
138
139 list( $template_id, $template_slug ) = $template_name_parts;
140
141 // If we are not dealing with a BetterDocs template let's return early and let it continue through the process.
142 if ( BlockTemplateUtils::PLUGIN_SLUG !== $template_id ) {
143 return $template;
144 }
145
146 // If we don't have a template let Gutenberg do its thing.
147 if ( ! $this->block_template_is_available( $template_slug, $template_type ) ) {
148 return $template;
149 }
150
151 $directory = $this->get_templates_directory( $template_type );
152 $template_file_path = $directory . '/' . $template_slug . '.html';
153 $template_object = BlockTemplateUtils::create_new_block_template_object( $template_file_path, $template_type, $template_slug );
154 $template_built = BlockTemplateUtils::build_template_result_from_file( $template_object, $template_type );
155
156 if ( null !== $template_built ) {
157 return $template_built;
158 }
159
160 // Hand back over to Gutenberg if we can't find a template.
161 return $template;
162 }
163
164 /**
165 * Add the block template objects to be used.
166 *
167 * @param array $query_result Array of template objects.
168 * @param array $query Optional. Arguments to retrieve templates.
169 * @param string $template_type wp_template or wp_template_part.
170 * @return array
171 */
172 public function add_block_templates( $query_result, $query, $template_type ) {
173 if ( ! BlockTemplateUtils::supports_block_templates() ) {
174 return $query_result;
175 }
176
177 $post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
178 $slugs = isset( $query['slug__in'] ) ? $query['slug__in'] : [];
179
180 $template_files = $this->get_block_templates( $slugs, $template_type );
181
182 // @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic.
183 foreach ( $template_files as $template_file ) {
184 // If we have a template which is eligible for a fallback, we need to explicitly tell Gutenberg that
185 // it has a theme file (because it is using the fallback template file). And then `continue` to avoid
186 // adding duplicates.
187 if ( BlockTemplateUtils::set_has_theme_file_if_fallback_is_available( $query_result, $template_file ) ) {
188 continue;
189 }
190
191 // If the current $post_type is set (e.g. on an Edit Post screen), and isn't included in the available post_types
192 // on the template file, then lets skip it so that it doesn't get added. This is typically used to hide templates
193 // in the template dropdown on the Edit Post page.
194 if ( $post_type &&
195 isset( $template_file->post_types ) &&
196 ! in_array( $post_type, $template_file->post_types, true )
197 ) {
198 continue;
199 }
200
201 // It would be custom if the template was modified in the editor, so if it's not custom we can load it from
202 // the filesystem.
203 if ( 'custom' !== $template_file->source ) {
204 $template = BlockTemplateUtils::build_template_result_from_file( $template_file, $template_type );
205 } else {
206 $template_file->title = BlockTemplateUtils::get_block_template_title( $template_file->slug );
207 $template_file->description = BlockTemplateUtils::get_block_template_description( $template_file->slug );
208 $query_result[] = $template_file;
209 continue;
210 }
211
212 $is_not_custom = false === array_search(
213 wp_get_theme()->get_stylesheet() . '//' . $template_file->slug,
214 array_column( $query_result, 'id' ),
215 true
216 );
217 $fits_slug_query =
218 ! isset( $query['slug__in'] ) || in_array( $template_file->slug, $query['slug__in'], true );
219 $fits_area_query =
220 ! isset( $query['area'] ) || $template_file->area === $query['area'];
221 $should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
222 if ( $should_include ) {
223 $query_result[] = $template;
224 }
225 }
226
227 // We need to remove theme (i.e. filesystem) templates that have the same slug as a customised one.
228 // This only affects saved templates that were saved BEFORE a theme template with the same slug was added.
229 $query_result = BlockTemplateUtils::remove_theme_templates_with_custom_alternative( $query_result );
230
231 /**
232 * WC templates from theme aren't included in `$this->get_block_templates()` but are handled by Gutenberg.
233 * We need to do additional search through all templates file to update title and description for WC
234 * templates that aren't listed in theme.json.
235 */
236 $query_result = array_map(
237 function ( $template ) {
238 if ( 'theme' === $template->origin ) {
239 return $template;
240 }
241 if ( $template->title === $template->slug ) {
242 $template->title = BlockTemplateUtils::get_block_template_title( $template->slug );
243 }
244 if ( ! $template->description ) {
245 $template->description = BlockTemplateUtils::get_block_template_description( $template->slug );
246 }
247 return $template;
248 },
249 $query_result
250 );
251
252 return $query_result;
253 }
254
255 /**
256 * Gets the templates saved in the database.
257 *
258 * @param array $slugs An array of slugs to retrieve templates for.
259 * @param string $template_type wp_template or wp_template_part.
260 *
261 * @return int[]|\WP_Post[] An array of found templates.
262 */
263 public function get_block_templates_from_db( $slugs = [], $template_type = 'wp_template' ) {
264 $check_query_args = [
265 'post_type' => $template_type,
266 'posts_per_page' => -1,
267 'no_found_rows' => true,
268 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
269 [
270 'taxonomy' => 'wp_theme',
271 'field' => 'name',
272 'terms' => [ BlockTemplateUtils::PLUGIN_SLUG, get_stylesheet() ]
273 ]
274 ]
275 ];
276
277 if ( is_array( $slugs ) && count( $slugs ) > 0 ) {
278 $check_query_args['post_name__in'] = $slugs;
279 }
280
281 $check_query = new \WP_Query( $check_query_args );
282 $saved_betterdocs_templates = $check_query->posts;
283
284 return array_map(
285 function ( $saved_betterdocs_template ) {
286 return BlockTemplateUtils::build_template_result_from_post( $saved_betterdocs_template );
287 },
288 $saved_betterdocs_templates
289 );
290 }
291
292 /**
293 * Gets the templates from the BetterDocs blocks directory
294 *
295 * @param string[] $slugs An array of slugs to filter templates by. Templates whose slug does not match will not be returned.
296 * @param array $already_found_templates Templates that have already been found, these are customised templates that are loaded from the database.
297 * @param string $template_type wp_template or wp_template_part.
298 *
299 * @return array Templates from the BetterDocs blocks plugin directory.
300 */
301 public function get_block_templates_from_betterdocs( $slugs, $already_found_templates, $template_type = 'wp_template' ) {
302 $directory = $this->get_templates_directory( $template_type );
303 $template_files = BlockTemplateUtils::get_template_paths( $directory );
304
305 $templates = [];
306
307 foreach ( $template_files as $template_file ) {
308 $template_slug = BlockTemplateUtils::generate_template_slug_from_path( $template_file );
309
310 // This template does not have a slug we're looking for. Skip it.
311 if ( is_array( $slugs ) && count( $slugs ) > 0 && ! in_array( $template_slug, $slugs, true ) ) {
312 continue;
313 }
314
315 // If the the template is already in the list (i.e. it came from the
316 // database) then we should not overwrite it with the one from the filesystem.
317 if (
318 count(
319 array_filter(
320 $already_found_templates,
321 function ( $template ) use ( $template_slug ) {
322 $template_obj = (object) $template; //phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
323 return $template_obj->slug === $template_slug;
324 }
325 )
326 ) > 0 ) {
327 continue;
328 }
329
330 // At this point the template only exists in the Blocks filesystem and has not been saved in the DB,
331 // or superseded by the theme.
332 $templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug );
333 }
334 return $templates;
335 }
336
337 /**
338 * Get and build the block template objects from the block template files.
339 *
340 * @param array $slugs An array of slugs to retrieve templates for.
341 * @param string $template_type wp_template or wp_template_part.
342 *
343 * @return array WP_Block_Template[] An array of block template objects.
344 */
345 public function get_block_templates( $slugs = [], $template_type = 'wp_template' ) {
346 $templates_from_db = $this->get_block_templates_from_db( $slugs, $template_type );
347 $templates_from_betterdocs = $this->get_block_templates_from_betterdocs( $slugs, $templates_from_db, $template_type );
348 $templates = array_merge( $templates_from_db, $templates_from_betterdocs );
349 return $templates;
350 }
351
352 /**
353 * Gets the directory where templates of a specific template type can be found.
354 *
355 * @param string $template_type wp_template or wp_template_part.
356 *
357 * @return string
358 */
359 protected function get_templates_directory( $template_type = 'wp_template' ) {
360 return $this->templates_directory;
361 }
362
363 /**
364 * Checks whether a block template with that name exists in BetterDocs Blocks
365 *
366 * @param string $template_name Template to check.
367 * @param string $template_type wp_template or wp_template_part.
368 *
369 * @return boolean
370 */
371 public function block_template_is_available( $template_name, $template_type = 'wp_template' ) {
372 if ( ! $template_name ) {
373 return false;
374 }
375 $directory = $this->get_templates_directory( $template_type ) . '/' . $template_name . '.html';
376
377 return is_readable( $directory ) || $this->get_block_templates( [ $template_name ], $template_type );
378 }
379 }
380