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