PluginProbe
BlockSpare – Gutenberg Blocks, AI Content Generator & Site Builder for News, Magazine & Blogs / 5.0.1
BlockSpare – Gutenberg Blocks, AI Content Generator & Site Builder for News, Magazine & Blogs v5.0.1
5.0.1 4.1.0 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.1.0 1.1.1 1.1.2 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 2.1.2 2.5.0 2.5.3 All 39 releases
blockspare / inc / theme-builder / class-blockspare-tb-template-loader.php

class-blockspare-tb-template-loader.php in BlockSpare – Gutenberg Blocks, AI Content Generator & Site Builder for News, Magazine & Blogs 5.0.1, at inc/theme-builder/class-blockspare-tb-template-loader.php

313 lines 8.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 defined('ABSPATH') || exit;
4
5
6 class Blockspare_TB_Template_Loader
7 {
8
9 /**
10 * @var Blockspare_TB_Condition_Registry
11 */
12 private $conditions;
13
14 /**
15 * @param Blockspare_TB_Condition_Registry $conditions Shared condition registry.
16 */
17 public function __construct(Blockspare_TB_Condition_Registry $conditions)
18 {
19 $this->conditions = $conditions;
20 }
21
22
23 public function init()
24 {
25 add_filter('template_include', array($this, 'maybe_override_page_template'));
26 add_action('get_header', array($this, 'maybe_render_header'), 1);
27 add_action('get_footer', array($this, 'maybe_render_footer'), 1);
28
29 // Block/FSE themes: intercept the core/template-part block itself.
30 add_filter('render_block_core/template-part', array($this, 'maybe_override_template_part'), 10, 2);
31 }
32
33
34 public function maybe_override_template_part($block_content, $block)
35 {
36 $slug = isset($block['attrs']['slug']) ? $block['attrs']['slug'] : '';
37
38 if (! in_array($slug, array('header', 'footer'), true)) {
39 return $block_content;
40 }
41
42 $post = $this->find_matching_template($slug);
43
44 if (null === $post) {
45 return $block_content; // No match — leave the theme's own template part untouched.
46 }
47
48 // Prefer the original block's own tagName (themes sometimes set
49 // this explicitly), falling back to 'header'/'footer' — which
50 // conveniently match our taxonomy slugs directly.
51 $tag = (! empty($block['attrs']['tagName']) && is_string($block['attrs']['tagName']))
52 ? $block['attrs']['tagName']
53 : $slug;
54
55 $tag = tag_escape($tag);
56 if (empty($tag)) {
57 $tag = $slug;
58 }
59
60 return sprintf(
61 '<%1$s class="wp-block-template-part">%2$s</%1$s>',
62 $tag,
63 do_blocks($post->post_content) // phpcs:ignore WordPress.Security.EscapeOutput -- do_blocks() output is core-sanitized block content.
64 );
65 }
66
67 /**
68 * Overrides the resolved page template for single/archive/404/search/front-page
69 * template types, if a matching custom template exists.
70 *
71 * @param string $template Absolute path to the template PHP file WP resolved.
72 * @return string
73 */
74 public function maybe_override_page_template($template)
75 {
76 $type = $this->resolve_current_page_type();
77
78 if (null === $type) {
79 return $template;
80 }
81
82 $post = $this->find_matching_template($type);
83
84 if (null === $post) {
85 return $template;
86 }
87
88 // Stash the matched post so our neutral wrapper template can render it,
89 // without relying on global state elsewhere in the request lifecycle.
90 global $stbldr_matched_template;
91 $stbldr_matched_template = $post;
92
93 return BLOCKSPARE_TB_PATH . 'views/page-wrapper.php';
94 }
95
96 /**
97 * Renders a custom header if one matches, and tells WP to skip the theme's own.
98 */
99 public function maybe_render_header()
100 {
101 $this->render_partial_if_matched('header');
102 }
103
104 /**
105 * Renders a custom footer if one matches, and tells WP to skip the theme's own.
106 */
107 public function maybe_render_footer()
108 {
109 $this->render_partial_if_matched('footer');
110 }
111
112 /**
113 * Shared logic for header/footer: find a match, render it, and prevent
114 * the theme's own header.php/footer.php from ALSO rendering.
115 *
116 * @param string $type 'header' or 'footer'.
117 */
118 private function render_partial_if_matched($type)
119 {
120 $post = $this->find_matching_template($type);
121
122 if (null === $post) {
123 return; // No match — let the theme's own header/footer render normally.
124 }
125
126
127 $handled_precisely = $this->suppress_known_theme_chrome($type);
128
129 if ('footer' === $type) {
130
131 add_action(
132 'wp_footer',
133 function () use ($post) {
134 echo '<!-- blocksparetb:footer start -->';
135 echo do_blocks($post->post_content); // phpcs:ignore WordPress.Security.EscapeOutput -- do_blocks() output is core-sanitized block content.
136 echo '<!-- blocksparetb:footer end -->';
137 }
138 );
139 } else {
140
141 echo '<!-- blocksparetb:header start -->';
142 echo do_blocks($post->post_content); // phpcs:ignore WordPress.Security.EscapeOutput -- do_blocks() output is core-sanitized block content.
143 echo '<!-- blocksparetb:header end -->';
144 }
145
146 if ($handled_precisely) {
147 return;
148 }
149
150 add_filter(
151 'locate_template',
152 function () use ($type) {
153 return 'footer' === $type
154 ? BLOCKSPARE_TB_PATH . 'views/empty-footer.php'
155 : BLOCKSPARE_TB_PATH . 'views/empty.php';
156 },
157 999
158 );
159
160
161 add_action('wp_head', array($this, 'print_legacy_theme_hide_css'), 999);
162 }
163
164
165 private function suppress_known_theme_chrome($type)
166 {
167 // get_template() returns the PARENT theme's directory slug, so this
168 // correctly detects Astra even when a child theme is active.
169 $theme = get_template();
170
171 if ('astra' !== $theme) {
172 return false;
173 }
174
175 if ('header' === $type) {
176 // Astra's older "Legacy" header.
177 remove_action('astra_masthead', 'astra_masthead_primary_template');
178
179 if (class_exists('Astra_Builder_Header')) {
180 $header_builder = Astra_Builder_Header::get_instance();
181 remove_action('astra_primary_header', array($header_builder, 'primary_header'));
182 remove_action('astra_mobile_primary_header', array($header_builder, 'mobile_primary_header'));
183 }
184 remove_action('astra_header', 'astra_header_markup');
185 } elseif ('footer' === $type) {
186 remove_action('astra_footer', 'astra_footer_markup');
187 if (class_exists('Astra_Builder_Footer')) {
188 remove_action('astra_footer', array(Astra_Builder_Footer::get_instance(), 'footer_markup'));
189 }
190 }
191
192
193 return true;
194 }
195
196
197 public function print_legacy_theme_hide_css()
198 {
199
200 ?>
201 <style id="blocksparetb-legacy-theme-hide">
202 body>header:not(.blocksparetb-content),
203 body>.site-header,
204 body>#masthead,
205 body>#header,
206 body>footer:not(.blocksparetb-content),
207 body>.site-footer,
208 body>#colophon,
209 body>#footer {
210 display: none !important;
211 }
212 </style>
213 <?php
214 }
215
216 private function resolve_current_page_type()
217 {
218 if (is_404()) {
219 return array('404');
220 }
221 if (is_search()) {
222 return array('search');
223 }
224 if (is_category()) {
225 return array('category', 'archive');
226 }
227 if (is_tag()) {
228 return array('tag', 'archive');
229 }
230 if (is_author()) {
231 return array('author', 'archive');
232 }
233 if (is_date()) {
234 return array('date', 'archive');
235 }
236 if (is_front_page()) {
237 // Check 'front_page' first, but fall back to 'archive'
238 return array('front_page', 'archive');
239 }
240 if (is_archive() || is_home()) {
241 return array('archive');
242 }
243 if (is_singular()) {
244 return array('single');
245 }
246
247 return null;
248 }
249
250
251 private function find_matching_template($type)
252 {
253 if (empty($type)) {
254 return null;
255 }
256
257 $terms = (array) $type;
258
259 $candidates = get_posts(
260 array(
261 'post_type' => Blockspare_TB_Template_Post_Type::SLUG,
262 'post_status' => 'publish',
263 'posts_per_page' => -1,
264 'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
265 array(
266 'taxonomy' => Blockspare_TB_Template_Type_Taxonomy::SLUG,
267 'field' => 'slug',
268 'terms' => $terms,
269 ),
270 ),
271 )
272 );
273
274 usort(
275 $candidates,
276 function ($a, $b) {
277 $priority_a = self::get_priority($a->ID);
278 $priority_b = self::get_priority($b->ID);
279 return $priority_a - $priority_b; // Lower number = higher priority.
280 }
281 );
282
283 foreach ($candidates as $candidate) {
284 $raw = get_post_meta($candidate->ID, '_stbldr_conditions', true);
285 $ruleset = $raw ? json_decode($raw, true) : array();
286
287 if (! is_array($ruleset)) {
288 continue;
289 }
290
291 if ($this->conditions->matches($ruleset)) {
292 return $candidate;
293 }
294 }
295
296 return null;
297 }
298
299 /**
300 * Reads a template's priority, defaulting to 10 when the meta row
301 * doesn't exist yet (mirrors the default shown in the REST `priority`
302 * field and the editor's Priority control).
303 *
304 * @param int $post_id Template post ID.
305 * @return int
306 */
307 private static function get_priority($post_id)
308 {
309 $value = get_post_meta($post_id, '_stbldr_priority', true);
310 return '' === $value ? 10 : (int) $value;
311 }
312 }
313