PluginProbe ʕ •ᴥ•ʔ
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More / 3.7.1
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More v3.7.1
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 trunk 1.0.0 2.0.0 2.0.1 2.0.2 2.0.3 3.0 3.0.1 3.0.2 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.2.0 3.2.1 3.2.2 3.2.4 3.2.5 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.4.5 3.4.6 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.6 3.5.7 3.5.8 3.5.9 3.6.0 3.6.1 3.6.2 3.7.0 3.7.1
superb-blocks / src / admin / controllers / wizard / class-wizard-template-preview-controller.php
superb-blocks / src / admin / controllers / wizard Last commit date
class-wizard-controller.php 5 months ago class-wizard-restorationpoint-controller.php 5 months ago class-wizard-template-preview-controller.php 5 months ago
class-wizard-template-preview-controller.php
394 lines
1 <?php
2
3 namespace SuperbAddons\Admin\Controllers\Wizard;
4
5 use SuperbAddons\Config\Capabilities;
6 use SuperbAddons\Data\Controllers\CacheController;
7 use SuperbAddons\Data\Utils\AllowedTemplateHTMLUtil;
8 use SuperbAddons\Data\Utils\CacheTypes;
9 use SuperbAddons\Data\Utils\GutenbergCache;
10 use SuperbAddons\Data\Utils\Wizard\AddonsPageTemplateUtil;
11 use SuperbAddons\Data\Utils\Wizard\WizardItemIdAffix;
12 use SuperbAddons\Data\Utils\Wizard\WizardItemTypes;
13 use WP_Query;
14
15 defined('ABSPATH') || exit();
16
17 class WizardTemplatePreviewController
18 {
19 const TEMPLATE_PREVIEW_KEY = 'superbaddons-template-preview';
20 const TEMPLATE_TYPE_KEY = 'superbaddons-template-type';
21 const USE_PAGE_TEMPLATE_KEY = 'superbaddons-template-custom';
22 const TEMPLATE_PART_PREVIEW_TRANSIENT = 'superbaddons_template_part_preview';
23
24 const TEMPLATE_PREVIEW_NONCE = 'superbaddons-template-preview-nonce';
25 const TEMPLATE_PREVIEW_NONCE_ACTION = 'superbaddons-template-preview-action';
26
27 public static function InitializeTemplatePreview()
28 {
29 if (is_admin()) {
30 return;
31 }
32
33 add_action('wp_loaded', function () {
34 if (!isset($_GET[self::TEMPLATE_PREVIEW_KEY]) || !isset($_GET[self::TEMPLATE_TYPE_KEY]) || !isset($_GET[self::USE_PAGE_TEMPLATE_KEY]) || !isset($_GET[self::TEMPLATE_PREVIEW_NONCE])) {
35 return;
36 }
37 if (!wp_verify_nonce(sanitize_text_field(wp_unslash($_GET[self::TEMPLATE_PREVIEW_NONCE])), self::TEMPLATE_PREVIEW_NONCE_ACTION)) {
38 return;
39 }
40 if (!current_user_can(Capabilities::ADMIN)) {
41 return;
42 }
43 $template_id = sanitize_text_field(wp_unslash($_GET[self::TEMPLATE_PREVIEW_KEY]));
44 $template_type = sanitize_text_field(wp_unslash($_GET[self::TEMPLATE_TYPE_KEY]));
45 $template_custom = $_GET[self::USE_PAGE_TEMPLATE_KEY] == 1;
46 show_admin_bar(false);
47 switch ($template_type) {
48 case WizardItemTypes::WP_TEMPLATE:
49 self::SetPlaceholderFilters($template_id, $template_type);
50 self::PreviewBlockTemplate($template_id, $template_type, $template_custom);
51 exit();
52 case WizardItemTypes::PATTERN:
53 case WizardItemTypes::PAGE:
54 self::SetPlaceholderFilters($template_id, $template_type);
55 self::PreviewSuperbTemplate($template_id, $template_type, $template_custom);
56 exit();
57 case WizardItemTypes::WP_TEMPLATE_PART:
58 self::PreviewBlockTemplate($template_id, $template_type, $template_custom);
59 exit();
60 default:
61 // Static preview
62 break;
63 }
64 });
65 }
66
67 private static function SetPlaceholderFilters($template_id, $template_type)
68 {
69 if ($template_type == WizardItemTypes::WP_TEMPLATE_PART || $template_type == WizardItemTypes::PATTERN) {
70 // Enqueue part preview specific styles
71 wp_enqueue_style('superbaddons-wizard-part-preview-styles', SUPERBADDONS_ASSETS_PATH . '/css/page-wizard-preview.min.css', array(), SUPERBADDONS_VERSION);
72 }
73 // Filter the header and footer parts to show a placeholder image instead of the actual content if a preview is requested instead of default theme header/footer.
74 self::FilterHeaderFooterPreviewParts();
75
76 // Set the post ID to 0 to prevent the preview from showing the actual content of the page and to prevent Undefined index: postId in /wp-includes/blocks/comments.php on line 31
77 add_filter("render_block_context", function ($context) {
78 if (isset($context['postId'])) {
79 return $context;
80 }
81 $context['postId'] = 0;
82 return $context;
83 });
84
85 add_filter('the_title', function ($title) use ($template_type, $template_id) {
86 if (!empty($title)) {
87 return $title;
88 }
89 if ($template_type === WizardItemTypes::WP_TEMPLATE) {
90 $template = get_block_template($template_id, WizardItemTypes::WP_TEMPLATE);
91 if ($template) {
92 return $template->title;
93 }
94 }
95
96 return esc_html__("Placeholder Title", "superb-blocks");
97 });
98
99 // Replace the post content with a placeholder content for preview
100 add_filter("the_content", function ($content) {
101 return sprintf('<!-- wp:heading {"level":1,"style":{"spacing":{"padding":{"top":"50px","bottom":"50px","left":"50px","right":"50px"}}}} --> <h1 class="wp-block-heading" style="padding-top:50px;padding-right:50px;padding-bottom:50px;padding-left:50px">%s</h1> <!-- /wp:heading -->', esc_html__("This is a preview of the template. Any page content that you create will appear here.", "superb-blocks"));
102 }, 10);
103
104 // Replace the posts with a placeholder title and placeholder content for preview
105 add_filter('the_posts', function ($posts, $query) {
106 if (!isset($query->query)) {
107 return $posts;
108 }
109
110 if (isset($query->query['post_type']) && $query->query['post_type'] !== 'post') {
111 return $posts;
112 }
113
114 $amount = isset($query->query['posts_per_page']) ? $query->query['posts_per_page'] : 3;
115 $amount = ($amount === -1) ? 3 : min($amount, 10);
116
117 $placeholder_posts = array();
118 for ($i = 0; $i < $amount; $i++) {
119 // Create a placeholder post
120 $placeholder_posts[] = (object)array(
121 'post_title' => esc_html__("Placeholder Post", "superb-blocks") . " " . ($i + 1),
122 'post_content' => '',
123 'post_status' => 'publish',
124 'post_type' => 'post',
125 'post_author' => 1,
126 'post_date' => gmdate('Y-m-d H:i:s'),
127 );
128 }
129 return $placeholder_posts;
130 }, 10, 2);
131
132 // Replace the post thumbnail with a placeholder for preview
133 add_filter("post_thumbnail_html", function () {
134 return self::GetPlaceholderPreviewImage();
135 }, 0, 0);
136
137 // Replace featured image in "Featured Image" blocks with a placeholder for preview
138 add_filter("render_block", function ($block_content, $block) {
139 if (isset($block['blockName']) && $block['blockName'] === 'core/post-featured-image' && empty($block_content)) {
140 return '<figure class="alignwide wp-block-post-featured-image">' . self::GetPlaceholderPreviewImage() . '</figure>';
141 }
142 return $block_content;
143 }, 10, 2);
144
145 // Start global wp_query query to make sure placeholder posts are shown.
146 global $wp_query;
147 if (!$wp_query || !$wp_query instanceof WP_Query) {
148 return;
149 }
150 $wp_query = new WP_Query([
151 'post_type' => 'post',
152 ]);
153 }
154
155 private static function GetPlaceholderPreviewImage()
156 {
157 return '<img width="1900" height="1267" src="' . esc_url(SUPERBADDONS_ASSETS_PATH . "/img/placeholder-image.svg") . '" class="attachment-post-thumbnail size-post-thumbnail wp-post-image" style="height:100%;object-fit:cover;">';
158 }
159
160 private static function PreviewBlockTemplate($template_id, $template_type = WizardItemTypes::WP_TEMPLATE)
161 {
162 if (strpos($template_id, WizardItemIdAffix::FILE_TEMPLATE) !== false) {
163 // If the template is a file template, get the file template preview.
164 $template_id = str_replace(WizardItemIdAffix::FILE_TEMPLATE, '', $template_id);
165 self::PreviewBlockFileTemplate($template_id, $template_type);
166 return;
167 }
168
169 if (strpos($template_id, WizardItemIdAffix::RESTORATION_POINT) !== false) {
170 // If the template is a restoration point, get the restoration point preview.
171 $template_id = str_replace(WizardItemIdAffix::RESTORATION_POINT, '', $template_id);
172 self::PreviewRestorationPoint($template_id);
173 return;
174 }
175
176 $template = get_block_template($template_id, $template_type);
177 if (!$template || $template->type !== $template_type || $template->status !== 'publish' || $template->theme !== get_stylesheet() || empty($template->content)) {
178 return;
179 }
180
181 $template_html = self::GetTheTemplateHTML($template->content);
182
183 self::RenderTemplateCanvas($template_html);
184 }
185
186 private static function PreviewBlockFileTemplate($template_id, $template_type)
187 {
188 $template = get_block_file_template($template_id, $template_type);
189 if ($template->theme !== get_stylesheet() || empty($template->content)) {
190 return;
191 }
192
193 $template_html = self::GetTheTemplateHTML($template->content);
194
195 self::RenderTemplateCanvas($template_html);
196 }
197
198 private static function PreviewRestorationPoint($template_id)
199 {
200 $restoration_point = WizardRestorationPointController::GetTemplateRestorationPoint($template_id);
201 if (!$restoration_point) return;
202
203 $template_html = self::GetTheTemplateHTML($restoration_point['content']);
204
205 self::RenderTemplateCanvas($template_html);
206 }
207
208 private static function PreviewSuperbTemplate($template_id, $template_type = WizardItemTypes::PAGE, $template_custom = false)
209 {
210 //// Get the preview URL from the cache by page id.
211 switch ($template_type) {
212 case WizardItemTypes::PAGE:
213 $cache_type = GutenbergCache::PAGES;
214 break;
215 case WizardItemTypes::PATTERN:
216 $cache_type = GutenbergCache::PATTERNS;
217 break;
218 default:
219 return;
220 }
221 // The cache has already been set, otherwise the preview will not work and we shouldn't be here.
222 $cache = CacheController::GetCache($cache_type, CacheTypes::GUTENBERG);
223 if (!$cache || !isset($cache->items)) return;
224 $preview_url = false;
225 foreach ($cache->items as $template) {
226 if ($template->id === $template_id) {
227 $preview_url = $template->preview;
228 break;
229 }
230 }
231
232 if (!$preview_url) return;
233
234 $preview_content = '<img src="' . esc_url($preview_url) . '" class="superbaddons-preview-image" width="100%" height="auto" style="user-select:none;">';
235
236 if ($template_type === WizardItemTypes::PAGE) {
237 // Override the content with a placeholder content for preview
238 if ($template_custom) {
239 add_filter("the_content", function () use ($preview_content) {
240 return $preview_content;
241 }, PHP_INT_MAX);
242 $template = get_block_template(get_stylesheet() . '//' . AddonsPageTemplateUtil::TEMPLATE_ID, WizardItemTypes::WP_TEMPLATE);
243 $content = $template->content;
244 } else {
245 $content = AddonsPageTemplateUtil::GetAddonsPageTemplateContent($preview_content);
246 }
247 } else {
248 // Preview pattern only
249 $content = $preview_content;
250 }
251
252 $template_html = self::GetTheTemplateHTML($content);
253
254 self::RenderTemplateCanvas($template_html);
255 }
256
257 private static function FilterHeaderFooterPreviewParts()
258 {
259 // track which areas are currently being processed to prevent infinite loops when a template part contains nested template parts
260 $processing_areas = [];
261
262 add_filter('render_block', function ($block_content, $block) use (&$processing_areas) {
263 if ($block['blockName'] !== 'core/template-part') {
264 return $block_content;
265 }
266
267 $attrs = $block['attrs'];
268 $template_id = ($attrs['theme'] ?? get_stylesheet()) . '//' . $attrs['slug'];
269 $template = get_block_template($template_id, 'wp_template_part');
270
271 if (!$template || ($template->area !== 'header' && $template->area !== 'footer')) {
272 return $block_content;
273 }
274
275 // Prevent infinite loop for nested template parts: if we're already processing this area, return original content
276 if (isset($processing_areas[$template->area])) {
277 return $block_content;
278 }
279
280 $template_part_preview = WizardController::GetPartPreviewTransient();
281 if (!$template_part_preview || !isset($template_part_preview[$template->area])) {
282 return $block_content;
283 }
284
285 // Mark this area as being processed
286 $processing_areas[$template->area] = true;
287
288 // Get the preview content
289 $preview_content = WizardTemplatePreviewController::GetTemplatePartPreview($template->area);
290
291 // Unmark this area as being processed
292 unset($processing_areas[$template->area]);
293
294 if (!$preview_content) {
295 return $block_content;
296 }
297
298 return $preview_content;
299 }, 10, 2);
300 }
301
302 private static function GetTemplatePartPreview($slug)
303 {
304 $template_part_preview = WizardController::GetPartPreviewTransient();
305 if (!$template_part_preview || !isset($template_part_preview[$slug])) {
306 return false;
307 }
308
309 // Check if it's a file template preview
310 if (strpos($template_part_preview[$slug], WizardItemIdAffix::FILE_TEMPLATE) !== false) {
311 $template_id = get_stylesheet() . '//' . str_replace(WizardItemIdAffix::FILE_TEMPLATE, '', $template_part_preview[$slug]);
312 $template = get_block_file_template($template_id, WizardItemTypes::WP_TEMPLATE_PART);
313 return self::GetTheTemplateHTML($template->content);
314 }
315
316 // Check if it's a restoration point preview
317 if (strpos($template_part_preview[$slug], WizardItemIdAffix::RESTORATION_POINT) !== false) {
318 $restoration_id = str_replace(WizardItemIdAffix::RESTORATION_POINT, '', $template_part_preview[$slug]);
319 $restoration_point = WizardRestorationPointController::GetTemplateRestorationPoint($restoration_id);
320 if (!$restoration_point) return false;
321
322 return self::GetTheTemplateHTML($restoration_point['content']);
323 }
324
325 // Avoid further handling of theme slug templates
326 if (strpos($slug, get_stylesheet()) !== false) {
327 return false;
328 }
329
330 // Try to get as file template first
331 $template_id = get_stylesheet() . '//' . $template_part_preview[$slug];
332 $template = get_block_file_template($template_id, WizardItemTypes::WP_TEMPLATE_PART);
333 if ($template && !empty($template->content)) {
334 return self::GetTheTemplateHTML($template->content);
335 }
336
337 // Try regular template next
338 $template = get_block_template($template_id, WizardItemTypes::WP_TEMPLATE_PART);
339 if ($template && !empty($template->content)) {
340 return self::GetTheTemplateHTML($template->content);
341 }
342
343 // If not a file template, check patterns cache
344 $cache = CacheController::GetCache(GutenbergCache::PATTERNS, CacheTypes::GUTENBERG);
345 if (!$cache || !isset($cache->items)) return false;
346
347 $preview_url = false;
348 foreach ($cache->items as $template) {
349 if ($template->id === $template_part_preview[$slug]) {
350 $preview_url = $template->preview;
351 break;
352 }
353 }
354
355 if (!$preview_url) return false;
356
357 return '<img src="' . esc_url($preview_url) . '" class="superbaddons-preview-image" width="100%" height="auto" style="user-select:none;">';
358 }
359
360 private static function GetTheTemplateHTML($content)
361 {
362 global $_wp_current_template_content;
363 // Variable is not defined by plugin, but is a global WP variable.
364 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
365 $_wp_current_template_content = $content;
366 return get_the_block_template_html();
367 }
368
369 private static function RenderTemplateCanvas($content)
370 {
371
372 ?>
373 <!DOCTYPE html>
374 <html <?php language_attributes(); ?>>
375
376 <head>
377 <meta charset="<?php bloginfo('charset'); ?>" />
378 <meta name="viewport" content="width=device-width, initial-scale=1" />
379 <?php wp_head(); ?>
380 </head>
381
382 <body <?php body_class(); ?>>
383 <?php wp_body_open(); ?>
384 <?php AllowedTemplateHTMLUtil::enable_safe_styles(); ?>
385 <?php echo wp_kses($content, "post"); ?>
386 <?php AllowedTemplateHTMLUtil::disable_safe_styles(); ?>
387 <?php wp_footer(); ?>
388 </body>
389
390 </html>
391 <?php
392 }
393 }
394