PluginProbe
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More / 2.2.0
StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More v2.2.0
2.3.0 2.2.0 2.1.1 2.1.0 2.0.0 1.10.0 1.9.1 1.9.0 1.2.1 1.2.2 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 All 59 releases
storeengine / includes / frontend / template / block.php

block.php in StoreEngine — Complete eCommerce Solution with Memberships, Licensing, Affiliates & More 2.2.0, at includes/frontend/template/block.php

423 lines 14.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace StoreEngine\frontend\template;
4
5 use RecursiveDirectoryIterator;
6 use RecursiveIteratorIterator;
7 use RegexIterator;
8 use StoreEngine\Utils\Fs;
9 use StoreEngine\Utils\Helper;
10 use WP_Block_Template;
11 use WP_Error;
12 use WP_Query;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit;
16 }
17
18 class Block {
19
20 public static function init() {
21 $self = new self();
22 add_filter( 'page_template_hierarchy', array( $self, 'update_hierarchy' ) );
23 add_filter( 'pre_get_block_file_template', array( $self, 'get_block_file_template' ), 10, 3 );
24 add_filter( 'get_block_templates', array( $self, 'add_block_templates' ), 10, 3 );
25 }
26
27 public static function update_hierarchy( array $templates ): array {
28 global $storeengine_settings;
29
30 if ( ! empty( $storeengine_settings->cart_page ) ) {
31 $cart_page = get_post_field( 'post_name', $storeengine_settings->cart_page );
32 if ( in_array( "page-$cart_page.php", $templates, true ) ) {
33 return array_merge( array( 'cart-storeengine.php' ), $templates );
34 }
35 }
36
37 if ( ! empty( $storeengine_settings->checkout_page ) ) {
38 $checkout_page = get_post_field( 'post_name', $storeengine_settings->checkout_page );
39 if ( in_array( "page-$checkout_page.php", $templates, true ) ) {
40 return array_merge( array( 'checkout-storeengine.php' ), $templates );
41 }
42 }
43
44 if ( ! empty( $storeengine_settings->dashboard_page ) ) {
45 $dashboard_page = get_post_field( 'post_name', $storeengine_settings->dashboard_page );
46 if ( in_array( "page-$dashboard_page.php", $templates, true ) ) {
47 return array_merge( array( 'store-dashboard-storeengine.php' ), $templates );
48 }
49 }
50
51 if ( ! empty( $storeengine_settings->thankyou_page ) ) {
52 $thankyou_page = get_post_field( 'post_name', $storeengine_settings->thankyou_page );
53 if ( in_array( "page-$thankyou_page.php", $templates, true ) ) {
54 return array_merge( array( 'thankyou-storeengine.php' ), $templates );
55 }
56 }
57
58 return $templates;
59 }
60
61 public function get_block_file_template( $template, $id, $template_type ) {
62 $template_name_parts = explode( '//', $id );
63
64 if ( count( $template_name_parts ) < 2 ) {
65 return $template;
66 }
67
68 list( $template_id, $template_slug ) = $template_name_parts;
69
70 if ( STOREENGINE_PLUGIN_SLUG !== $template_id ) {
71 return $template;
72 }
73
74 // If we don't have a template let Gutenberg do its thing.
75 if ( ! $this->block_template_is_available( $template_slug, $template_type ) ) {
76 return $template;
77 }
78
79 $directory = STOREENGINE_BLOCK_TEMPLATES_DIR_PATH;
80
81 $template_file_path = $directory . '/' . $template_slug . '.html';
82
83 $template_object = $this->create_new_block_template_object( $template_file_path, $template_type, $template_slug );
84
85 return $this->build_template_result_from_file( $template_object, $template_type );
86 }
87
88 public function block_template_is_available( $template_name, $template_type = 'wp_template' ): bool {
89 if ( ! $template_name ) {
90 return false;
91 }
92 $directory = STOREENGINE_BLOCK_TEMPLATES_DIR_PATH . $template_name . '.html';
93
94 return is_readable( $directory ) || $this->get_block_templates( [ $template_name ], $template_type );
95 }
96
97 public function get_block_templates( $slugs = [], $template_type = 'wp_template' ): array {
98 $templates_from_db = $this->get_block_templates_from_db( $slugs, $template_type );
99 $templates_from_plugin = $this->get_block_templates_from_plugin( $slugs, $templates_from_db, $template_type );
100
101 return array_merge( $templates_from_db, $templates_from_plugin );
102 }
103
104 public function get_block_templates_from_db( $slugs = [], $template_type = 'wp_template' ): array {
105 $check_query_args = [
106 'post_type' => $template_type,
107 'posts_per_page' => - 1,
108 'no_found_rows' => true,
109 'tax_query' => [ // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
110 [
111 'taxonomy' => 'wp_theme',
112 'field' => 'name',
113 'terms' => array( STOREENGINE_PLUGIN_SLUG, get_stylesheet() ),
114 ],
115 ],
116 ];
117
118 if ( is_array( $slugs ) && count( $slugs ) > 0 ) {
119 $check_query_args['post_name__in'] = $slugs;
120 }
121
122 $check_query = new WP_Query( $check_query_args );
123 $saved_templates = $check_query->posts;
124
125 return array_map(
126 function ( $saved_template ) {
127 return $this->build_template_result_from_post( $saved_template );
128 },
129 $saved_templates
130 );
131 }
132
133 public static function build_template_result_from_post( $post ) {
134 $terms = get_the_terms( $post, 'wp_theme' );
135
136 if ( is_wp_error( $terms ) ) {
137 return $terms;
138 }
139
140 if ( ! $terms ) {
141 return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'storeengine' ) );
142 }
143
144 $theme = $terms[0]->name;
145
146 $template = new WP_Block_Template();
147 $template->wp_id = $post->ID;
148 $template->id = $theme . '//' . $post->post_name;
149 $template->theme = $theme;
150 $template->content = $post->post_content;
151 $template->slug = $post->post_name;
152 $template->source = 'custom';
153 $template->type = $post->post_type;
154 $template->description = $post->post_excerpt;
155 $template->title = $post->post_title;
156 $template->status = $post->post_status;
157 $template->has_theme_file = true;
158 $template->is_custom = false;
159 $template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
160
161 if ( 'wp_template_part' === $post->post_type ) {
162 $type_terms = get_the_terms( $post, 'wp_template_part_area' );
163 if ( ! is_wp_error( $type_terms ) && false !== $type_terms ) {
164 $template->area = $type_terms[0]->name;
165 }
166 }
167
168 if ( STOREENGINE_PLUGIN_SLUG === $theme ) {
169 $template->origin = 'plugin';
170 }
171
172 return $template;
173 }
174
175 public function get_block_templates_from_plugin( $slugs, $already_found_templates, $template_type = 'wp_template' ): array {
176 $template_files = $this->get_templates_files_from_plugin();
177 $templates = [];
178
179 foreach ( $template_files as $template_file ) {
180 $template_slug = $this->generate_template_slug_from_path( $template_file );
181
182 // This template does not have a slug we're looking for. Skip it.
183 if ( is_array( $slugs ) && count( $slugs ) > 0 && ! in_array( $template_slug, $slugs, true ) ) {
184 continue;
185 }
186
187 // If the template is already in the list (i.e. it came from the
188 // database) then we should not overwrite it with the one from the filesystem.
189 if (
190 count(
191 array_filter(
192 $already_found_templates,
193 function ( $template ) use ( $template_slug ) {
194 $template_obj = (object) $template; //phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.Found
195
196 return $template_obj->slug === $template_slug;
197 }
198 )
199 ) > 0 ) {
200 continue;
201 }
202
203 // At this point the template only exists in the Blocks filesystem and has not been saved in the DB,
204 // or superseded by the theme.
205 $templates[] = $this->create_new_block_template_object( $template_file, $template_type, $template_slug );
206 }//end foreach
207 return $templates;
208 }
209
210 public function get_templates_files_from_plugin(): array {
211 return $this->get_template_paths( STOREENGINE_BLOCK_TEMPLATES_DIR_PATH );
212 }
213
214 public function get_template_paths( $base_directory ): array {
215 $path_list = [];
216 if ( file_exists( $base_directory ) ) {
217 $nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
218 $nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RegexIterator::GET_MATCH );
219 foreach ( $nested_html_files as $path => $file ) {
220 $path_list[] = $path;
221 }
222 }
223
224 return $path_list;
225 }
226
227 public function generate_template_slug_from_path( $path ): string {
228 $template_extension = '.html';
229
230 return basename( $path, $template_extension );
231 }
232
233 public function create_new_block_template_object( $template_file, $template_type, $template_slug, $template_is_from_theme = false ): object {
234 $theme_slug = get_template();
235
236 $new_template_item = [
237 'slug' => $template_slug,
238 'id' => $template_is_from_theme ? $theme_slug . '//' . $template_slug : STOREENGINE_PLUGIN_SLUG . '//' . $template_slug,
239 'path' => $template_file,
240 'type' => $template_type,
241 'theme' => $template_is_from_theme ? $theme_slug : STOREENGINE_PLUGIN_SLUG,
242 'source' => $template_is_from_theme ? 'theme' : 'plugin',
243 'title' => $this->convert_slug_to_title( $template_slug ),
244 'description' => '',
245 'post_types' => array(), // Don't appear in any Edit Post template selector dropdown.
246 ];
247
248 return (object) $new_template_item;
249 }
250
251 public function convert_slug_to_title( $template_slug ): ?string {
252 switch ( $template_slug ) {
253 case 'single-storeengine_product':
254 return __( 'Single Product', 'storeengine' );
255 case 'archive-storeengine_product':
256 return __( 'Archive Products', 'storeengine' );
257 default:
258 // Replace all hyphens and underscores with spaces.
259 return ucwords( preg_replace( '/[\-_]/', ' ', $template_slug ) );
260 }
261 }
262
263 public function build_template_result_from_file( $template_file, $template_type ): WP_Block_Template {
264 $template_file = (object) $template_file;
265
266 // If the theme has an archive-products.html template but does not have product taxonomy templates
267 // then we will load in the archive-product.html template from the theme to use for product taxonomies on the frontend.
268 $template_is_from_theme = 'theme' === $template_file->source;
269 $theme_slug = get_template();
270
271 $template_content = Helper::remove_line_break( Fs::get_contents( $template_file->path ) );
272 $template = new WP_Block_Template();
273 $template->id = $template_is_from_theme ? $theme_slug . '//' . $template_file->slug : STOREENGINE_PLUGIN_SLUG . '//' . $template_file->slug;
274 $template->theme = $template_is_from_theme ? $theme_slug : STOREENGINE_PLUGIN_SLUG;
275 $template->content = $template_content;
276 $template->content = self::inject_theme_attribute_in_content( $template_content );
277 // Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909.
278 $template->source = $template_file->source ? $template_file->source : 'plugin';
279 $template->slug = $template_file->slug;
280 $template->type = $template_type;
281 $template->title = ! empty( $template_file->title ) ? $template_file->title : $this->get_block_template_title( $template_file->slug );
282 $template->description = ! empty( $template_file->description ) ? $template_file->description : $this->get_block_template_description( $template_file->slug );
283 $template->status = 'publish';
284 $template->has_theme_file = true;
285 $template->origin = $template_file->source;
286 $template->is_custom = false; // Templates loaded from the filesystem aren't custom, ones that have been edited and loaded from the DB are.
287 $template->post_types = array(); // Don't appear in any Edit Post template selector dropdown.
288 $template->area = 'uncategorized';
289
290 return $template;
291 }
292
293 public function inject_theme_attribute_in_content( $template_content ) {
294 $has_updated_content = false;
295 $new_content = '';
296 $template_blocks = parse_blocks( $template_content );
297
298 $blocks = $this->flatten_blocks( $template_blocks );
299 foreach ( $blocks as &$block ) {
300 if (
301 'core/template-part' === $block['blockName'] &&
302 ! isset( $block['attrs']['theme'] )
303 ) {
304 $block['attrs']['theme'] = wp_get_theme()->get_stylesheet();
305 $has_updated_content = true;
306 }
307 }
308
309 if ( $has_updated_content ) {
310 foreach ( $template_blocks as &$block ) {
311 $new_content .= serialize_block( $block );
312 }
313
314 return $new_content;
315 }
316
317 return $template_content;
318 }
319
320 public function flatten_blocks( &$blocks ): array {
321 $all_blocks = [];
322 $queue = [];
323 foreach ( $blocks as &$block ) {
324 $queue[] = &$block;
325 }
326 $queue_count = count( $queue );
327
328 while ( $queue_count > 0 ) {
329 $block = &$queue[0];
330 array_shift( $queue );
331 $all_blocks[] = &$block;
332
333 if ( ! empty( $block['innerBlocks'] ) ) {
334 foreach ( $block['innerBlocks'] as &$inner_block ) {
335 $queue[] = &$inner_block;
336 }
337 }
338
339 $queue_count = count( $queue );
340 }
341
342 return $all_blocks;
343 }
344
345 public function get_block_template_title( $template_slug ) {
346 $plugin_template_types = $this->get_plugin_block_template_types();
347 if ( isset( $plugin_template_types[ $template_slug ] ) ) {
348 return $plugin_template_types[ $template_slug ]['title'];
349 } else {
350 // Human friendly title converted from the slug.
351 return ucwords( preg_replace( '/[\-_]/', ' ', $template_slug ) );
352 }
353 }
354
355 public function get_plugin_block_template_types(): array {
356 return [
357 'single-storeengine_product' => array(
358 'title' => _x( 'Single Product', 'Template name', 'storeengine' ),
359 'description' => __( 'Template used to display the single docs.', 'storeengine' ),
360 ),
361 'archive-storeengine_product' => array(
362 'title' => _x( 'Archive Products', 'Template name', 'storeengine' ),
363 'description' => __( 'Template used to display the single docs.', 'storeengine' ),
364 ),
365 ];
366 }
367
368 public function get_block_template_description( $template_slug ) {
369 $plugin_template_types = $this->get_plugin_block_template_types();
370 if ( isset( $plugin_template_types[ $template_slug ] ) ) {
371 return $plugin_template_types[ $template_slug ]['description'];
372 }
373
374 return '';
375 }
376
377 public function add_block_templates( $query_result, $query, $template_type ) {
378 if ( 'wp_template' !== trim( $template_type ) ) {
379 return $query_result;
380 }
381
382 $post_type = $query['post_type'] ?? '';
383 $slugs = $query['slug__in'] ?? [];
384
385 $template_files = $this->get_block_templates( $slugs, $template_type );
386
387 // @todo: Add apply_filters to _gutenberg_get_template_files() in Gutenberg to prevent duplication of logic.
388 foreach ( $template_files as $template_file ) {
389 if ( $post_type && isset( $template_file->post_types ) && ! in_array( $post_type, $template_file->post_types, true )
390 ) {
391 continue;
392 }
393
394 // It would be custom if the template was modified in the editor, so if it's not custom we can load it from
395 // the filesystem.
396 if ( 'custom' !== $template_file->source ) {
397 $template = $this->build_template_result_from_file( $template_file, $template_type );
398 } else {
399 $template_file->title = $this->get_block_template_title( $template_file->slug );
400 $template_file->description = $this->get_block_template_description( $template_file->slug );
401 $query_result[] = $template_file;
402 continue;
403 }
404
405 $is_not_custom = false === array_search(
406 wp_get_theme()->get_stylesheet() . '//' . $template_file->slug,
407 array_column( $query_result, 'id' ),
408 true
409 );
410 $fits_slug_query =
411 ! isset( $query['slug__in'] ) || in_array( $template_file->slug, $query['slug__in'], true );
412 $fits_area_query =
413 ! isset( $query['area'] ) || $template_file->area === $query['area'];
414 $should_include = $is_not_custom && $fits_slug_query && $fits_area_query;
415 if ( $should_include ) {
416 $query_result[] = $template;
417 }
418 }//end foreach
419
420 return $query_result;
421 }
422 }
423