PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.44
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.44
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
king-addons / includes / extensions / Maintenance_Mode / Maintenance_Mode.php

Maintenance_Mode.php in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.44, at includes/extensions/Maintenance_Mode/Maintenance_Mode.php

1,043 lines 35.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Maintenance Mode extension.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons\Maintenance_Mode;
9
10 if (!defined('ABSPATH')) {
11 exit;
12 }
13
14 class Maintenance_Mode
15 {
16 private const OPTION_NAME = 'kng_maintenance_settings';
17
18 private static ?Maintenance_Mode $instance = null;
19
20 /**
21 * Cached settings.
22 *
23 * @var array<string, mixed>
24 */
25 private array $settings = [];
26
27 public static function instance(): Maintenance_Mode
28 {
29 if (self::$instance === null) {
30 self::$instance = new self();
31 }
32
33 return self::$instance;
34 }
35
36 private function __construct()
37 {
38 $this->settings = $this->get_settings();
39
40 add_action('admin_menu', [$this, 'register_admin_menu']);
41 add_action('admin_init', [$this, 'register_settings']);
42 add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_assets']);
43 add_action('template_redirect', [$this, 'maybe_render_maintenance'], 1);
44
45 add_action('admin_post_kng_maintenance_export', [$this, 'handle_export']);
46 add_action('admin_post_kng_maintenance_import', [$this, 'handle_import']);
47
48 add_shortcode('kng_maintenance_page', [$this, 'render_shortcode']);
49 }
50
51 public function register_admin_menu(): void
52 {
53 add_submenu_page(
54 'king-addons',
55 __('Maintenance Mode', 'king-addons'),
56 __('Maintenance Mode', 'king-addons'),
57 'manage_options',
58 'king-addons-maintenance-mode',
59 [$this, 'render_admin_page']
60 );
61 }
62
63 public function register_settings(): void
64 {
65 register_setting(
66 'kng_maintenance_settings_group',
67 self::OPTION_NAME,
68 [
69 'type' => 'array',
70 'sanitize_callback' => [$this, 'sanitize_settings'],
71 'default' => $this->get_default_settings(),
72 ]
73 );
74 }
75
76 public function enqueue_admin_assets(string $hook): void
77 {
78 if ($hook !== 'king-addons_page_king-addons-maintenance-mode') {
79 return;
80 }
81
82 $shared_css = KING_ADDONS_URL . 'includes/admin/layouts/shared/admin-v3-styles.css';
83 $shared_path = KING_ADDONS_PATH . 'includes/admin/layouts/shared/admin-v3-styles.css';
84 $shared_version = file_exists($shared_path) ? filemtime($shared_path) : KING_ADDONS_VERSION;
85 wp_enqueue_style('king-addons-admin-v3', $shared_css, [], $shared_version);
86
87 $admin_css = KING_ADDONS_URL . 'includes/extensions/Maintenance_Mode/assets/admin.css';
88 $admin_path = KING_ADDONS_PATH . 'includes/extensions/Maintenance_Mode/assets/admin.css';
89 $admin_version = file_exists($admin_path) ? filemtime($admin_path) : KING_ADDONS_VERSION;
90 wp_enqueue_style('king-addons-maintenance-admin', $admin_css, ['king-addons-admin-v3'], $admin_version);
91
92 $admin_js = KING_ADDONS_URL . 'includes/extensions/Maintenance_Mode/assets/admin.js';
93 $admin_path_js = KING_ADDONS_PATH . 'includes/extensions/Maintenance_Mode/assets/admin.js';
94 $admin_js_version = file_exists($admin_path_js) ? filemtime($admin_path_js) : KING_ADDONS_VERSION;
95 wp_enqueue_script('king-addons-maintenance-admin', $admin_js, ['jquery'], $admin_js_version, true);
96
97 wp_localize_script('king-addons-maintenance-admin', 'KNGMaintenance', [
98 'ajaxUrl' => admin_url('admin-ajax.php'),
99 'themeNonce' => wp_create_nonce('king_addons_dashboard_ui'),
100 ]);
101 }
102
103 public function render_admin_page(): void
104 {
105 if (!current_user_can('manage_options')) {
106 return;
107 }
108
109 $view = isset($_GET['view']) ? sanitize_key($_GET['view']) : 'dashboard';
110 $is_pro = $this->is_pro();
111 $settings = $this->get_settings();
112 $templates = $this->get_builtin_templates();
113
114 include __DIR__ . '/templates/admin-page.php';
115 }
116
117 public function maybe_render_maintenance(): void
118 {
119 if ($this->is_preview_request()) {
120 $this->render_maintenance_response(true);
121 exit;
122 }
123
124 if (!$this->is_mode_active()) {
125 return;
126 }
127
128 if ($this->should_bypass_request()) {
129 return;
130 }
131
132 $this->render_maintenance_response(false);
133 exit;
134 }
135
136 private function is_mode_active(): bool
137 {
138 if (empty($this->settings['enabled'])) {
139 return false;
140 }
141
142 if (empty($this->settings['schedule_enabled'])) {
143 return true;
144 }
145
146 $start = $this->parse_schedule_time($this->settings['schedule_start'] ?? '');
147 $end = $this->parse_schedule_time($this->settings['schedule_end'] ?? '');
148 $now = current_time('timestamp', true);
149
150 if ($start && $end) {
151 return $now >= $start && $now <= $end;
152 }
153
154 if ($start && !$end) {
155 return $now >= $start;
156 }
157
158 if (!$start && $end) {
159 return $now <= $end;
160 }
161
162 return false;
163 }
164
165 private function should_bypass_request(): bool
166 {
167 if (defined('WP_CLI') && WP_CLI) {
168 return true;
169 }
170
171 if (wp_doing_cron()) {
172 return true;
173 }
174
175 if (is_admin()) {
176 return true;
177 }
178
179 if (wp_doing_ajax() && !empty($this->settings['allow_admin_ajax'])) {
180 return true;
181 }
182
183 if ($this->is_login_request()) {
184 return true;
185 }
186
187 if (!empty($this->settings['disable_elementor_editor']) && $this->is_elementor_editor()) {
188 return true;
189 }
190
191 if ($this->is_rest_request()) {
192 if (is_user_logged_in()) {
193 return true;
194 }
195 return !empty($this->settings['allow_rest']);
196 }
197
198 if ($this->is_user_allowed()) {
199 return true;
200 }
201
202 if ($this->is_ip_whitelisted()) {
203 return true;
204 }
205
206 if ($this->is_path_whitelisted()) {
207 return true;
208 }
209
210 return false;
211 }
212
213 private function is_user_allowed(): bool
214 {
215 if (!is_user_logged_in()) {
216 return false;
217 }
218
219 $user = wp_get_current_user();
220 if (!$user || !$user->ID) {
221 return false;
222 }
223
224 if (!empty($this->settings['exclude_admin']) && user_can($user, 'manage_options')) {
225 return true;
226 }
227
228 if ($this->is_pro()) {
229 $allowed_roles = $this->settings['allowed_roles'] ?? [];
230 if (!empty($allowed_roles) && array_intersect((array) $user->roles, $allowed_roles)) {
231 return true;
232 }
233 }
234
235 return false;
236 }
237
238 private function is_ip_whitelisted(): bool
239 {
240 $whitelist = $this->settings['whitelist_ips'] ?? [];
241 if (empty($whitelist)) {
242 return false;
243 }
244
245 $ip = $this->get_client_ip();
246 if ($ip === '') {
247 return false;
248 }
249
250 return in_array($ip, $whitelist, true);
251 }
252
253 private function is_path_whitelisted(): bool
254 {
255 $paths = $this->settings['whitelist_paths'] ?? [];
256 if (empty($paths)) {
257 return false;
258 }
259
260 $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : '';
261 $path = wp_parse_url($request_uri, PHP_URL_PATH);
262 $path = $path ? '/' . ltrim($path, '/') : '/';
263
264 foreach ($paths as $allowed) {
265 if ($allowed !== '/' && strpos($path, $allowed) === 0) {
266 return true;
267 }
268 if ($allowed === $path) {
269 return true;
270 }
271 }
272
273 return false;
274 }
275
276 private function is_login_request(): bool
277 {
278 $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : '';
279 return strpos($request_uri, 'wp-login.php') !== false || strpos($request_uri, 'wp-register.php') !== false;
280 }
281
282 private function is_rest_request(): bool
283 {
284 return defined('REST_REQUEST') && REST_REQUEST;
285 }
286
287 private function is_elementor_editor(): bool
288 {
289 if (isset($_GET['elementor-preview'])) {
290 return true;
291 }
292
293 if (class_exists('\\Elementor\\Plugin')) {
294 $plugin = \Elementor\Plugin::instance();
295 if ($plugin->editor && $plugin->editor->is_edit_mode()) {
296 return true;
297 }
298 }
299
300 return false;
301 }
302
303 private function render_maintenance_response(bool $is_preview): void
304 {
305 $mode = $this->settings['mode'] ?? 'coming_soon';
306 $status = $mode === 'maintenance' ? 503 : 200;
307
308 if ($is_preview) {
309 $status = 200;
310 }
311
312 status_header($status);
313 nocache_headers();
314 header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
315
316 $retry_after = (int) ($this->settings['retry_after'] ?? 0);
317 if ($status === 503 && $retry_after > 0) {
318 header('Retry-After: ' . $retry_after);
319 }
320
321 if (!empty($this->settings['noindex'])) {
322 header('X-Robots-Tag: noindex, nofollow', true);
323 }
324
325 $title = $mode === 'maintenance' ? __('Maintenance Mode', 'king-addons') : __('Coming Soon', 'king-addons');
326 $content = $this->get_rendered_page_content();
327
328 $this->enqueue_frontend_assets();
329
330 echo '<!doctype html>';
331 echo '<html ' . get_language_attributes() . '>';
332 echo '<head>';
333 echo '<meta charset="' . esc_attr(get_bloginfo('charset')) . '">';
334 echo '<meta name="viewport" content="width=device-width, initial-scale=1">';
335 echo '<title>' . esc_html($title) . '</title>';
336 if (!empty($this->settings['noindex'])) {
337 echo '<meta name="robots" content="noindex, nofollow">';
338 }
339 wp_head();
340 echo '</head>';
341 echo '<body class="kng-maintenance-body">';
342 echo $content;
343 wp_footer();
344 echo '</body></html>';
345 }
346
347 private function enqueue_frontend_assets(): void
348 {
349 wp_enqueue_style(
350 'king-addons-maintenance-frontend',
351 KING_ADDONS_URL . 'includes/extensions/Maintenance_Mode/assets/frontend.css',
352 [],
353 KING_ADDONS_VERSION
354 );
355
356 wp_enqueue_script(
357 'king-addons-maintenance-frontend',
358 KING_ADDONS_URL . 'includes/extensions/Maintenance_Mode/assets/frontend.js',
359 [],
360 KING_ADDONS_VERSION,
361 true
362 );
363 }
364
365 private function get_rendered_page_content(): string
366 {
367 $theme = $this->settings['theme'] ?? 'dark';
368 $mode = $this->settings['mode'] ?? 'coming_soon';
369 $template_source = $this->settings['template_source'] ?? 'built_in';
370
371 $wrapper_classes = [
372 'kng-maintenance',
373 $theme === 'light' ? 'kng-maintenance-theme-light' : 'kng-maintenance-theme-dark',
374 $mode === 'maintenance' ? 'kng-maintenance-mode-maintenance' : 'kng-maintenance-mode-coming-soon',
375 ];
376
377 $content = '';
378 if ($template_source === 'page' && !empty($this->settings['page_id'])) {
379 $content = $this->get_page_content((int) $this->settings['page_id']);
380 } elseif ($template_source === 'elementor' && !empty($this->settings['elementor_id'])) {
381 $content = $this->get_elementor_content((int) $this->settings['elementor_id']);
382 } else {
383 $content = $this->render_builtin_template((string) $this->settings['template_id']);
384 }
385
386 return '<main class="' . esc_attr(implode(' ', $wrapper_classes)) . '">' . $content . '</main>';
387 }
388
389 public function render_shortcode(array $atts): string
390 {
391 $atts = shortcode_atts([
392 'id' => '',
393 'type' => '',
394 'theme' => '',
395 'full_height' => 'false',
396 ], $atts, 'kng_maintenance_page');
397
398 $theme = $atts['theme'] !== '' ? sanitize_key($atts['theme']) : ($this->settings['theme'] ?? 'dark');
399 $theme = $theme === 'light' ? 'light' : 'dark';
400
401 $template_source = $this->settings['template_source'] ?? 'built_in';
402 $template_id = $this->settings['template_id'] ?? 'minimal';
403 $page_id = (int) ($this->settings['page_id'] ?? 0);
404 $elementor_id = (int) ($this->settings['elementor_id'] ?? 0);
405
406 if ($atts['id'] !== '') {
407 if (is_numeric($atts['id'])) {
408 $template_source = 'page';
409 $page_id = absint($atts['id']);
410 } else {
411 $template_source = 'built_in';
412 $template_id = sanitize_key($atts['id']);
413 }
414 }
415
416 if ($atts['type'] !== '') {
417 $template_source = sanitize_key($atts['type']);
418 if ($template_source === 'elementor' && is_numeric($atts['id'])) {
419 $elementor_id = absint($atts['id']);
420 }
421 }
422
423 $content = '';
424 if ($template_source === 'page' && $page_id) {
425 $content = $this->get_page_content($page_id);
426 } elseif ($template_source === 'elementor' && $elementor_id) {
427 $content = $this->get_elementor_content($elementor_id);
428 } else {
429 $content = $this->render_builtin_template($this->ensure_template_allowed($template_id));
430 }
431
432 $this->enqueue_frontend_assets();
433
434 $classes = [
435 'kng-maintenance',
436 $theme === 'light' ? 'kng-maintenance-theme-light' : 'kng-maintenance-theme-dark',
437 ];
438
439 if (filter_var($atts['full_height'], FILTER_VALIDATE_BOOLEAN)) {
440 $classes[] = 'kng-maintenance-full';
441 }
442
443 return '<div class="' . esc_attr(implode(' ', $classes)) . '">' . $content . '</div>';
444 }
445
446 private function get_page_content(int $page_id): string
447 {
448 $page = get_post($page_id);
449 if (!$page) {
450 return $this->render_builtin_template('minimal');
451 }
452
453 $old_post = $GLOBALS['post'] ?? null;
454 $GLOBALS['post'] = $page;
455 setup_postdata($page);
456
457 $content = apply_filters('the_content', $page->post_content);
458
459 wp_reset_postdata();
460 $GLOBALS['post'] = $old_post;
461
462 return $content;
463 }
464
465 private function get_elementor_content(int $template_id): string
466 {
467 if (!class_exists('\\Elementor\\Plugin')) {
468 return $this->render_builtin_template('minimal');
469 }
470
471 $plugin = \Elementor\Plugin::instance();
472 if (!$plugin->frontend) {
473 return $this->render_builtin_template('minimal');
474 }
475
476 $plugin->frontend->enqueue_styles();
477 $plugin->frontend->enqueue_scripts();
478
479 return $plugin->frontend->get_builder_content_for_display($template_id, true);
480 }
481
482 private function render_builtin_template(string $template_id): string
483 {
484 $templates = $this->get_builtin_templates();
485 $template_id = isset($templates[$template_id]) ? $template_id : 'minimal';
486 $template_id = $this->ensure_template_allowed($template_id);
487
488 $mode = $this->settings['mode'] ?? 'coming_soon';
489 $site_name = get_bloginfo('name');
490 $tagline = get_bloginfo('description');
491 $schedule_end = $this->settings['schedule_end'] ?? '';
492 $content = $this->get_template_content($template_id);
493
494 $headline = $mode === 'maintenance'
495 ? __('We are upgrading the experience.', 'king-addons')
496 : __('A new experience is on the horizon.', 'king-addons');
497
498 $subhead = $tagline !== '' ? $tagline : __('Our team is preparing something beautiful. Stay tuned.', 'king-addons');
499
500 $mode_label = $mode === 'maintenance' ? __('Maintenance Mode', 'king-addons') : __('Coming Soon', 'king-addons');
501
502 $badge = $content['badge'] !== '' ? $content['badge'] : $mode_label;
503 $headline = $content['headline'] !== '' ? $content['headline'] : $headline;
504 $subhead = $content['subhead'] !== '' ? $content['subhead'] : $subhead;
505
506 $launch_label = '';
507 if ($content['launch_label'] !== '') {
508 $launch_label = $content['launch_label'];
509 }
510 if ($schedule_end !== '') {
511 $local_end = get_date_from_gmt($schedule_end, 'Y-m-d H:i');
512 if ($launch_label === '') {
513 $launch_label = sprintf(__('Estimated return: %s', 'king-addons'), esc_html($local_end));
514 }
515 }
516
517 $footer_left = $content['footer_left'] !== '' ? $content['footer_left'] : $site_name;
518 $footer_right = $content['footer_right'] !== '' ? $content['footer_right'] : __('Powered by King Addons', 'king-addons');
519
520 $countdown_days = str_pad((string) (int) ($content['countdown_days'] ?? 0), 2, '0', STR_PAD_LEFT);
521 $countdown_hours = str_pad((string) (int) ($content['countdown_hours'] ?? 0), 2, '0', STR_PAD_LEFT);
522 $countdown_minutes = str_pad((string) (int) ($content['countdown_minutes'] ?? 0), 2, '0', STR_PAD_LEFT);
523 $progress_percent = isset($content['progress_percent']) ? min(100, max(0, (int) $content['progress_percent'])) : 0;
524 $progress_label = $content['progress_label'] ?? '';
525 if ($progress_label === '') {
526 $progress_label = sprintf(__('%d%% complete', 'king-addons'), $progress_percent);
527 }
528 $form_placeholder = $content['form_placeholder'] ?? '';
529 if ($form_placeholder === '') {
530 $form_placeholder = __('Email address', 'king-addons');
531 }
532 $form_button = $content['form_button'] ?? '';
533 if ($form_button === '') {
534 $form_button = __('Notify me', 'king-addons');
535 }
536
537 ob_start();
538 ?>
539 <section class="kng-maintenance-shell kng-maintenance-template-<?php echo esc_attr($template_id); ?>">
540 <div class="kng-maintenance-card">
541 <div class="kng-maintenance-badge"><?php echo esc_html($badge); ?></div>
542 <h1><?php echo esc_html($headline); ?></h1>
543 <p class="kng-maintenance-subhead"><?php echo esc_html($subhead); ?></p>
544
545 <?php if ($launch_label !== '') : ?>
546 <p class="kng-maintenance-launch"><?php echo esc_html($launch_label); ?></p>
547 <?php endif; ?>
548
549 <?php if ($template_id === 'countdown') : ?>
550 <div class="kng-maintenance-countdown">
551 <div><strong><?php echo esc_html($countdown_days); ?></strong><span><?php esc_html_e('Days', 'king-addons'); ?></span></div>
552 <div><strong><?php echo esc_html($countdown_hours); ?></strong><span><?php esc_html_e('Hours', 'king-addons'); ?></span></div>
553 <div><strong><?php echo esc_html($countdown_minutes); ?></strong><span><?php esc_html_e('Minutes', 'king-addons'); ?></span></div>
554 </div>
555 <?php endif; ?>
556
557 <?php if ($template_id === 'progress') : ?>
558 <div class="kng-maintenance-progress">
559 <div class="kng-maintenance-progress-bar">
560 <span style="width: <?php echo esc_attr($progress_percent); ?>%;"></span>
561 </div>
562 <div class="kng-maintenance-progress-label"><?php echo esc_html($progress_label); ?></div>
563 </div>
564 <?php endif; ?>
565
566 <?php if (in_array($template_id, ['subscribe', 'product-launch'], true)) : ?>
567 <form class="kng-maintenance-form">
568 <input type="email" placeholder="<?php echo esc_attr($form_placeholder); ?>" aria-label="<?php echo esc_attr($form_placeholder); ?>">
569 <button type="button"><?php echo esc_html($form_button); ?></button>
570 </form>
571 <?php endif; ?>
572
573 <?php if ($template_id === 'split') : ?>
574 <div class="kng-maintenance-split">
575 <div>
576 <h3><?php echo esc_html($content['split_title_a']); ?></h3>
577 <p><?php echo esc_html($content['split_text_a']); ?></p>
578 </div>
579 <div>
580 <h3><?php echo esc_html($content['split_title_b']); ?></h3>
581 <p><?php echo esc_html($content['split_text_b']); ?></p>
582 </div>
583 </div>
584 <?php endif; ?>
585
586 <div class="kng-maintenance-footer">
587 <span><?php echo esc_html($footer_left); ?></span>
588 <span class="kng-maintenance-dot"></span>
589 <span><?php echo esc_html($footer_right); ?></span>
590 </div>
591 </div>
592 <div class="kng-maintenance-orb"></div>
593 <div class="kng-maintenance-orb secondary"></div>
594 </section>
595 <?php
596 return ob_get_clean();
597 }
598
599 private function get_builtin_templates(): array
600 {
601 return [
602 'minimal' => __('Minimal', 'king-addons'),
603 'dark' => __('Dark', 'king-addons'),
604 'gradient' => __('Gradient', 'king-addons'),
605 'countdown' => __('Coming Soon Countdown', 'king-addons'),
606 'progress' => __('Maintenance Progress', 'king-addons'),
607 'subscribe' => __('Simple Subscribe', 'king-addons'),
608 'product-launch' => __('Product Launch', 'king-addons'),
609 'construction' => __('Under Construction', 'king-addons'),
610 'split' => __('Split Layout', 'king-addons'),
611 'logo' => __('Centered Logo', 'king-addons'),
612 ];
613 }
614
615 public function get_pro_templates(): array
616 {
617 return [
618 'countdown',
619 'progress',
620 'product-launch',
621 'split',
622 ];
623 }
624
625 public function handle_export(): void
626 {
627 if (!current_user_can('manage_options')) {
628 wp_die(esc_html__('Unauthorized request.', 'king-addons'));
629 }
630
631 check_admin_referer('kng_maintenance_export');
632
633 $settings = $this->get_settings();
634
635 header('Content-Type: application/json; charset=utf-8');
636 header('Content-Disposition: attachment; filename=maintenance-mode-settings.json');
637 echo wp_json_encode($settings);
638 exit;
639 }
640
641 public function handle_import(): void
642 {
643 if (!current_user_can('manage_options')) {
644 wp_die(esc_html__('Unauthorized request.', 'king-addons'));
645 }
646
647 check_admin_referer('kng_maintenance_import');
648
649 if (empty($_FILES['import_file']) || !isset($_FILES['import_file']['tmp_name'])) {
650 $this->redirect_with_message('import-export', 'error_import');
651 }
652
653 $file = $_FILES['import_file'];
654 if (!empty($file['error'])) {
655 $this->redirect_with_message('import-export', 'error_import');
656 }
657
658 $contents = file_get_contents($file['tmp_name']);
659 if ($contents === false) {
660 $this->redirect_with_message('import-export', 'error_import');
661 }
662
663 $decoded = json_decode($contents, true);
664 if (!is_array($decoded)) {
665 $this->redirect_with_message('import-export', 'error_import');
666 }
667
668 $sanitized = $this->sanitize_settings($decoded);
669 update_option(self::OPTION_NAME, $sanitized);
670
671 $this->redirect_with_message('import-export', 'imported');
672 }
673
674 private function get_default_settings(): array
675 {
676 return [
677 'enabled' => false,
678 'mode' => 'coming_soon',
679 'template_source' => 'built_in',
680 'template_id' => 'minimal',
681 'template_content' => [],
682 'page_id' => 0,
683 'elementor_id' => 0,
684 'theme' => 'dark',
685 'noindex' => true,
686 'retry_after' => 3600,
687 'whitelist_ips' => [],
688 'whitelist_paths' => [],
689 'exclude_admin' => true,
690 'allowed_roles' => [],
691 'schedule_enabled' => false,
692 'schedule_start' => '',
693 'schedule_end' => '',
694 'allow_rest' => true,
695 'allow_admin_ajax' => true,
696 'disable_elementor_editor' => true,
697 'custom_css' => '',
698 'custom_js' => '',
699 ];
700 }
701
702 public function get_settings(): array
703 {
704 $defaults = $this->get_default_settings();
705 $saved = get_option(self::OPTION_NAME, []);
706
707 $settings = wp_parse_args($saved, $defaults);
708 if (!is_array($settings['template_content'])) {
709 $settings['template_content'] = $defaults['template_content'];
710 }
711 if (!is_array($settings['whitelist_ips'])) {
712 $settings['whitelist_ips'] = $defaults['whitelist_ips'];
713 }
714 if (!is_array($settings['whitelist_paths'])) {
715 $settings['whitelist_paths'] = $defaults['whitelist_paths'];
716 }
717 if (!is_array($settings['allowed_roles'])) {
718 $settings['allowed_roles'] = $defaults['allowed_roles'];
719 }
720
721 return $settings;
722 }
723
724 public function sanitize_settings(array $settings): array
725 {
726 $defaults = $this->get_default_settings();
727 $existing = $this->get_settings();
728 $settings = wp_parse_args($settings, $existing);
729 $templates = array_keys($this->get_builtin_templates());
730 $pro_templates = $this->get_pro_templates();
731
732 $source = isset($settings['template_source']) ? sanitize_key($settings['template_source']) : $defaults['template_source'];
733 if (!in_array($source, ['built_in', 'page', 'elementor'], true)) {
734 $source = 'built_in';
735 }
736
737 $template_id = isset($settings['template_id']) ? sanitize_key($settings['template_id']) : $defaults['template_id'];
738 if (!in_array($template_id, $templates, true)) {
739 $template_id = $defaults['template_id'];
740 }
741 if (!$this->is_pro() && in_array($template_id, $pro_templates, true)) {
742 $template_id = 'minimal';
743 }
744
745 $mode = isset($settings['mode']) ? sanitize_key($settings['mode']) : $defaults['mode'];
746 if (!in_array($mode, ['coming_soon', 'maintenance'], true)) {
747 $mode = $defaults['mode'];
748 }
749
750 $theme = isset($settings['theme']) ? sanitize_key($settings['theme']) : $defaults['theme'];
751 $theme = $theme === 'light' ? 'light' : 'dark';
752
753 $whitelist_ips = $this->sanitize_list($settings['whitelist_ips'] ?? []);
754 $whitelist_paths = $this->sanitize_list($settings['whitelist_paths'] ?? []);
755
756 if (!$this->is_pro()) {
757 $whitelist_ips = array_slice($whitelist_ips, 0, 10);
758 $whitelist_paths = array_slice($whitelist_paths, 0, 10);
759 }
760
761 $clean = [
762 'enabled' => !empty($settings['enabled']),
763 'mode' => $mode,
764 'template_source' => $source,
765 'template_id' => $template_id,
766 'template_content' => $this->sanitize_template_content($settings['template_content'] ?? []),
767 'page_id' => absint($settings['page_id'] ?? 0),
768 'elementor_id' => absint($settings['elementor_id'] ?? 0),
769 'theme' => $theme,
770 'noindex' => !empty($settings['noindex']),
771 'retry_after' => max(0, absint($settings['retry_after'] ?? $defaults['retry_after'])),
772 'whitelist_ips' => $whitelist_ips,
773 'whitelist_paths' => $this->normalize_paths($whitelist_paths),
774 'exclude_admin' => !empty($settings['exclude_admin']),
775 'allowed_roles' => $this->sanitize_list($settings['allowed_roles'] ?? []),
776 'schedule_enabled' => !empty($settings['schedule_enabled']),
777 'schedule_start' => $this->sanitize_datetime($settings['schedule_start'] ?? ''),
778 'schedule_end' => $this->sanitize_datetime($settings['schedule_end'] ?? ''),
779 'allow_rest' => !empty($settings['allow_rest']),
780 'allow_admin_ajax' => !empty($settings['allow_admin_ajax']),
781 'disable_elementor_editor' => !empty($settings['disable_elementor_editor']),
782 'custom_css' => '',
783 'custom_js' => '',
784 ];
785
786 if (!$this->is_pro()) {
787 $clean['allowed_roles'] = [];
788 }
789
790 return $clean;
791 }
792
793 public function get_template_content(string $template_id): array
794 {
795 $templates = $this->get_builtin_templates();
796 $template_id = isset($templates[$template_id]) ? $template_id : 'minimal';
797 $template_id = $this->ensure_template_allowed($template_id);
798
799 $defaults = $this->get_template_content_defaults($template_id);
800 $saved = $this->settings['template_content'][$template_id] ?? [];
801 if (!is_array($saved)) {
802 $saved = [];
803 }
804
805 return wp_parse_args($saved, $defaults);
806 }
807
808 private function get_template_content_defaults(string $template_id): array
809 {
810 $mode = $this->settings['mode'] ?? 'coming_soon';
811 $site_name = get_bloginfo('name');
812 $tagline = get_bloginfo('description');
813
814 $headline = $mode === 'maintenance'
815 ? __('We are upgrading the experience.', 'king-addons')
816 : __('A new experience is on the horizon.', 'king-addons');
817
818 $subhead = $tagline !== '' ? $tagline : __('Our team is preparing something beautiful. Stay tuned.', 'king-addons');
819
820 $base = [
821 'badge' => '',
822 'headline' => $headline,
823 'subhead' => $subhead,
824 'launch_label' => '',
825 'footer_left' => $site_name,
826 'footer_right' => __('Powered by King Addons', 'king-addons'),
827 ];
828
829 if ($template_id === 'countdown') {
830 $base['countdown_days'] = '14';
831 $base['countdown_hours'] = '06';
832 $base['countdown_minutes'] = '42';
833 }
834
835 if ($template_id === 'progress') {
836 $base['progress_percent'] = 68;
837 $base['progress_label'] = __('68% complete', 'king-addons');
838 }
839
840 if (in_array($template_id, ['subscribe', 'product-launch'], true)) {
841 $base['form_placeholder'] = __('Email address', 'king-addons');
842 $base['form_button'] = __('Notify me', 'king-addons');
843 }
844
845 if ($template_id === 'split') {
846 $base['split_title_a'] = __('What is happening', 'king-addons');
847 $base['split_text_a'] = __('We are refining the experience with faster performance and new visuals.', 'king-addons');
848 $base['split_title_b'] = __('Stay connected', 'king-addons');
849 $base['split_text_b'] = __('Follow our updates while we prepare the launch.', 'king-addons');
850 }
851
852 return $base;
853 }
854
855 private function sanitize_template_content(array $content): array
856 {
857 $fields = $this->get_template_content_fields();
858 $clean = [];
859
860 foreach ($fields as $template_id => $template_fields) {
861 $values = isset($content[$template_id]) && is_array($content[$template_id]) ? $content[$template_id] : [];
862 $template_clean = [];
863
864 foreach ($template_fields as $field => $type) {
865 $value = $values[$field] ?? '';
866 if ($type === 'int') {
867 $int = absint($value);
868 if ($field === 'progress_percent') {
869 $int = min(100, max(0, $int));
870 }
871 if ($field === 'countdown_hours') {
872 $int = min(23, max(0, $int));
873 }
874 if ($field === 'countdown_minutes') {
875 $int = min(59, max(0, $int));
876 }
877 $template_clean[$field] = $int;
878 } else {
879 $template_clean[$field] = sanitize_text_field((string) $value);
880 }
881 }
882
883 $clean[$template_id] = $template_clean;
884 }
885
886 return $clean;
887 }
888
889 private function get_template_content_fields(): array
890 {
891 $base = [
892 'badge' => 'text',
893 'headline' => 'text',
894 'subhead' => 'text',
895 'launch_label' => 'text',
896 'footer_left' => 'text',
897 'footer_right' => 'text',
898 ];
899
900 return [
901 'minimal' => $base,
902 'dark' => $base,
903 'gradient' => $base,
904 'construction' => $base,
905 'logo' => $base,
906 'countdown' => $base + [
907 'countdown_days' => 'int',
908 'countdown_hours' => 'int',
909 'countdown_minutes' => 'int',
910 ],
911 'progress' => $base + [
912 'progress_percent' => 'int',
913 'progress_label' => 'text',
914 ],
915 'subscribe' => $base + [
916 'form_placeholder' => 'text',
917 'form_button' => 'text',
918 ],
919 'product-launch' => $base + [
920 'form_placeholder' => 'text',
921 'form_button' => 'text',
922 ],
923 'split' => $base + [
924 'split_title_a' => 'text',
925 'split_text_a' => 'text',
926 'split_title_b' => 'text',
927 'split_text_b' => 'text',
928 ],
929 ];
930 }
931
932 private function ensure_template_allowed(string $template_id): string
933 {
934 if (!$this->is_pro() && in_array($template_id, $this->get_pro_templates(), true)) {
935 return 'minimal';
936 }
937
938 return $template_id;
939 }
940
941 private function sanitize_list($value): array
942 {
943 if (is_array($value)) {
944 $items = $value;
945 } else {
946 $items = preg_split('/\\r\\n|\\r|\\n|,/', (string) $value);
947 }
948
949 $items = array_map('trim', $items);
950 $items = array_filter($items, static function ($item) {
951 return $item !== '';
952 });
953 $items = array_map('sanitize_text_field', $items);
954
955 return array_values(array_unique($items));
956 }
957
958 private function normalize_paths(array $paths): array
959 {
960 $normalized = [];
961 foreach ($paths as $path) {
962 $path = wp_parse_url($path, PHP_URL_PATH) ?: $path;
963 $path = '/' . ltrim((string) $path, '/');
964 $normalized[] = $path === '' ? '/' : $path;
965 }
966
967 return array_values(array_unique($normalized));
968 }
969
970 private function sanitize_datetime(string $value): string
971 {
972 $value = sanitize_text_field($value);
973 if ($value === '') {
974 return '';
975 }
976
977 $value = str_replace('T', ' ', $value);
978 if (strlen($value) === 16) {
979 $value .= ':00';
980 }
981
982 $timestamp = strtotime($value);
983 if (!$timestamp) {
984 return '';
985 }
986
987 return get_gmt_from_date($value, 'Y-m-d H:i:s');
988 }
989
990 private function parse_schedule_time(string $value): int
991 {
992 if ($value === '') {
993 return 0;
994 }
995
996 return (int) strtotime($value . ' UTC');
997 }
998
999 private function get_client_ip(): string
1000 {
1001 if (!empty($_SERVER['REMOTE_ADDR']) && filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)) {
1002 return (string) $_SERVER['REMOTE_ADDR'];
1003 }
1004
1005 return '';
1006 }
1007
1008 private function is_preview_request(): bool
1009 {
1010 if (empty($_GET['kng_maintenance_preview'])) {
1011 return false;
1012 }
1013
1014 if (!current_user_can('manage_options')) {
1015 return false;
1016 }
1017
1018 $nonce = isset($_GET['_kng_preview_nonce']) ? sanitize_text_field(wp_unslash($_GET['_kng_preview_nonce'])) : '';
1019 if ($nonce === '' || !wp_verify_nonce($nonce, 'kng_maintenance_preview')) {
1020 return false;
1021 }
1022
1023 return true;
1024 }
1025
1026 private function is_pro(): bool
1027 {
1028 return function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only();
1029 }
1030
1031 private function redirect_with_message(string $view, string $message): void
1032 {
1033 $args = [
1034 'page' => 'king-addons-maintenance-mode',
1035 'view' => $view,
1036 'message' => $message,
1037 ];
1038
1039 wp_safe_redirect(add_query_arg($args, admin_url('admin.php')));
1040 exit;
1041 }
1042 }
1043