PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / trunk
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! vtrunk
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / modules / theme-builder / Managers / LocationManager.php

LocationManager.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! trunk, at modules/theme-builder/Managers/LocationManager.php

621 lines 18.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Modules\ThemeBuilder\Managers;
4
5 use EbStyleHandler;
6 use EssentialBlocks\Modules\StyleHandler;
7 use Templately\Modules\ThemeBuilder\PageTemplates;
8 use Templately\Modules\ThemeBuilder\Source;
9 use Templately\Modules\ThemeBuilder\ThemeBuilder;
10 use Templately\Modules\ThemeBuilder\Types\BaseTemplate;
11 use Templately\Modules\ThemeBuilder\Types\ThemeTemplate;
12 use ElementorPro\Modules\ThemeBuilder\Module;
13 use Elementor\Core\Files\CSS\Post as Post_CSS;
14 use Elementor\Plugin;
15 use Templately\Modules\ThemeBuilder\TemplateLoader;
16 use Elementor\Core\Base\Elements_Iteration_Actions\Assets as Assets_Iteration_Action;
17
18 class LocationManager {
19 /**
20 * @var array<string, ThemeTemplate>
21 */
22 public $locations_queue = [];
23 public $locations_skipped = [];
24 public $locations_printed = [];
25 public $did_locations = [];
26
27 protected $locations = [];
28
29 /**
30 * @var ThemeBuilder
31 */
32 protected $builder;
33
34 public function __construct( $builder ) {
35 $this->builder = $builder;
36
37
38 /**
39 * Priority is 13,
40 * Because it should be run after elementor & woocommerce
41 */
42 add_filter( 'template_include', [ $this, 'template_include' ], 13 );
43
44 /**
45 * Priority is 7,
46 * Because it should run before elementor
47 */
48 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_elementor_styles' ], 7 );
49 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_template_assets' ], 8 );
50 add_action( 'wp_enqueue_scripts', [ $this, 'preload_gutenberg_template_styles' ], 9 );
51
52 // Cache clearing hooks
53 add_action( 'save_post', [ $this, 'clear_header_styles_cache' ] );
54 add_action( 'delete_post', [ $this, 'clear_header_styles_cache' ] );
55 add_action( 'trash_post', [ $this, 'clear_header_styles_cache' ] );
56 }
57
58 private function set_locations() {
59 if(!empty($this->locations)) {
60 return;
61 }
62 $this->locations = [
63 'header' => [
64 'label' => __( 'Header', 'templately' ),
65 'multiple' => false
66 ],
67 'footer' => [
68 'label' => __( 'Footer', 'templately' ),
69 'multiple' => false
70 ],
71 'archive' => [
72 'label' => __( 'Archive', 'templately' ),
73 'multiple' => false
74 ],
75 'single' => [
76 'label' => __( 'Single', 'templately' ),
77 'multiple' => false
78 ]
79 ];
80 }
81
82 public function template_include( $template_path ) {
83 $location = '';
84 $page_template_module = $this->get_template_module();
85
86 /**
87 * Return if it is Elementor Template.
88 * This should be elementor's responsibility.
89 */
90
91 if ( get_post_type( get_the_ID() ) === 'elementor_library' ) {
92 return $template_path;
93 }
94 else if( $this->get_platform(get_the_ID()) === 'elementor' && !class_exists('Elementor\Plugin') ) {
95 return $template_path;
96 }
97
98 if ( is_singular() ) {
99 /**
100 * @var BaseTemplate $template
101 */
102 $template = $this->builder::$templates_manager->get( get_the_ID() );
103
104 if ( $template && $template->get_property( 'support_wp_page_templates' ) ) {
105 $page_template_module->set_platform( $template->get_platform() );
106 $wp_page_template = $template->get_meta( '_wp_page_template' );
107
108 $_custom_template_path = $page_template_module->get_template_path( $wp_page_template );
109
110
111 if ( empty( $_custom_template_path ) ) {
112 $location = 'single';
113
114 $templates_for_location = $this->builder::$conditions_manager->get_templates_by_location( $location );
115
116 if ( empty( $templates_for_location ) ) {
117 return $template_path;
118 }
119
120 $template_id = key( $templates_for_location );
121
122 $template = $templates_for_location[ $template_id ];
123 $page_template = $template->get_meta( '_wp_page_template' );
124 $platform = $template->get_platform();
125
126 if( $platform === 'elementor' && !class_exists('Elementor\Plugin') ) {
127 return $template_path;
128 }
129 if ( ! empty( $platform ) ) {
130 $page_template_module->set_platform( $platform );
131 }
132 // A conditioned single whose document has no custom page template
133 // (_wp_page_template = 'default' / '') resolves to no path, so fall
134 // back to the header/footer wrapper — the same fallback the
135 // archive/non-singular block below applies. Without this,
136 // get_template_path() returns '' and the unconditional `return`
137 // made WP `include ''` → a blank, 0-byte page.
138 if ( empty( $page_template ) || 'default' === $page_template ) {
139 $page_template = $page_template_module->get_header_footer_template();
140 }
141
142 $path = $page_template_module->get_template_path( $page_template );
143
144 // Don't hijack the request with an empty template (blank page);
145 // let the original theme template render instead.
146 if ( empty( $path ) ) {
147 return $template_path;
148 }
149
150 $page_template_module->set_print_callback( function () use ( $location ) {
151 $this->do_location( $location );
152 } );
153 set_query_var( 'using_templately_template', 1 );
154
155 return $path;
156 }
157
158 if ( $wp_page_template && $wp_page_template !== 'default' ) {
159 set_query_var( 'using_templately_template', 1 );
160
161 return $_custom_template_path;
162 }
163 }
164 } else {
165 $template = false;
166 }
167
168 if ( $template instanceof ThemeTemplate ) {
169 $location = $template->get_location();
170 } elseif ( function_exists( 'is_shop' ) && is_shop() ) {
171 $location = 'archive';
172 } elseif ( is_archive() || is_tax() || is_home() || is_search() ) {
173 $location = 'archive';
174 } elseif ( is_singular() || is_404() ) {
175 $location = 'single';
176 }
177
178 if ( $location ) {
179 $templates_for_location = $this->builder::$conditions_manager->get_templates_by_location( $location );
180
181 if ( empty( $templates_for_location ) ) {
182 set_query_var( 'using_templately_template', 1 );
183
184 return $template_path;
185 }
186
187 if ( 'single' === $location || 'archive' === $location ) {
188 $template_id = key( $templates_for_location );
189 $template = $templates_for_location[ $template_id ];
190 $document_page_template = $template->get_meta( '_wp_page_template' );
191 $platform = $template->get_platform();
192
193 if ( ! empty( $platform ) ) {
194 $page_template_module->set_platform( $platform );
195 }
196
197 if ( $document_page_template ) {
198 $page_template = $document_page_template;
199 }
200
201 if(!empty($template_id) && !empty($template) && $template->get_type() == "course_archive"){
202 // $__post = $GLOBALS['post'];
203 // learndash_course_grid_load_resources();
204 // $GLOBALS['post'] = $__post;
205 $GLOBALS['post'] = get_post($template_id);
206 }
207 }
208 }
209 $is_header_footer = 'header' === $location || 'footer' === $location;
210 if ( empty( $page_template ) && ! $is_header_footer ) {
211 $page_template = $page_template_module->get_header_footer_template();
212 }
213
214 if ( ! empty( $page_template ) ) {
215 $path = $page_template_module->get_template_path( $page_template );
216
217 if ( $path ) {
218 $page_template_module->set_print_callback( function () use ( $location ) {
219 $this->do_location( $location );
220 } );
221 set_query_var( 'using_templately_template', 1 );
222 $template_path = $path;
223 }
224 }
225
226 return $template_path;
227 }
228
229 private function get_platform($post_id) {
230 $post_type = get_post_type( get_the_ID() );
231 if ( $post_type == Source::CPT ) {
232 $platform = get_post_meta( $post_id, Source::PLATFORM_META_KEY, true );
233 } elseif ( get_post_meta( $post_id, '_elementor_edit_mode', true ) == 'builder' || $post_type == 'elementor_library' ) {
234 $platform = 'elementor';
235 } else {
236 $platform = 'gutenberg';
237 }
238 return $platform;
239 }
240
241 /**
242 * Get page templating modules and set platform if needed.
243 *
244 * @param string $platform
245 *
246 * @return PageTemplates
247 */
248 private function get_template_module( string $platform = '' ): PageTemplates {
249 $module = templately()->theme_builder::$page_template_module;
250
251 if ( ! empty( $platform ) ) {
252 $module = $module->set_platform( $platform );
253 }
254
255 return $module;
256 }
257
258 public function get_location( $location ) {
259 $locations = $this->get_locations();
260
261 return $locations[ $location ] ?? [];
262 }
263
264 public function get_locations(): array {
265 /**
266 * Don't know yet if we need it or not.
267 */
268 $this->set_locations();
269
270 $this->register_locations();
271
272 return $this->locations;
273 }
274
275 public function register_locations() {
276 if ( ! did_action( 'templately_locations' ) ) {
277 do_action( 'templately_locations', $this );
278 }
279 }
280
281 /**
282 * Getting the Idea From Elementor itself.
283 *
284 * @param $location
285 *
286 * @return bool
287 */
288 public function do_location( $location ): bool {
289 $templates_for_location = $this->builder::$conditions_manager->get_templates_by_location( $location );
290
291 foreach ( $templates_for_location as $template_id => $template ) {
292 $this->add_template_to_location( $location, $template_id );
293 }
294
295 if ( empty( $this->locations_queue[ $location ] ) ) {
296 return false;
297 }
298
299 while ( ! empty( $this->locations_queue[ $location ] ) ) {
300 $template_id = key( $this->locations_queue[ $location ] );
301 $template = $this->builder->get_template( $template_id );
302
303 if ( ! $template || $this->is_printed( $location, $template_id ) ) {
304 $this->skip_template_from_location( $location, $template_id );
305 continue;
306 }
307
308 if ( empty( $documents_by_conditions[ $template_id ] ) ) {
309 $post_status = get_post_status( $template_id );
310 if ( 'publish' !== $post_status ) {
311 $this->skip_template_from_location( $location, $template_id );
312 continue;
313 }
314 }
315
316 // Fire before printing to allow style preparation
317 do_action("templately_printed_location", $template_id, $location, $template);
318
319 $template->print_content();
320 $this->did_locations[] = $location;
321
322 $this->set_is_printed( $location, $template_id );
323 }
324
325 return true;
326 }
327
328 /**
329 * Enqueue CSS styles for Elementor-based templates.
330 *
331 * Uses Elementor's Post_CSS class to enqueue styles for all locations
332 * (header, footer, archive, single). Only runs for Elementor platform.
333 *
334 * @since 3.0.0
335 * @return void
336 */
337 public function enqueue_elementor_styles() {
338 if (
339 class_exists('ElementorPro\Modules\ThemeBuilder\Module') &&
340 Module::is_preview()
341 ) {
342 return;
343 }
344
345 $locations = $this->get_locations();
346
347 if ( empty( $locations ) ) {
348 return;
349 }
350
351 if(class_exists('Elementor\Core\Files\CSS\Post')){
352 $current_post_id = get_the_ID();
353
354 foreach ( $locations as $location => $settings ) {
355 $templates_for_location = $this->builder::$conditions_manager->get_templates_by_location( $location );
356
357 foreach ( $templates_for_location as $template ) {
358 if ( ! $template->is_elementor_template() ) {
359 continue;
360 }
361
362 $this->iterate_elements($template->get_main_id());
363
364 $post_id = $template->get_main_id();
365
366 // Don't enqueue current post here (let the preview/frontend components to handle it)
367 if ( $current_post_id !== $post_id ) {
368 $css_file = new Post_CSS( $post_id );
369 $css_file->enqueue();
370 }
371 }
372 }
373
374 }
375 }
376
377 private function iterate_elements( $post_id ) {
378 if ( ! class_exists( 'Elementor\Plugin' ) || ! class_exists( Assets_Iteration_Action::class ) ) {
379 return;
380 }
381
382 $plugin = Plugin::instance();
383 if ( ! $plugin || ! isset( $plugin->documents ) || ! isset( $plugin->db ) || ! isset( $plugin->elements_manager ) ) {
384 return;
385 }
386
387 $document = $plugin->documents->get( $post_id );
388 if ( ! $document ) {
389 return;
390 }
391
392 $elements = $document->get_elements_data();
393 if ( empty( $elements ) ) {
394 return;
395 }
396
397 $unique_page_elements = [];
398 $elements_iteration_action = new Assets_Iteration_Action( $document );
399 $elements_iteration_action->set_mode( 'render' );
400
401 $plugin->db->iterate_data( $elements, function( array $element_data ) use ( &$unique_page_elements, $elements_iteration_action, $plugin ) {
402 $element_type = 'widget' === $element_data['elType'] ? $element_data['widgetType'] : $element_data['elType'];
403
404 $element = $plugin->elements_manager->create_element_instance( $element_data );
405
406 if ( $element ) {
407 if ( ! in_array( $element_type, $unique_page_elements, true ) ) {
408 $unique_page_elements[] = $element_type;
409
410 $elements_iteration_action->unique_element_action( $element );
411 }
412
413 $elements_iteration_action->element_action( $element );
414 }
415
416 return $element_data;
417 } );
418
419 $elements_iteration_action->after_elements_iteration();
420 }
421
422 /**
423 * Fire action for Essential Blocks to write CSS files.
424 *
425 * Iterates all Gutenberg template locations and fires the
426 * templately_printed_location action.
427 *
428 * Note: This action only triggers Essential Blocks to generate the CSS file
429 * and add the template ID to its processing list. It does NOT enqueue the
430 * CSS file immediately. The actual enqueue happens later in Essential Blocks'
431 * `enqueue_frontend_assets` method (hooked to wp_enqueue_scripts at priority 10),
432 * which relies on this action having already fired (at priority 8).
433 *
434 * @since 3.0.0
435 * @return void
436 */
437 public function enqueue_template_assets() {
438 $using_templately_builder = get_query_var( 'using_templately_template' );
439 if ( ($using_templately_builder || TemplateLoader::is_header_footer()) && function_exists( 'templately' ) ) {
440 $template_locations = [ 'header', 'footer', 'archive', 'single' ];
441 foreach ( $template_locations as $location ) {
442 $template = templately()->theme_builder::$conditions_manager->get_templates_by_location( $location );
443 if ( empty( $template ) ) {
444 continue;
445 }
446 $template = array_pop( $template );
447 if ( $template->platform == 'gutenberg' ) {
448 $template = is_array( $template ) ? array_pop( $template ) : $template;
449 do_action("templately_printed_location", $template->get_main_id(), $location, $template);
450 }
451 }
452 }
453 }
454
455 /**
456 * Pre-parse Gutenberg template blocks to enqueue styles in head.
457 *
458 * Iterates all template locations, parses block content, and triggers
459 * block style registration BEFORE wp_head() outputs styles. This ensures
460 * all block styles are properly enqueued rather than printed inline.
461 *
462 * @since 3.0.0
463 * @return void
464 */
465 public function preload_gutenberg_template_styles() {
466 if ( ! function_exists( 'templately' ) || ! function_exists( 'parse_blocks' ) ) {
467 return;
468 }
469
470 $template_locations = [ 'header' ];
471
472 foreach ( $template_locations as $location ) {
473 $templates = templately()->theme_builder::$conditions_manager->get_templates_by_location( $location );
474
475 if ( empty( $templates ) ) {
476 continue;
477 }
478
479 foreach ( $templates as $template ) {
480 if ( $template->platform !== 'gutenberg' ) {
481 continue;
482 }
483
484 $post_id = $template->get_main_id();
485 $post = get_post( $post_id );
486
487 if ( ! $post ) {
488 continue;
489 }
490
491 // Check for cached styles
492 $transient_key = 'templately_header_styles_' . $post_id;
493 $style_handles = get_transient( $transient_key );
494
495 if ( false === $style_handles ) {
496 if ( empty( $post->post_content ) ) {
497 continue;
498 }
499
500 // Parse blocks and extract styles
501 $blocks = parse_blocks( $post->post_content );
502 $style_handles = [];
503 $this->get_block_styles_recursive( $blocks, $style_handles );
504
505 // Cache the handles for a long time (e.g., 1 month)
506 set_transient( $transient_key, $style_handles, MONTH_IN_SECONDS );
507 }
508
509 // Enqueue the styles
510 if ( ! empty( $style_handles ) ) {
511 foreach ( $style_handles as $handle ) {
512 wp_enqueue_style( $handle );
513 }
514 }
515 }
516 }
517 }
518
519 /**
520 * Recursively extract style handles for all blocks.
521 *
522 * @since 3.0.0
523 * @param array $blocks Parsed blocks array.
524 * @param array $handles Reference to array of handles to populate.
525 * @return void
526 */
527 private function get_block_styles_recursive( array $blocks, array &$handles ) {
528 if ( ! class_exists( 'WP_Block_Type_Registry' ) ) {
529 return;
530 }
531
532 foreach ( $blocks as $block ) {
533 if ( ! empty( $block['blockName'] ) ) {
534 // Get registered block type
535 $block_type = \WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
536
537 if ( $block_type ) {
538 // Enqueue style handles (WP 5.9+)
539 if ( ! empty( $block_type->style_handles ) ) {
540 foreach ( $block_type->style_handles as $handle ) {
541 if ( ! in_array( $handle, $handles ) ) {
542 $handles[] = $handle;
543 }
544 }
545 }
546 // Legacy style property support
547 if ( ! empty( $block_type->style ) ) {
548 $styles = is_array( $block_type->style ) ? $block_type->style : [ $block_type->style ];
549 foreach ( $styles as $style ) {
550 if ( ! in_array( $style, $handles ) ) {
551 $handles[] = $style;
552 }
553 }
554 }
555 }
556 }
557
558 // Process inner blocks recursively
559 if ( ! empty( $block['innerBlocks'] ) ) {
560 $this->get_block_styles_recursive( $block['innerBlocks'], $handles );
561 }
562 }
563 }
564
565
566 /**
567 * @param string $location
568 * @param integer $template_id
569 */
570 public function add_template_to_location( string $location, int $template_id ) {
571 if ( isset( $this->locations_skipped[ $location ][ $template_id ] ) ) {
572 return;
573 }
574
575 if ( ! isset( $this->locations_queue[ $location ] ) ) {
576 $this->locations_queue[ $location ] = [];
577 }
578
579 $this->locations_queue[ $location ][ $template_id ] = $template_id;
580 }
581
582 public function is_printed( $location, $template_id ): bool {
583 return isset( $this->locations_printed[ $location ][ $template_id ] );
584 }
585
586 public function skip_template_from_location( $location, $template_id ) {
587 $this->remove_template_from_location( $location, $template_id );
588
589 if ( ! isset( $this->locations_skipped[ $location ] ) ) {
590 $this->locations_skipped[ $location ] = [];
591 }
592
593 $this->locations_skipped[ $location ][ $template_id ] = $template_id;
594 }
595
596 public function remove_template_from_location( $location, $template_id ) {
597 unset( $this->locations_queue[ $location ][ $template_id ] );
598 }
599
600 public function set_is_printed( $location, $template_id ) {
601 if ( ! isset( $this->locations_printed[ $location ] ) ) {
602 $this->locations_printed[ $location ] = [];
603 }
604
605 $this->locations_printed[ $location ][ $template_id ] = $template_id;
606 $this->remove_template_from_location( $location, $template_id );
607 }
608
609 /**
610 * Clear the header styles cache for a specific post.
611 *
612 * @param int $post_id Post ID.
613 * @return void
614 */
615 public function clear_header_styles_cache( $post_id ) {
616 // Only clear if it might be a header template.
617 // Since we don't strictly know if it IS a header without checking metadata (which might be what's changing),
618 // we just clear the specific transient for this ID. It's harmless if the transient doesn't exist.
619 delete_transient( 'templately_header_styles_' . $post_id );
620 }
621 }