| @@ -249,13 +249,17 @@ | ||
| 249 | 249 | * @return void |
| 250 | 250 | */ |
| 251 | 251 | public function enqueue_front_assets(): void |
| 252 | 252 | { |
| 253 | + if (defined('KING_ADDONS_IS_MAINTENANCE_PAGE') && KING_ADDONS_IS_MAINTENANCE_PAGE) { | |
| 254 | + return; | |
| 255 | + } | |
| 256 | + | |
| 253 | 257 | if (is_admin()) { |
| 254 | 258 | return; |
| 255 | 259 | } |
| 256 | 260 | |
| 257 | - $should_render = $this->should_render() || $this->should_render_block_state(); | |
| 261 | + $should_render = $this->should_render() || $this->should_render_block_state() || $this->is_preview_mode(); | |
| 258 | 262 | |
| 259 | 263 | if (!$should_render) { |
| 260 | 264 | return; |
| 261 | 265 | } |
| @@ -287,11 +291,15 @@ | ||
| 287 | 291 | * @return void |
| 288 | 292 | */ |
| 289 | 293 | public function render_frontend_markup(): void |
| 290 | 294 | { |
| 291 | - if (!$this->should_render() && !$this->should_render_block_state()) { | |
| 295 | + if (defined('KING_ADDONS_IS_MAINTENANCE_PAGE') && KING_ADDONS_IS_MAINTENANCE_PAGE) { | |
| 292 | 296 | return; |
| 293 | 297 | } |
| 298 | + | |
| 299 | + if (!$this->should_render() && !$this->should_render_block_state() && !$this->is_preview_mode()) { | |
| 300 | + return; | |
| 301 | + } | |
| 294 | 302 | ?> |
| 295 | 303 | <div id="king-addons-age-gate" class="king-addons-age-gate" aria-hidden="true"> |
| 296 | 304 | <div class="king-addons-age-gate__overlay"></div> |
| 297 | 305 | <div class="king-addons-age-gate__card" role="dialog" aria-modal="true" aria-label="<?php echo esc_attr($this->options['design']['title']); ?>"> |
| @@ -307,12 +315,16 @@ | ||
| 307 | 315 | * @return void |
| 308 | 316 | */ |
| 309 | 317 | public function render_portal_container(): void |
| 310 | 318 | { |
| 311 | - if (!$this->should_render() && !$this->should_render_block_state()) { | |
| 319 | + if (defined('KING_ADDONS_IS_MAINTENANCE_PAGE') && KING_ADDONS_IS_MAINTENANCE_PAGE) { | |
| 312 | 320 | return; |
| 313 | 321 | } |
| 314 | 322 | |
| 323 | + if (!$this->should_render() && !$this->should_render_block_state() && !$this->is_preview_mode()) { | |
| 324 | + return; | |
| 325 | + } | |
| 326 | + | |
| 315 | 327 | echo '<div id="king-addons-age-gate-portal" class="king-addons-age-gate__portal" aria-hidden="true"></div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 316 | 328 | } |
| 317 | 329 | |
| 318 | 330 | /** |
| @@ -321,8 +333,12 @@ | ||
| 321 | 333 | * @return void |
| 322 | 334 | */ |
| 323 | 335 | public function maybe_handle_denied_redirect(): void |
| 324 | 336 | { |
| 337 | + if (defined('KING_ADDONS_IS_MAINTENANCE_PAGE') && KING_ADDONS_IS_MAINTENANCE_PAGE) { | |
| 338 | + return; | |
| 339 | + } | |
| 340 | + | |
| 325 | 341 | if (is_admin() || wp_doing_ajax()) { |
| 326 | 342 | return; |
| 327 | 343 | } |
| 328 | 344 | |
| @@ -331,23 +347,44 @@ | ||
| 331 | 347 | } |
| 332 | 348 | |
| 333 | 349 | $status = $this->get_cookie_status(); |
| 334 | 350 | $behaviour = $this->options['behaviour']; |
| 335 | - $redirect_id = isset($behaviour['deny_redirect_page']) ? (int)$behaviour['deny_redirect_page'] : 0; | |
| 336 | 351 | |
| 337 | - if ($status !== 'denied' || $behaviour['deny_action'] !== 'redirect' || !$redirect_id) { | |
| 352 | + if ($status !== 'denied' || !$this->is_deny_action_redirect($behaviour['deny_action'] ?? '')) { | |
| 338 | 353 | return; |
| 339 | 354 | } |
| 340 | 355 | |
| 341 | - if (is_page($redirect_id)) { | |
| 356 | + $deny_action = (string) ($behaviour['deny_action'] ?? ''); | |
| 357 | + if ($deny_action === 'redirect_page' || $deny_action === 'redirect') { | |
| 358 | + $redirect_id = isset($behaviour['deny_redirect_page']) ? (int) $behaviour['deny_redirect_page'] : 0; | |
| 359 | + if ($redirect_id && is_page($redirect_id)) { | |
| 360 | + return; | |
| 361 | + } | |
| 362 | + } | |
| 363 | + | |
| 364 | + $url = $this->resolve_deny_redirect_url(); | |
| 365 | + if (!$url) { | |
| 342 | 366 | return; |
| 343 | 367 | } |
| 344 | 368 | |
| 345 | - $url = get_permalink($redirect_id); | |
| 346 | - if ($url) { | |
| 369 | + $request_uri = isset($_SERVER['REQUEST_URI']) ? (string) wp_unslash($_SERVER['REQUEST_URI']) : ''; | |
| 370 | + if ($request_uri) { | |
| 371 | + $current_url = home_url($request_uri); | |
| 372 | + if (untrailingslashit($current_url) === untrailingslashit($url)) { | |
| 373 | + return; | |
| 374 | + } | |
| 375 | + } | |
| 376 | + | |
| 377 | + $site_host = wp_parse_url(home_url(), PHP_URL_HOST); | |
| 378 | + $target_host = wp_parse_url($url, PHP_URL_HOST); | |
| 379 | + | |
| 380 | + // Prefer safe redirects for internal destinations; allow external URLs when configured by admin. | |
| 381 | + if ($site_host && $target_host && strtolower((string) $site_host) === strtolower((string) $target_host)) { | |
| 347 | 382 | wp_safe_redirect($url); |
| 348 | - exit; | |
| 383 | + } else { | |
| 384 | + wp_redirect($url); | |
| 349 | 385 | } |
| 386 | + exit; | |
| 350 | 387 | } |
| 351 | 388 | |
| 352 | 389 | /** |
| 353 | 390 | * Determines whether the overlay should render. |
| @@ -355,8 +392,12 @@ | ||
| 355 | 392 | * @return bool |
| 356 | 393 | */ |
| 357 | 394 | public function should_render(): bool |
| 358 | 395 | { |
| 396 | + if (defined('KING_ADDONS_IS_MAINTENANCE_PAGE') && KING_ADDONS_IS_MAINTENANCE_PAGE) { | |
| 397 | + return false; | |
| 398 | + } | |
| 399 | + | |
| 359 | 400 | if (is_admin() || wp_doing_ajax()) { |
| 360 | 401 | return false; |
| 361 | 402 | } |
| 362 | 403 | |
| @@ -363,8 +404,12 @@ | ||
| 363 | 404 | if (!$this->is_enabled()) { |
| 364 | 405 | return false; |
| 365 | 406 | } |
| 366 | 407 | |
| 408 | + if ($this->is_preview_mode()) { | |
| 409 | + return true; | |
| 410 | + } | |
| 411 | + | |
| 367 | 412 | if (!$this->passes_display_rules()) { |
| 368 | 413 | return false; |
| 369 | 414 | } |
| 370 | 415 | |
| @@ -377,9 +422,9 @@ | ||
| 377 | 422 | if ($status === 'allowed') { |
| 378 | 423 | return false; |
| 379 | 424 | } |
| 380 | 425 | |
| 381 | - if ($status === 'denied' && $this->options['behaviour']['deny_action'] === 'redirect') { | |
| 426 | + if ($status === 'denied' && $this->is_deny_action_redirect($this->options['behaviour']['deny_action'] ?? '') && $this->has_deny_redirect_target()) { | |
| 382 | 427 | return false; |
| 383 | 428 | } |
| 384 | 429 | |
| 385 | 430 | return true; |
| @@ -391,14 +436,18 @@ | ||
| 391 | 436 | * @return bool |
| 392 | 437 | */ |
| 393 | 438 | protected function should_render_block_state(): bool |
| 394 | 439 | { |
| 440 | + if (defined('KING_ADDONS_IS_MAINTENANCE_PAGE') && KING_ADDONS_IS_MAINTENANCE_PAGE) { | |
| 441 | + return false; | |
| 442 | + } | |
| 443 | + | |
| 395 | 444 | if (!$this->is_enabled()) { |
| 396 | 445 | return false; |
| 397 | 446 | } |
| 398 | 447 | |
| 399 | 448 | $status = $this->get_cookie_status(); |
| 400 | - return $status === 'denied' && $this->options['behaviour']['deny_action'] === 'block'; | |
| 449 | + return $status === 'denied' && (($this->options['behaviour']['deny_action'] ?? '') === 'block'); | |
| 401 | 450 | } |
| 402 | 451 | |
| 403 | 452 | /** |
| 404 | 453 | * Checks if general display rules allow the gate. |
| @@ -420,9 +469,9 @@ | ||
| 420 | 469 | return is_singular('post'); |
| 421 | 470 | } |
| 422 | 471 | |
| 423 | 472 | if ($scope === 'pages') { |
| 424 | - return is_page(); | |
| 473 | + return $this->is_pages_scope_match(); | |
| 425 | 474 | } |
| 426 | 475 | |
| 427 | 476 | return true; |
| 428 | 477 | } |
| @@ -427,8 +476,23 @@ | ||
| 427 | 476 | return true; |
| 428 | 477 | } |
| 429 | 478 | |
| 430 | 479 | /** |
| 480 | + * Pages scope includes WordPress pages and the WooCommerce shop archive. | |
| 481 | + * | |
| 482 | + * WooCommerce shop is a product archive, so is_page() is false there even | |
| 483 | + * when the Shop page ID is selected as a page. | |
| 484 | + */ | |
| 485 | + protected function is_pages_scope_match(): bool | |
| 486 | + { | |
| 487 | + if (is_page()) { | |
| 488 | + return true; | |
| 489 | + } | |
| 490 | + | |
| 491 | + return function_exists('is_shop') && is_shop(); | |
| 492 | + } | |
| 493 | + | |
| 494 | + /** | |
| 431 | 495 | * Checks if current page is excluded from gating. |
| 432 | 496 | * |
| 433 | 497 | * @return bool |
| 434 | 498 | */ |
| @@ -433,13 +497,21 @@ | ||
| 433 | 497 | * @return bool |
| 434 | 498 | */ |
| 435 | 499 | protected function is_excluded_page(): bool |
| 436 | 500 | { |
| 501 | + $exclude_ids = array_map('absint', $this->options['display']['exclude_ids']); | |
| 502 | + | |
| 503 | + if (function_exists('is_shop') && is_shop() && function_exists('wc_get_page_id')) { | |
| 504 | + $shop_id = (int) wc_get_page_id('shop'); | |
| 505 | + if ($shop_id && in_array($shop_id, $exclude_ids, true)) { | |
| 506 | + return true; | |
| 507 | + } | |
| 508 | + } | |
| 509 | + | |
| 437 | 510 | if (!is_singular()) { |
| 438 | 511 | return false; |
| 439 | 512 | } |
| 440 | 513 | |
| 441 | - $exclude_ids = array_map('absint', $this->options['display']['exclude_ids']); | |
| 442 | 514 | $current_id = get_the_ID(); |
| 443 | 515 | |
| 444 | 516 | if (in_array($current_id, $exclude_ids, true)) { |
| 445 | 517 | return true; |
| @@ -464,8 +536,16 @@ | ||
| 464 | 536 | $behaviour = $this->options['behaviour']; |
| 465 | 537 | $general = $this->options['general']; |
| 466 | 538 | $dob = $this->options['dob']; |
| 467 | 539 | |
| 540 | + $deny_redirect_id = isset($behaviour['deny_redirect_page']) ? (int) $behaviour['deny_redirect_page'] : 0; | |
| 541 | + $deny_redirect_url = $this->resolve_deny_redirect_url(); | |
| 542 | + $deny_action_raw = (string) ($behaviour['deny_action'] ?? 'block'); | |
| 543 | + $deny_action = $this->is_deny_action_redirect($deny_action_raw) ? 'redirect' : 'block'; | |
| 544 | + if ($deny_action === 'redirect' && !$deny_redirect_url) { | |
| 545 | + $deny_action = 'block'; | |
| 546 | + } | |
| 547 | + | |
| 468 | 548 | return [ |
| 469 | 549 | 'enabled' => $this->is_enabled(), |
| 470 | 550 | 'mode' => $general['mode'], |
| 471 | 551 | 'minAge' => $this->resolve_minimum_age(), |
| @@ -500,14 +580,14 @@ | ||
| 500 | 580 | 'logo' => $design['logo'], |
| 501 | 581 | 'backgroundImage' => $design['background_image'], |
| 502 | 582 | ], |
| 503 | 583 | 'behaviour' => [ |
| 504 | - 'denyAction' => $behaviour['deny_action'], | |
| 505 | - 'denyRedirect' => (int) $behaviour['deny_redirect_page'], | |
| 506 | - 'denyRedirectUrl' => $behaviour['deny_redirect_page'] ? get_permalink((int) $behaviour['deny_redirect_page']) : '', | |
| 584 | + 'denyAction' => $deny_action, | |
| 585 | + 'denyRedirect' => $deny_redirect_id, | |
| 586 | + 'denyRedirectUrl' => $deny_redirect_url, | |
| 507 | 587 | 'blockMessage' => $behaviour['block_message'], |
| 508 | 588 | 'consentCheckbox' => (bool) $behaviour['consent_checkbox'], |
| 509 | - 'consentLabel' => esc_html__('I agree to the policy.', 'king-addons'), | |
| 589 | + 'consentLabel' => __('I agree to the policy.', 'king-addons'), | |
| 510 | 590 | 'repeatMode' => $behaviour['repeat_mode'] ?? 'days', |
| 511 | 591 | 'repeatDays' => (int) ($behaviour['repeat_days'] ?? 30), |
| 512 | 592 | ], |
| 513 | 593 | 'cookie' => [ |
| @@ -524,9 +604,10 @@ | ||
| 524 | 604 | 'denied' => $dob['error_denied'], |
| 525 | 605 | ], |
| 526 | 606 | ], |
| 527 | 607 | 'status' => $this->get_cookie_status(), |
| 528 | - 'shouldRender' => $this->should_render(), | |
| 608 | + 'shouldRender' => $this->should_render() || $this->is_preview_mode(), | |
| 609 | + 'isPreview' => $this->is_preview_mode(), | |
| 529 | 610 | 'ajax' => [ |
| 530 | 611 | 'url' => admin_url('admin-ajax.php'), |
| 531 | 612 | 'nonce' => wp_create_nonce(self::NONCE_ACTION), |
| 532 | 613 | 'action' => self::AJAX_ACTION_DOB, |
| @@ -536,8 +617,29 @@ | ||
| 536 | 617 | ]; |
| 537 | 618 | } |
| 538 | 619 | |
| 539 | 620 | /** |
| 621 | + * Preview mode: force render Age Gate for admins via query param. | |
| 622 | + * Example: /?ka_age_gate_preview=1 | |
| 623 | + */ | |
| 624 | + protected function is_preview_mode(): bool | |
| 625 | + { | |
| 626 | + if (is_admin() || wp_doing_ajax()) { | |
| 627 | + return false; | |
| 628 | + } | |
| 629 | + | |
| 630 | + if (!$this->is_enabled()) { | |
| 631 | + return false; | |
| 632 | + } | |
| 633 | + | |
| 634 | + if (!is_user_logged_in() || !current_user_can('manage_options')) { | |
| 635 | + return false; | |
| 636 | + } | |
| 637 | + | |
| 638 | + return isset($_GET['ka_age_gate_preview']) && (string) $_GET['ka_age_gate_preview'] === '1'; | |
| 639 | + } | |
| 640 | + | |
| 641 | + /** | |
| 540 | 642 | * Indicates whether premium code is available. |
| 541 | 643 | * |
| 542 | 644 | * @return bool |
| 543 | 645 | */ |
| @@ -561,8 +663,12 @@ | ||
| 561 | 663 | * @return array<string, mixed> |
| 562 | 664 | */ |
| 563 | 665 | public function get_options(): array |
| 564 | 666 | { |
| 667 | + if (class_exists('King_Addons\\Text_Entities_Migration')) { | |
| 668 | + Text_Entities_Migration::maybe_run(); | |
| 669 | + } | |
| 670 | + | |
| 565 | 671 | $saved = get_option(self::OPTION_NAME, []); |
| 566 | 672 | $defaults = $this->get_default_options(); |
| 567 | 673 | |
| 568 | 674 | return wp_parse_args($saved, $defaults); |
| @@ -601,12 +707,12 @@ | ||
| 601 | 707 | 'overlay_opacity' => 0.7, |
| 602 | 708 | 'card_background' => '#ffffff', |
| 603 | 709 | 'card_width' => 520, |
| 604 | 710 | 'card_align' => 'center', |
| 605 | - 'title' => esc_html__('Age verification required', 'king-addons'), | |
| 606 | - 'subtitle' => esc_html__('This content is restricted to visitors over the specified age.', 'king-addons'), | |
| 607 | - 'button_yes' => esc_html__('Yes, continue', 'king-addons'), | |
| 608 | - 'button_no' => esc_html__('No, leave', 'king-addons'), | |
| 711 | + 'title' => __('Age verification required', 'king-addons'), | |
| 712 | + 'subtitle' => __('This content is restricted to visitors over the specified age.', 'king-addons'), | |
| 713 | + 'button_yes' => __('Yes, continue', 'king-addons'), | |
| 714 | + 'button_no' => __('No, leave', 'king-addons'), | |
| 609 | 715 | 'text_color' => '#111827', |
| 610 | 716 | 'title_size' => 24, |
| 611 | 717 | 'body_size' => 16, |
| 612 | 718 | 'title_weight' => 700, |
| @@ -624,11 +730,12 @@ | ||
| 624 | 730 | 'button_no_hover_color' => '#ffffff', |
| 625 | 731 | 'elementor_template' => 0, |
| 626 | 732 | ], |
| 627 | 733 | 'behaviour' => [ |
| 628 | - 'deny_action' => 'redirect', | |
| 734 | + 'deny_action' => 'redirect_url', | |
| 629 | 735 | 'deny_redirect_page' => 0, |
| 630 | - 'block_message' => esc_html__('Access denied. You do not meet the minimum age requirement.', 'king-addons'), | |
| 736 | + 'deny_redirect_url' => 'https://google.com', | |
| 737 | + 'block_message' => __('Access denied. You do not meet the minimum age requirement.', 'king-addons'), | |
| 631 | 738 | 'consent_checkbox' => false, |
| 632 | 739 | 'reset_on_rule_change' => true, |
| 633 | 740 | 'repeat_mode' => 'days', |
| 634 | 741 | 'repeat_days' => 30, |
| @@ -643,10 +750,10 @@ | ||
| 643 | 750 | ], |
| 644 | 751 | 'dob' => [ |
| 645 | 752 | 'format' => 'dmy', |
| 646 | 753 | 'max_age' => 120, |
| 647 | - 'error_invalid' => esc_html__('Enter a valid date of birth.', 'king-addons'), | |
| 648 | - 'error_denied' => esc_html__('You do not meet the minimum age requirement.', 'king-addons'), | |
| 754 | + 'error_invalid' => __('Enter a valid date of birth.', 'king-addons'), | |
| 755 | + 'error_denied' => __('You do not meet the minimum age requirement.', 'king-addons'), | |
| 649 | 756 | ], |
| 650 | 757 | ]; |
| 651 | 758 | } |
| 652 | 759 | |
| @@ -666,8 +773,37 @@ | ||
| 666 | 773 | $display = $raw['display'] ?? []; |
| 667 | 774 | $design = $raw['design'] ?? []; |
| 668 | 775 | $behaviour = $raw['behaviour'] ?? []; |
| 669 | 776 | |
| 777 | + $allowed_templates = [ | |
| 778 | + 'center-card', | |
| 779 | + 'bottom-card', | |
| 780 | + 'top-card', | |
| 781 | + 'side-left', | |
| 782 | + 'side-right', | |
| 783 | + 'fullscreen', | |
| 784 | + ]; | |
| 785 | + | |
| 786 | + $template = in_array($design['template'] ?? 'center-card', $allowed_templates, true) | |
| 787 | + ? $design['template'] | |
| 788 | + : 'center-card'; | |
| 789 | + | |
| 790 | + // Back-compat: if card_align isn't explicitly set, infer it from template. | |
| 791 | + $inferred_align = 'center'; | |
| 792 | + if ($template === 'bottom-card') { | |
| 793 | + $inferred_align = 'bottom'; | |
| 794 | + } elseif ($template === 'top-card') { | |
| 795 | + $inferred_align = 'top'; | |
| 796 | + } | |
| 797 | + | |
| 798 | + $deny_action_raw = (string) ($behaviour['deny_action'] ?? ($defaults['behaviour']['deny_action'] ?? 'redirect_url')); | |
| 799 | + if ($deny_action_raw === 'redirect') { | |
| 800 | + // Back-compat: older saved values. | |
| 801 | + $deny_action_raw = 'redirect_page'; | |
| 802 | + } | |
| 803 | + $allowed_deny_actions = ['redirect_page', 'redirect_url', 'block']; | |
| 804 | + $deny_action = in_array($deny_action_raw, $allowed_deny_actions, true) ? $deny_action_raw : ($defaults['behaviour']['deny_action'] ?? 'redirect_url'); | |
| 805 | + | |
| 670 | 806 | $sanitized = [ |
| 671 | 807 | 'general' => [ |
| 672 | 808 | 'enabled' => !empty($general['enabled']), |
| 673 | 809 | 'audience' => in_array($general['audience'] ?? 'guests', ['guests', 'all'], true) ? $general['audience'] : $defaults['general']['audience'], |
| @@ -679,14 +815,14 @@ | ||
| 679 | 815 | 'scope' => in_array($display['scope'] ?? 'site', ['site', 'posts', 'pages'], true) ? $display['scope'] : $defaults['display']['scope'], |
| 680 | 816 | 'exclude_ids' => array_values(array_filter(array_map('absint', $display['exclude_ids'] ?? []))), |
| 681 | 817 | ], |
| 682 | 818 | 'design' => [ |
| 683 | - 'template' => in_array($design['template'] ?? 'center-card', ['center-card', 'bottom-card'], true) ? $design['template'] : 'center-card', | |
| 819 | + 'template' => $template, | |
| 684 | 820 | 'overlay_color' => sanitize_hex_color($design['overlay_color'] ?? $defaults['design']['overlay_color']) ?: $defaults['design']['overlay_color'], |
| 685 | 821 | 'overlay_opacity' => min(1, max(0, floatval($design['overlay_opacity'] ?? $defaults['design']['overlay_opacity']))), |
| 686 | 822 | 'card_background' => sanitize_hex_color($design['card_background'] ?? $defaults['design']['card_background']) ?: $defaults['design']['card_background'], |
| 687 | 823 | 'card_width' => max(280, absint($design['card_width'] ?? $defaults['design']['card_width'])), |
| 688 | - 'card_align' => in_array($design['card_align'] ?? 'center', ['center', 'bottom'], true) ? $design['card_align'] : 'center', | |
| 824 | + 'card_align' => in_array($design['card_align'] ?? $inferred_align, ['center', 'bottom', 'top'], true) ? ($design['card_align'] ?? $inferred_align) : 'center', | |
| 689 | 825 | 'title' => sanitize_text_field($design['title'] ?? $defaults['design']['title']), |
| 690 | 826 | 'subtitle' => sanitize_text_field($design['subtitle'] ?? $defaults['design']['subtitle']), |
| 691 | 827 | 'button_yes' => sanitize_text_field($design['button_yes'] ?? $defaults['design']['button_yes']), |
| 692 | 828 | 'button_no' => sanitize_text_field($design['button_no'] ?? $defaults['design']['button_no']), |
| @@ -703,10 +839,11 @@ | ||
| 703 | 839 | 'logo' => esc_url_raw($design['logo'] ?? ''), |
| 704 | 840 | 'background_image' => esc_url_raw($design['background_image'] ?? ''), |
| 705 | 841 | ], |
| 706 | 842 | 'behaviour' => [ |
| 707 | - 'deny_action' => in_array($behaviour['deny_action'] ?? 'redirect', ['redirect', 'block'], true) ? $behaviour['deny_action'] : 'redirect', | |
| 843 | + 'deny_action' => $deny_action, | |
| 708 | 844 | 'deny_redirect_page' => absint($behaviour['deny_redirect_page'] ?? 0), |
| 845 | + 'deny_redirect_url' => $this->sanitize_redirect_url($behaviour['deny_redirect_url'] ?? ($defaults['behaviour']['deny_redirect_url'] ?? '')), | |
| 709 | 846 | 'block_message' => sanitize_text_field($behaviour['block_message'] ?? $defaults['behaviour']['block_message']), |
| 710 | 847 | 'consent_checkbox' => !empty($behaviour['consent_checkbox']), |
| 711 | 848 | 'reset_on_rule_change' => isset($behaviour['reset_on_rule_change']) ? (bool) $behaviour['reset_on_rule_change'] : $defaults['behaviour']['reset_on_rule_change'], |
| 712 | 849 | 'repeat_mode' => in_array($behaviour['repeat_mode'] ?? 'days', ['days', 'session', 'once'], true) ? $behaviour['repeat_mode'] : 'days', |
| @@ -790,8 +927,74 @@ | ||
| 790 | 927 | 'max_age' => max(10, absint($dob['max_age'] ?? $defaults['max_age'])), |
| 791 | 928 | 'error_invalid' => sanitize_text_field($dob['error_invalid'] ?? $defaults['error_invalid']), |
| 792 | 929 | 'error_denied' => sanitize_text_field($dob['error_denied'] ?? $defaults['error_denied']), |
| 793 | 930 | ]; |
| 931 | + } | |
| 932 | + | |
| 933 | + /** | |
| 934 | + * Sanitizes a custom redirect URL. | |
| 935 | + * | |
| 936 | + * @param mixed $raw_url Raw URL. | |
| 937 | + * @return string Sanitized URL or empty string. | |
| 938 | + */ | |
| 939 | + protected function sanitize_redirect_url($raw_url): string | |
| 940 | + { | |
| 941 | + $url = is_string($raw_url) ? trim($raw_url) : ''; | |
| 942 | + if ($url === '') { | |
| 943 | + return ''; | |
| 944 | + } | |
| 945 | + | |
| 946 | + $url = esc_url_raw($url); | |
| 947 | + if ($url && wp_http_validate_url($url)) { | |
| 948 | + return $url; | |
| 949 | + } | |
| 950 | + | |
| 951 | + return ''; | |
| 952 | + } | |
| 953 | + | |
| 954 | + /** | |
| 955 | + * Returns the effective redirect destination when denial action is redirect. | |
| 956 | + * Prefers an internal selected page, otherwise falls back to the custom URL. | |
| 957 | + */ | |
| 958 | + protected function resolve_deny_redirect_url(): string | |
| 959 | + { | |
| 960 | + $behaviour = $this->options['behaviour'] ?? []; | |
| 961 | + $deny_action = (string) ($behaviour['deny_action'] ?? ''); | |
| 962 | + | |
| 963 | + // Legacy back-compat. | |
| 964 | + if ($deny_action === 'redirect') { | |
| 965 | + $deny_action = 'redirect_page'; | |
| 966 | + } | |
| 967 | + | |
| 968 | + $custom_url = isset($behaviour['deny_redirect_url']) ? $this->sanitize_redirect_url($behaviour['deny_redirect_url']) : ''; | |
| 969 | + | |
| 970 | + if ($deny_action === 'redirect_url') { | |
| 971 | + return $custom_url; | |
| 972 | + } | |
| 973 | + | |
| 974 | + // redirect_page (default): prefer page ID, but fall back to URL if provided. | |
| 975 | + $redirect_id = isset($behaviour['deny_redirect_page']) ? (int) $behaviour['deny_redirect_page'] : 0; | |
| 976 | + if ($redirect_id) { | |
| 977 | + return get_permalink($redirect_id) ?: ''; | |
| 978 | + } | |
| 979 | + | |
| 980 | + return $custom_url; | |
| 981 | + } | |
| 982 | + | |
| 983 | + /** | |
| 984 | + * True for redirect-based deny actions. | |
| 985 | + */ | |
| 986 | + protected function is_deny_action_redirect(string $deny_action): bool | |
| 987 | + { | |
| 988 | + return in_array($deny_action, ['redirect', 'redirect_page', 'redirect_url'], true); | |
| 989 | + } | |
| 990 | + | |
| 991 | + /** | |
| 992 | + * Whether a denial redirect destination is configured. | |
| 993 | + */ | |
| 994 | + protected function has_deny_redirect_target(): bool | |
| 995 | + { | |
| 996 | + return $this->resolve_deny_redirect_url() !== ''; | |
| 794 | 997 | } |
| 795 | 998 | |
| 796 | 999 | /** |
| 797 | 1000 | * Determines whether the feature is active. |