PluginProbe ʕ •ᴥ•ʔ
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More / 3.6.1
Superb Addons: Blocks, Patterns, Pre-built Pages, Sliders, Popups, Free Forms, Animations & More v3.6.1
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-controller.php
superb-blocks / src / admin / controllers / wizard Last commit date
class-wizard-controller.php 10 months ago class-wizard-restorationpoint-controller.php 10 months ago class-wizard-template-preview-controller.php 10 months ago
class-wizard-controller.php
449 lines
1 <?php
2
3 namespace SuperbAddons\Admin\Controllers\Wizard;
4
5 use Exception;
6 use SuperbAddons\Admin\Controllers\DashboardController;
7 use SuperbAddons\Config\Capabilities;
8 use SuperbAddons\Data\Controllers\LogController;
9 use SuperbAddons\Data\Controllers\RestController;
10 use SuperbAddons\Data\Utils\ThemeInstaller;
11 use SuperbAddons\Data\Utils\ThemeInstallerException;
12 use SuperbAddons\Data\Utils\Wizard\AddonsPageTemplateUtil;
13 use SuperbAddons\Data\Utils\Wizard\WizardActionParameter;
14 use SuperbAddons\Data\Utils\Wizard\WizardException;
15 use SuperbAddons\Data\Utils\Wizard\WizardItemTypes;
16 use SuperbAddons\Data\Utils\Wizard\WizardMenuCreator;
17 use SuperbAddons\Data\Utils\Wizard\WizardPageCreator;
18 use SuperbAddons\Data\Utils\Wizard\WizardPartCreator;
19 use SuperbAddons\Data\Utils\Wizard\WizardStageTypes;
20 use SuperbAddons\Data\Utils\Wizard\WizardStageUtil;
21 use SuperbAddons\Gutenberg\Controllers\GutenbergController;
22 use WP_Error;
23 use WP_REST_Server;
24
25 defined('ABSPATH') || exit();
26
27 class WizardController
28 {
29 const WIZARD_ROUTE = '/wizard';
30 const ACTION_QUERY_PARAM = 'superbaddons-wizard-action';
31 const COMPLETED_QUERY_PARAM = 'superbaddons-wizard-completed';
32 const RECOMMENDER_TRANSIENT = 'superbaddons_wizard_recommender_transient';
33 const WOOCOMMERCE_TRANSIENT = 'superbaddons_wizard_woocommerce_transient';
34
35
36 public static function Initialize()
37 {
38 self::InitializeWizardRecommenderSwitchAction();
39 self::InitializeTemplateWizardEndpoints();
40 if (!GutenbergController::is_block_theme()) {
41 return;
42 }
43
44 self::InitializeWizardPageTemplates();
45 WizardTemplatePreviewController::InitializeTemplatePreview();
46 }
47
48 private static function InitializeWizardPageTemplates()
49 {
50 add_filter('get_block_templates', function ($query_result, $query, $template_type) {
51 if ($template_type !== WizardItemTypes::WP_TEMPLATE) {
52 return $query_result;
53 }
54
55 if (
56 !empty($query) &&
57 (isset($query['slug__in']) && !in_array(AddonsPageTemplateUtil::TEMPLATE_ID, $query['slug__in'])) ||
58 (isset($query['slug__not_in']) && in_array(AddonsPageTemplateUtil::TEMPLATE_ID, $query['slug__not_in']))
59 ) {
60 return $query_result;
61 }
62
63 $template = AddonsPageTemplateUtil::GetAddonsPageBlockTemplateObject();
64
65 $query_result[] = $template;
66
67 return $query_result;
68 }, 10, 3);
69
70 add_filter('get_block_file_template', function ($block_template, $id, $template_type) {
71 if ($template_type !== WizardItemTypes::WP_TEMPLATE) {
72 return $block_template;
73 }
74
75 if ($id === get_stylesheet() . "//" . AddonsPageTemplateUtil::TEMPLATE_ID || $id === AddonsPageTemplateUtil::PLUGIN_SLUG . '//' . AddonsPageTemplateUtil::TEMPLATE_ID) {
76 return AddonsPageTemplateUtil::GetAddonsPageBlockTemplateObject();
77 }
78
79 return $block_template;
80 }, 10, 3);
81 }
82
83 private static function InitializeWizardRecommenderSwitchAction()
84 {
85 // Add action to the switch theme hook
86 add_action('switch_theme', function () {
87 self::MaybeSetWizardRecommenderTransient();
88 });
89
90 // Check if WooCommerce is active
91 add_action('activated_plugin', function ($plugin) {
92 if ($plugin !== 'woocommerce/woocommerce.php') {
93 return;
94 }
95 self::MaybeSetWizardWooCommerceTransient();
96 });
97
98 add_action('deactivated_plugin', function ($plugin) {
99 if ($plugin !== 'woocommerce/woocommerce.php') {
100 return;
101 }
102 self::RemoveWizardWooCommerceTransient();
103 });
104 }
105
106 public static function MaybeSetWizardRecommenderTransient()
107 {
108 if (!GutenbergController::is_block_theme()) {
109 self::RemoveWizardRecommenderTransient();
110 self::RemoveWizardWooCommerceTransient();
111 return;
112 }
113 $current_theme = get_stylesheet();
114 $completed_themes = get_option('superbaddons_wizard_completed_themes', []);
115 if (in_array($current_theme, $completed_themes)) {
116 self::RemoveWizardRecommenderTransient();
117 return;
118 }
119 set_transient(self::RECOMMENDER_TRANSIENT, true, MONTH_IN_SECONDS);
120 self::MaybeSetWizardWooCommerceTransient();
121 }
122
123 public static function MaybeSetWizardWooCommerceTransient()
124 {
125 if (is_plugin_active('woocommerce/woocommerce.php')) {
126 set_transient(self::WOOCOMMERCE_TRANSIENT, true, MONTH_IN_SECONDS);
127 } else {
128 self::RemoveWizardWooCommerceTransient();
129 }
130 }
131
132 public static function RemoveWizardRecommenderTransient()
133 {
134 return delete_transient(self::RECOMMENDER_TRANSIENT);
135 }
136
137 public static function GetWizardRecommenderTransient()
138 {
139 return get_transient(self::RECOMMENDER_TRANSIENT);
140 }
141
142 public static function GetWizardWoocommerceTransient()
143 {
144 return get_transient(self::WOOCOMMERCE_TRANSIENT);
145 }
146
147 public static function RemoveWizardWooCommerceTransient()
148 {
149 return delete_transient(self::WOOCOMMERCE_TRANSIENT);
150 }
151
152 private static function SetWizardPartPreviewTransient($preview_data)
153 {
154 $user_id = get_current_user_id();
155 $transient = get_transient(WizardTemplatePreviewController::TEMPLATE_PART_PREVIEW_TRANSIENT);
156 $transient[$user_id] = $preview_data;
157 return set_transient(WizardTemplatePreviewController::TEMPLATE_PART_PREVIEW_TRANSIENT, $transient, DAY_IN_SECONDS);
158 }
159
160 public static function GetPartPreviewTransient()
161 {
162 $user_id = get_current_user_id();
163 $transient = get_transient(WizardTemplatePreviewController::TEMPLATE_PART_PREVIEW_TRANSIENT);
164 return isset($transient[$user_id]) ? $transient[$user_id] : false;
165 }
166
167 public static function RemoveWizardPartPreviewTransient()
168 {
169 return delete_transient(self::RECOMMENDER_TRANSIENT);
170 }
171
172 public static function GetRecommendedBlockThemes()
173 {
174 if (!function_exists('themes_api')) {
175 require_once(ABSPATH . 'wp-admin/includes/theme.php');
176 }
177
178 return themes_api(
179 "query_themes",
180 array(
181 "author" => "superbaddons",
182 "per_page" => 24,
183 "browse" => "popular",
184 "fields" => array("name" => true, "slug" => true, "screenshot_url" => true)
185 )
186 );
187 }
188
189 public static function GetWizardURL($action)
190 {
191 return add_query_arg(
192 array(
193 'page' => DashboardController::PAGE_WIZARD,
194 self::ACTION_QUERY_PARAM => $action,
195 ),
196 admin_url("admin.php")
197 );
198 }
199
200 public static function GetWizardCompleteURL($wizardType)
201 {
202 return add_query_arg(
203 array(
204 'page' => DashboardController::PAGE_WIZARD,
205 self::ACTION_QUERY_PARAM => WizardActionParameter::COMPLETE,
206 self::COMPLETED_QUERY_PARAM => $wizardType
207 ),
208 admin_url("admin.php")
209 );
210 }
211
212 public static function GetCompletedWizardType()
213 {
214 // determine the type of wizard page that was completed.
215 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
216 return isset($_GET[self::COMPLETED_QUERY_PARAM]) ? sanitize_text_field(wp_unslash($_GET[self::COMPLETED_QUERY_PARAM])) : false;
217 }
218
219 private static function isAllowedAction($action)
220 {
221 if (!isset($action)) {
222 return false;
223 }
224 $allowed_actions = [WizardActionParameter::ADD_NEW_PAGES, WizardActionParameter::HEADER_FOOTER, WizardActionParameter::WOOCOMMERCE_HEADER, WizardActionParameter::THEME_DESIGNER, WizardActionParameter::RESTORE];
225 return in_array($action, $allowed_actions);
226 }
227
228 public static function IsWizardStages()
229 {
230 // determine if we are on a wizard stage page.
231 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
232 if (!isset($_GET[self::ACTION_QUERY_PARAM]) || !self::isAllowedAction(sanitize_text_field(wp_unslash($_GET[self::ACTION_QUERY_PARAM])))) {
233 return false;
234 }
235
236 return true;
237 }
238
239 public static function IsCompleteScreen()
240 {
241 // determine if we are on the wizard complete screen.
242 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
243 return isset($_GET[self::ACTION_QUERY_PARAM]) && sanitize_text_field(wp_unslash($_GET[self::ACTION_QUERY_PARAM])) === WizardActionParameter::COMPLETE;
244 }
245
246 public static function ThemeHasCompletedWizard()
247 {
248 $current_theme = get_stylesheet();
249 $completed_themes = get_option('superbaddons_wizard_completed_themes', []);
250 return in_array($current_theme, $completed_themes);
251 }
252
253 public static function CompleteWizard()
254 {
255 $current_theme = get_stylesheet();
256 $completed_themes = get_option('superbaddons_wizard_completed_themes', []);
257 if (!in_array($current_theme, $completed_themes)) {
258 $completed_themes[] = $current_theme;
259 update_option('superbaddons_wizard_completed_themes', $completed_themes, false);
260 }
261 self::RemoveWizardRecommenderTransient();
262 }
263
264 public static function CompleteWooCommerceWizard()
265 {
266 self::RemoveWizardWooCommerceTransient();
267 }
268
269 private static function InitializeTemplateWizardEndpoints()
270 {
271 RestController::AddRoute(self::WIZARD_ROUTE, array(
272 'methods' => WP_REST_Server::EDITABLE,
273 'permission_callback' => array(self::class, 'TemplateWizardCallbackPermissionCheck'),
274 'callback' => array(self::class, 'TemplateWizardCallback'),
275 ));
276 }
277
278 public static function TemplateWizardCallbackPermissionCheck()
279 {
280 // Restrict endpoint to only users who have the proper capability.
281 if (!current_user_can(Capabilities::ADMIN)) {
282 return new WP_Error('rest_forbidden', esc_html__('Unauthorized. Please check user permissions.', "superb-blocks"), array('status' => 401));
283 }
284
285 return true;
286 }
287
288 public static function TemplateWizardCallback($request)
289 {
290 if (!isset($request['action'])) {
291 return new \WP_Error('bad_request_plugin', 'Bad Plugin Request', array('status' => 400));
292 }
293 switch ($request['action']) {
294 case 'switchtheme':
295 return self::SwitchThemeCallback($request);
296 case 'create':
297 if (!GutenbergController::is_block_theme()) {
298 return new \WP_Error('bad_request_plugin', 'Bad Plugin Request', array('status' => 400));
299 }
300 return self::TemplateWizardCreateCallback($request);
301 case 'headerfooterpreview':
302 if (!GutenbergController::is_block_theme()) {
303 return new \WP_Error('bad_request_plugin', 'Bad Plugin Request', array('status' => 400));
304 }
305 return self::HeaderFooterPreviewCallback($request);
306 default:
307 return new \WP_Error('bad_request_plugin', 'Bad Plugin Request', array('status' => 400));
308 }
309 }
310
311 private static function SwitchThemeCallback($request)
312 {
313 try {
314 $theme_slug = sanitize_text_field($request['theme']);
315 if (empty($theme_slug)) {
316 return rest_ensure_response(['success' => false, 'text' => esc_html__("Couldn't find selected theme.", "superb-blocks")]);
317 }
318
319 $themes_api = self::GetRecommendedBlockThemes();
320
321 $accepted_theme = false;
322 foreach ($themes_api->themes as $theme) {
323 if ($theme->slug === $theme_slug) {
324 $accepted_theme = true;
325 break;
326 }
327 }
328
329 if (!$accepted_theme) {
330 return rest_ensure_response(['success' => false, 'text' => esc_html__("Selected theme is invalid. Please contact support for assistance.", "superb-blocks")]);
331 }
332
333 $installed = ThemeInstaller::Install($theme_slug);
334
335 return rest_ensure_response(['success' => $installed]);
336 } catch (ThemeInstallerException $tex) {
337 return rest_ensure_response(['success' => false, 'text' => $tex->getMessage()]);
338 } catch (Exception $ex) {
339 LogController::HandleException($ex);
340 return new \WP_Error('internal_error_plugin', 'Internal Plugin Error', array('status' => 500));
341 }
342 }
343
344 private static function TemplateWizardCreateCallback($request)
345 {
346 try {
347 if (!isset($request['selection_data'])) {
348 return rest_ensure_response(['success' => false, 'text' => esc_html__("Something went wrong. The process could not start.", "superb-blocks")]);
349 }
350
351 $selection_data = json_decode($request['selection_data'], true);
352
353 $stageUtil = new WizardStageUtil($request['wizardType']);
354 if (!self::ValidateSelectionData($selection_data, $stageUtil)) {
355 return rest_ensure_response(['success' => false, 'text' => esc_html__("Something went wrong. Please double-check that all steps have been correctly completed.", "superb-blocks")]);
356 }
357
358 WizardPartCreator::CreateTemplateParts($selection_data, $stageUtil);
359
360 if ($stageUtil->HasPageStages()) {
361 $menu_items = WizardPageCreator::CreateTemplatePages($selection_data, $stageUtil);
362
363 if (empty($menu_items)) {
364 // If the wizard is in restore mode, we can continue even if no menu items are created.
365 if (!$stageUtil->IsRestore()) {
366 // If the wizard is not in restore mode, we need to throw an error.
367 return rest_ensure_response(['success' => false, 'text' => esc_html__("Something went wrong. Templates and/or pages were not able to be properly processed.", "superb-blocks")]);
368 }
369 }
370
371 WizardMenuCreator::MaybeUpdateMenu($selection_data, $menu_items);
372 }
373
374 if ($stageUtil->GetType() === WizardActionParameter::THEME_DESIGNER) {
375 WizardController::CompleteWizard();
376 } elseif ($stageUtil->GetType() === WizardActionParameter::WOOCOMMERCE_HEADER) {
377 WizardController::CompleteWooCommerceWizard();
378 }
379
380 return rest_ensure_response(['success' => true]);
381 } catch (WizardException $wex) {
382 return rest_ensure_response(['success' => false, 'text' => $wex->getMessage()]);
383 } catch (Exception $ex) {
384 LogController::HandleException($ex);
385 return new WP_Error('internal_error_plugin', 'Internal Plugin Error', array('status' => 500));
386 }
387 }
388
389 private static function ValidateSelectionData($selection_data, $stageUtil)
390 {
391 if (!self::isAllowedAction($stageUtil->GetType())) {
392 return false;
393 }
394
395 foreach ($stageUtil->GetStages() as $stage_type) {
396 if (!isset($selection_data[$stage_type])) {
397 if ($stageUtil->GetType() === WizardActionParameter::RESTORE) {
398 // If stages are missing, but the action is restore, then we can continue as the restore action can have any number of stages.
399 continue;
400 }
401 if ($stage_type === WizardStageTypes::NAVIGATION_MENU_STAGE && !$stageUtil->GetMenuAvailability()['available']) {
402 // If the navigation stage is missing and navigation menu is not available, then we can continue.
403 continue;
404 }
405 return false;
406 }
407
408 $stage_selections = $selection_data[$stage_type];
409
410 if (!is_array($stage_selections)) {
411 return false;
412 }
413
414 if (empty($stage_selections)) {
415 continue;
416 }
417
418 if (isset($stage_selections[0]['isChanged']) && !boolval($stage_selections[0]['isChanged'])) {
419 continue;
420 }
421
422 foreach ($stage_selections as $selection) {
423 if (!isset($selection['slug']) || !isset($selection['title'])) {
424 return false;
425 }
426 }
427 }
428
429 return true;
430 }
431
432 public static function HeaderFooterPreviewCallback($request)
433 {
434 $preview_transient = [];
435 // Do not set if the header is the default header.
436 if (isset($request['header']) && $request['header'] !== 'header') {
437 $preview_transient['header'] = $request['header'];
438 }
439 // Do not set if the footer is the default footer.
440 if (isset($request['footer']) && $request['footer'] !== 'footer') {
441 $preview_transient['footer'] = $request['footer'];
442 }
443
444 self::SetWizardPartPreviewTransient($preview_transient);
445
446 return rest_ensure_response(['success' => true]);
447 }
448 }
449