PluginProbe ʕ •ᴥ•ʔ
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More / 4.0.7
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More v4.0.7
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 / data / utils / wizard / class-wizard-template-provider.php
superb-blocks / src / data / utils / wizard Last commit date
wizard-items 5 days ago class-wizard-constants.php 5 days ago class-wizard-creation-util.php 5 days ago class-wizard-exception.php 5 days ago class-wizard-menu-creator.php 5 days ago class-wizard-page-creator.php 5 days ago class-wizard-part-creator.php 5 days ago class-wizard-stage-util.php 5 days ago class-wizard-template-provider.php 5 days ago
class-wizard-template-provider.php
547 lines
1 <?php
2
3 namespace SuperbAddons\Data\Utils\Wizard;
4
5 use SuperbAddons\Admin\Controllers\Wizard\WizardRestorationPointController;
6 use SuperbAddons\Data\Controllers\KeyController;
7 use SuperbAddons\Data\Controllers\RestController;
8 use SuperbAddons\Library\Controllers\LibraryRequestController;
9
10 use WP_REST_Request;
11
12 defined('ABSPATH') || exit();
13
14 class WizardTemplateProvider
15 {
16 private $headerTemplates;
17 private $footerTemplates;
18 private $blogTemplates;
19 private $homeTemplates;
20 private $indexTemplates;
21 private $frontPageTemplates;
22 private $templatePages;
23
24 private $hasFrontPageTemplate;
25 private $hasHomeTemplate;
26 private $hasIndexTemplate;
27
28 private $totalHeaderParts;
29 private $totalFooterParts;
30
31 private $userIsPremium;
32
33 public function __construct()
34 {
35 $this->headerTemplates = array();
36 $this->footerTemplates = array();
37 $this->blogTemplates = array();
38 $this->homeTemplates = array();
39 $this->indexTemplates = array();
40 $this->frontPageTemplates = array();
41 $this->templatePages = array();
42
43 $this->totalHeaderParts = 0;
44 $this->totalFooterParts = 0;
45
46 $this->hasFrontPageTemplate = false;
47 $this->hasHomeTemplate = false;
48 $this->hasIndexTemplate = false;
49 $this->userIsPremium = KeyController::HasValidPremiumKey();
50 }
51
52 private static function NormalizeCategoryName($name)
53 {
54 return strtolower(preg_replace('/[^a-zA-Z0-9]/', '', (string) $name));
55 }
56
57 private static function BuildCategoryMap($categories)
58 {
59 $map = array();
60 if (!is_array($categories) && !is_object($categories)) {
61 return $map;
62 }
63 foreach ($categories as $cat) {
64 if (isset($cat->id) && isset($cat->name)) {
65 $map[$cat->id] = self::NormalizeCategoryName($cat->name);
66 }
67 }
68 return $map;
69 }
70
71 private static function ResolveCategoryKey($item, $category_map)
72 {
73 if (!isset($item->category)) {
74 return '';
75 }
76 $cat_id = is_object($item->category) ? (isset($item->category->id) ? $item->category->id : '') : $item->category;
77 if (isset($category_map[$cat_id])) {
78 return $category_map[$cat_id];
79 }
80 // Fallback: the value itself may already be a slug/name rather than an opaque id
81 return self::NormalizeCategoryName($cat_id);
82 }
83
84 private function HasModifiedTemplate($slug, $template_type, $area = false)
85 {
86 $args = array(
87 'post_type' => $template_type,
88 'posts_per_page' => 1,
89 'post_name__in' => array($slug),
90 // Ensure we only get template parts for the current theme -> Tax query is used like in WP core
91 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
92 'tax_query' => array(
93 array(
94 'taxonomy' => 'wp_theme',
95 'field' => 'name',
96 'terms' => get_stylesheet()
97 )
98 )
99 );
100 if ($template_type === WizardItemTypes::WP_TEMPLATE_PART) {
101 $args['tax_query'][] = array(
102 'taxonomy' => 'wp_template_part_area',
103 'field' => 'name',
104 'terms' => $area ? $area : $slug
105 );
106 $args['tax_query']['relation'] = 'AND';
107 }
108
109 $modifiedTemplatePost = get_posts($args);
110
111 return $modifiedTemplatePost && !empty($modifiedTemplatePost) && $modifiedTemplatePost[0]->post_name === $slug;
112 }
113
114 private function InitializePart($slugOrWizardItem, $type, $area, $regularTitle, $modifiedTitle, $baseTitle)
115 {
116 if ($slugOrWizardItem instanceof WizarditemRestorationPoint) {
117 $this->AddToAppropriateArray($slugOrWizardItem, $area);
118 return;
119 }
120
121 $datatype = false;
122 if ($slugOrWizardItem instanceof WizardItem) {
123 $datatype = $slugOrWizardItem->datatype;
124 $slug = $slugOrWizardItem->GetBaseSlug();
125 } else {
126 $slug = $slugOrWizardItem;
127 }
128
129 $partTemplate = get_block_template(get_stylesheet() . '//' . $slug, $type);
130 if (!$partTemplate) {
131 return;
132 }
133
134 $hasModifiedPart = $this->HasModifiedTemplate($slug, $type, $area);
135 if ($partTemplate) {
136 $partItem = new WizardItemTemplate($partTemplate, $regularTitle, $modifiedTitle, $hasModifiedPart);
137 if ($datatype) {
138 $partItem->datatype = $datatype;
139 }
140 if ($area === 'header' && !has_block('core/navigation', $partTemplate->content)) {
141 $partItem->is_missing_navigation_block = true;
142 }
143 }
144
145 if ($hasModifiedPart) {
146 if ($area === $slug) {
147 // To avoid clutter, we don't need to add the modified part for all variants
148 $this->AddToAppropriateArray($partItem, $area);
149 }
150
151 $themePartTemplate = get_block_file_template(get_stylesheet() . '//' . $slug, $type);
152 if (!$themePartTemplate) {
153 // No theme part exists, add the modified part -> likely a custom part
154 $partItem->title = $baseTitle . __(" (Custom)", "superb-blocks");
155 $this->AddToAppropriateArray($partItem, $area);
156 return;
157 }
158 // Get the original part template from the theme file
159 $themePartItem = new WizardItemFile($slug, $type, $regularTitle);
160 if ($datatype) {
161 $themePartItem->datatype = $datatype;
162 }
163
164 if ($type === WizardItemTypes::WP_TEMPLATE_PART && $area === 'header') {
165 if (!has_block('core/navigation', $themePartTemplate->content)) {
166 $themePartItem->is_missing_navigation_block = true;
167 }
168 }
169 $this->AddToAppropriateArray($themePartItem, $area);
170 return;
171 }
172
173
174 // No modified part exists, add the block template part
175 $this->AddToAppropriateArray($partItem, $area);
176 }
177
178 public function InitializePatterns($required_plugin = false)
179 {
180 // Get all block template parts
181 $templates = get_block_templates([], 'wp_template_part');
182
183 $this->totalHeaderParts = 0;
184 $this->totalFooterParts = 0;
185 // Filter for header/footer patterns and collect template parts
186 foreach ($templates as $template) {
187 if (!isset($template->area)) continue;
188 // Skip parts registered by plugins (e.g. WooCommerce's checkout header) so they don't count toward theme part totals
189 if (!isset($template->theme) || $template->theme !== get_stylesheet()) continue;
190
191 $regularTitleAffix = isset($template->source) && $template->source === 'theme' ? __(" (Theme)", "superb-blocks") : "";
192 if ($template->area === 'header') {
193 $this->InitializePart($template->slug, WizardItemTypes::WP_TEMPLATE_PART, 'header', $template->title . $regularTitleAffix, $template->title . __(" (Current)", "superb-blocks"), $template->title);
194 $this->totalHeaderParts++;
195 }
196 if ($template->area === 'footer') {
197 $this->InitializePart($template->slug, WizardItemTypes::WP_TEMPLATE_PART, 'footer', $template->title . $regularTitleAffix, $template->title . __(" (Current)", "superb-blocks"), $template->title);
198 $this->totalFooterParts++;
199 }
200 }
201
202 try {
203 $request = new WP_REST_Request('GET', '/' . RestController::NAMESPACE . LibraryRequestController::GUTENBERG_V2_LIST_ROUTE);
204 $library_response = rest_do_request($request);
205 if (!$library_response || is_wp_error($library_response) || $library_response->is_error() || !isset($library_response->data) || !isset($library_response->data->patterns) || !isset($library_response->data->patterns->items)) {
206 throw new WizardException(__("An error occurred while fetching available patterns.", "superb-blocks"));
207 }
208
209 $patterns_items = $library_response->data->patterns->items;
210
211 // Order free to the top of list array if the user is not premium
212 if (!$this->userIsPremium) {
213 $freePatterns = array();
214 $premiumPatterns = array();
215 foreach ($patterns_items as $pattern) {
216 if (isset($pattern->package) && $pattern->package === 'premium') {
217 $premiumPatterns[] = $pattern;
218 } else {
219 $freePatterns[] = $pattern;
220 }
221 }
222 $patterns_items = array_merge($freePatterns, $premiumPatterns);
223 }
224 $category_map = self::BuildCategoryMap(isset($library_response->data->patterns->categories) ? $library_response->data->patterns->categories : null);
225 // Get header and footer templates from patterns
226 foreach ($patterns_items as $pattern) {
227 if ($required_plugin && !isset($pattern->required_plugins) || $required_plugin && !in_array($required_plugin, $pattern->required_plugins)) {
228 continue;
229 }
230 $patternItem = new WizardItemPattern($pattern);
231 $cat_key = self::ResolveCategoryKey($pattern, $category_map);
232 if ($cat_key === WizardDataCategory::NAVIGATION) {
233 $this->headerTemplates[] = $patternItem;
234 } else if ($cat_key === WizardDataCategory::FOOTER) {
235 $this->footerTemplates[] = $patternItem;
236 }
237 }
238
239 // 'header' slug should always be first in the header templates
240 if (!empty($this->headerTemplates) && count($this->headerTemplates) > 1) {
241 usort($this->headerTemplates, function ($a, $b) {
242 if ($a->GetBaseSlug() === 'header') {
243 return -1;
244 }
245 if ($b->GetBaseSlug() === 'header') {
246 return 1;
247 }
248 return 0;
249 });
250 }
251 // 'footer' slug should always be first in the footer templates
252 if (!empty($this->footerTemplates) && count($this->footerTemplates) > 1) {
253 usort($this->footerTemplates, function ($a, $b) {
254 if ($a->GetBaseSlug() === 'footer') {
255 return -1;
256 }
257 if ($b->GetBaseSlug() === 'footer') {
258 return 1;
259 }
260 return 0;
261 });
262 }
263 } catch (WizardException $e) {
264 // Exception is automatically logged. Catch it so that the wizard can continue with the available theme templates.
265 }
266 }
267
268 public function InitalizePageTemplates()
269 {
270 $acceptedTemplates = $this->GetAcceptedTemplates();
271
272 // Initial sort templates by slug
273 $this->SortTemplatesIntoArraysBySlug($acceptedTemplates);
274
275 // Additional template logic
276 $this->HandleTemplateLogic();
277
278 // Add addons service templates
279 try {
280 $request = new WP_REST_Request('GET', '/' . RestController::NAMESPACE . LibraryRequestController::GUTENBERG_V2_LIST_ROUTE);
281 $library_response = rest_do_request($request);
282 if (!$library_response || is_wp_error($library_response) || $library_response->is_error() || !isset($library_response->data) || !isset($library_response->data->pages) || !isset($library_response->data->pages->items)) {
283 throw new WizardException(__("An error occurred while fetching available pages.", "superb-blocks"));
284 }
285
286 $pages_items = $library_response->data->pages->items;
287
288 // Order free to the top of list array if the user is not premium
289 if (!$this->userIsPremium) {
290 $freePages = array();
291 $premiumPages = array();
292 foreach ($pages_items as $page) {
293 if (isset($page->package) && $page->package === 'premium') {
294 $premiumPages[] = $page;
295 } else {
296 $freePages[] = $page;
297 }
298 }
299 $pages_items = array_merge($freePages, $premiumPages);
300 }
301
302 $page_category_map = self::BuildCategoryMap(isset($library_response->data->pages->categories) ? $library_response->data->pages->categories : null);
303 foreach ($pages_items as $page) {
304 $pageItem = new WizardItemPage($page);
305 $pageItem->use_custom_page_template_preview = 1;
306 $this->templatePages[] = $pageItem;
307
308 // Add landing page templates to the front page templates
309 $cat_key = self::ResolveCategoryKey($page, $page_category_map);
310 if ($cat_key === WizardDataCategory::LANDING_PAGE) {
311 $landingPageItem = new WizardItemPage($page);
312 $this->frontPageTemplates[] = $landingPageItem;
313 }
314 if ($cat_key === WizardDataCategory::BLOG) {
315 $this->blogTemplates[] = $pageItem;
316 }
317 }
318 } catch (WizardException $e) {
319 // Exception is automatically logged. Catch it so that the wizard can continue with the available theme templates.
320 }
321 }
322
323 private function HandleTemplateLogic($logicType = 'default')
324 {
325 // Check for static pages
326 $has_static_pages = get_option('show_on_front') === 'page';
327 $staticFrontPageID = get_option('page_on_front');
328 $hasStaticFrontPage = $has_static_pages && $staticFrontPageID && $staticFrontPageID !== 0;
329 $staticBlogPageID = get_option('page_for_posts');
330 $hasStaticBlogPage = $has_static_pages && $staticBlogPageID && $staticBlogPageID !== 0;
331
332 if ($logicType === "default") {
333 // Front page templates additional logic
334 if (!$this->hasFrontPageTemplate) {
335 // If no front page template exists, set regular theme templates
336 $this->frontPageTemplates = $this->templatePages;
337
338 if ($hasStaticFrontPage) {
339 $static_page = new WizardItemStatic(__("Static Homepage (Current)", "superb-blocks"), $staticFrontPageID);
340 $this->frontPageTemplates = array_merge(array($static_page), $this->frontPageTemplates);
341 }
342 }
343
344 // Add "No blog page" selection
345 $noBlogPage = array(new WizardItemIgnore(__("No Blog Page", "superb-blocks")));
346 // Blog templates logic
347 if ($this->hasHomeTemplate || $this->hasIndexTemplate) {
348 $this->blogTemplates = array_merge($noBlogPage, $this->homeTemplates, $this->indexTemplates, $this->blogTemplates);
349 } else {
350 if ($hasStaticBlogPage) {
351 $static_page = array(new WizardItemStatic(__("Static Blog Page (Current)", "superb-blocks"), $staticBlogPageID));
352 $this->blogTemplates = array_merge($noBlogPage, $static_page);
353 } else {
354 $this->blogTemplates = $noBlogPage;
355 }
356 }
357
358 // If home templates exist and no front page template exists, add the home templates to the front page templates
359 if (!empty($this->homeTemplates) && !$this->hasFrontPageTemplate) {
360 if ($hasStaticFrontPage) {
361 // Add the home templates after the first item
362 array_splice($this->frontPageTemplates, 1, 0, $this->homeTemplates);
363 } else {
364 // Add the home templates to the front of the front page templates
365 $this->frontPageTemplates = array_merge($this->homeTemplates, $this->frontPageTemplates);
366 }
367 }
368 }
369
370 if ($logicType === "restoration") {
371 if (!empty($this->headerTemplates)) {
372 $currentHeader = get_block_template(get_stylesheet() . '//header', WizardItemTypes::WP_TEMPLATE_PART);
373 $currentHeader->title = $currentHeader->title . __(" (Current)", "superb-blocks");
374 $currentHeader = new WizardItem($currentHeader);
375 $this->headerTemplates = array_merge(array($currentHeader), $this->headerTemplates);
376 }
377
378 if (!empty($this->footerTemplates)) {
379 $currentFooter = get_block_template(get_stylesheet() . '//footer', WizardItemTypes::WP_TEMPLATE_PART);
380 $currentFooter->title = $currentFooter->title . __(" (Current)", "superb-blocks");
381 $currentFooter = new WizardItem($currentFooter);
382 $this->footerTemplates = array_merge(array($currentFooter), $this->footerTemplates);
383 }
384
385
386 if (!empty($this->frontPageTemplates)) {
387 if ($this->hasFrontPageTemplate) {
388 $currentFrontPage = get_block_template(get_stylesheet() . '//' . 'front-page', WizardItemTypes::WP_TEMPLATE);
389 $currentFrontPage->title = $currentFrontPage->title . __(" (Current)", "superb-blocks");
390 $currentFrontPage = new WizardItem($currentFrontPage);
391 $this->frontPageTemplates = array_merge(array($currentFrontPage), $this->frontPageTemplates);
392 }
393 }
394
395 $this->blogTemplates = array_merge($this->homeTemplates, $this->indexTemplates, $this->blogTemplates);
396 if (!empty($this->blogTemplates)) {
397 if ($this->hasHomeTemplate) {
398 $currentBlogPage = get_block_template(get_stylesheet() . '//' . 'home', WizardItemTypes::WP_TEMPLATE);
399 } elseif ($this->hasIndexTemplate) {
400 $currentBlogPage = get_block_template(get_stylesheet() . '//' . 'index', WizardItemTypes::WP_TEMPLATE);
401 }
402
403 if ($currentBlogPage) {
404 $currentBlogPage->datatype = 'blog';
405 $currentBlogPage->title = $currentBlogPage->title . __(" (Current)", "superb-blocks");
406 $currentBlogPage = new WizardItem($currentBlogPage);
407 $this->blogTemplates = array_merge(array($currentBlogPage), $this->blogTemplates);
408 }
409 }
410 }
411 }
412
413 private function GetAcceptedTemplates()
414 {
415 $templates = get_block_templates();
416 $acceptedTemplates = array();
417 // Filter out templates that are not accepted for the current theme
418 foreach ($templates as $template) {
419 if ($template->status !== 'publish') continue;
420 if ($template->type !== WizardItemTypes::WP_TEMPLATE) continue;
421 if ($template->theme !== get_stylesheet()) continue;
422 if ($template->slug === AddonsPageTemplateUtil::TEMPLATE_ID) continue; // Do not include the wizard page creation template
423 if ($template->slug !== 'front-page' && $template->slug !== 'home' && $template->slug !== 'index') {
424 if (!$template->is_custom) continue;
425 if (!$template->post_types || !in_array('page', $template->post_types)) continue;
426 }
427 $acceptedTemplates[] = new WizardItem($template);
428 }
429 return $acceptedTemplates;
430 }
431
432 public function InitializeRestorationTemplates()
433 {
434 $theme_restoration = WizardRestorationPointController::GetThemeRestorationPoints();
435 if (!$theme_restoration) {
436 return;
437 }
438
439 $restoration_templates = [];
440 foreach ($theme_restoration as $restorationId => $restorationPointArray) {
441 $restoration_templates[] = new WizarditemRestorationPoint($restorationId, $restorationPointArray);
442 }
443
444 // Sort restoration templates by timestamp
445 usort($restoration_templates, function ($a, $b) {
446 return $a->timestamp <=> $b->timestamp;
447 });
448
449 $this->SortTemplatesIntoArraysBySlug($restoration_templates, "restoration");
450 $this->HandleTemplateLogic('restoration');
451 }
452
453 private function SortTemplatesIntoArraysBySlug($acceptedTemplates, $logicType = "default")
454 {
455 foreach ($acceptedTemplates as $template) {
456 $slug = $template->GetBaseSlug();
457
458 if ($slug === 'front-page' || $slug === 'home' || $slug === 'index') {
459 switch ($slug) {
460 case "front-page":
461 $base_label = __("Front Page", "superb-blocks");
462 break;
463 case "home":
464 $template->datatype = 'blog';
465 $base_label = __("Blog Home", "superb-blocks");
466 break;
467 case "index":
468 $template->datatype = 'blog';
469 $base_label = __("Index", "superb-blocks");
470 break;
471 }
472 $this->InitializePart($template, WizardItemTypes::WP_TEMPLATE, $slug, $base_label . __(" (Theme)", "superb-blocks"), $base_label . __(" (Current)", "superb-blocks"), $base_label);
473 } else {
474 if ($logicType === "default") {
475 $template->title = $template->title . ' (' . __('Theme', 'superb-blocks') . ')';
476 }
477 $this->AddToAppropriateArray($template, $template->category);
478 }
479 }
480 }
481
482 private function AddToAppropriateArray($item, $category)
483 {
484 switch ($category) {
485 case 'header':
486 $this->headerTemplates[] = $item;
487 break;
488 case 'footer':
489 $this->footerTemplates[] = $item;
490 break;
491 case 'front-page':
492 $this->hasFrontPageTemplate = true;
493 $this->frontPageTemplates[] = $item;
494 break;
495 case 'home':
496 $this->hasHomeTemplate = true;
497 $this->homeTemplates[] = $item;
498 break;
499 case 'index':
500 $this->hasIndexTemplate = true;
501 $this->indexTemplates[] = $item;
502 break;
503 default:
504 if (strpos($category, 'blog') !== false) {
505 $this->blogTemplates[] = $item;
506 }
507 $this->templatePages[] = $item;
508 break;
509 }
510 }
511
512 public function GetHeaderTemplates()
513 {
514 return $this->headerTemplates;
515 }
516
517 public function GetFooterTemplates()
518 {
519 return $this->footerTemplates;
520 }
521
522 public function HasMultipleHeaderParts()
523 {
524 return $this->totalHeaderParts > 1;
525 }
526
527 public function HasMultipleFooterParts()
528 {
529 return $this->totalFooterParts > 1;
530 }
531
532 public function GetBlogTemplates()
533 {
534 return $this->blogTemplates;
535 }
536
537 public function GetFrontPageTemplates()
538 {
539 return $this->frontPageTemplates;
540 }
541
542 public function GetTemplatePages()
543 {
544 return $this->templatePages;
545 }
546 }
547