| @@ -45,23 +45,41 @@ | ||
| 45 | 45 | } |
| 46 | 46 | return self::$instance; |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | + /** | |
| 50 | + * Newsletter list endpoint the welcome step's opt-in posts to. | |
| 51 | + */ | |
| 52 | + const SUBSCRIBE_ENDPOINT = 'https://marketing.sigmative.com/newsletter/rui/lists/6a9943aacbe70/embedded-form-subscribe'; | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * Customer identifier required by the newsletter endpoint. | |
| 56 | + */ | |
| 57 | + const SUBSCRIBE_CUSTOMER_UID = '6a93d39ce0ebd'; | |
| 58 | + | |
| 49 | 59 | // Initialize hooks |
| 50 | 60 | private function init_hooks() { |
| 51 | - add_action( 'wp_ajax_setup_wizard_install_plugins', array( $this, 'install_plugins' ) ); | |
| 61 | + add_action( 'wp_ajax_ultimate_post_kit_setup_wizard_install_plugins', array( $this, 'install_plugins' ) ); | |
| 62 | + add_action( 'wp_ajax_ultimate_post_kit_setup_wizard_subscribe', array( $this, 'ajax_subscribe' ) ); | |
| 52 | 63 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); |
| 53 | 64 | add_action( 'admin_init', array( $this, 'activate_default_widgets' ) ); |
| 54 | 65 | add_action( 'admin_init', array( $this, 'maybe_display_setup_wizard' ) ); |
| 55 | 66 | add_action( 'admin_init', array( $this, 'check_manual_wizard_request' ) ); |
| 56 | 67 | |
| 57 | - if ( function_exists( 'add_filter' ) ) { | |
| 58 | - add_filter( 'auto_update_translation', '__return_false' ); | |
| 59 | - } | |
| 68 | + // NOTE: WordPress manages plugin/translation updates. Do not add filters | |
| 69 | + // that interfere with the built-in update pipeline (wp.org Guideline). | |
| 60 | 70 | } |
| 61 | 71 | |
| 62 | 72 | // Check for manual wizard requests |
| 63 | 73 | public function check_manual_wizard_request() { |
| 74 | + // This runs on admin_init, which also fires on admin-ajax.php before any | |
| 75 | + // authentication, and on every admin screen for every logged-in role. The setup | |
| 76 | + // wizard is an administrator-only flow, so gate it explicitly. | |
| 77 | + if ( wp_doing_ajax() || ! current_user_can( 'manage_options' ) ) { | |
| 78 | + return; | |
| 79 | + } | |
| 80 | + | |
| 81 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only check of a GET flag to decide whether to render the setup wizard screen, no form data processed. | |
| 64 | 82 | $is_setup_wizard_request = isset($_GET['upk_setup_wizard']) && $_GET['upk_setup_wizard'] === 'show'; |
| 65 | 83 | |
| 66 | 84 | if ( $is_setup_wizard_request ) { |
| 67 | 85 | // Use the same approach as first activation - completely override the page |
| @@ -150,8 +168,15 @@ | ||
| 150 | 168 | } |
| 151 | 169 | |
| 152 | 170 | // Check if this is first activation and display setup wizard if needed |
| 153 | 171 | public function maybe_display_setup_wizard() { |
| 172 | + // This runs on admin_init, which also fires on admin-ajax.php before any | |
| 173 | + // authentication, and on every admin screen for every logged-in role. The setup | |
| 174 | + // wizard is an administrator-only flow, so gate it explicitly. | |
| 175 | + if ( wp_doing_ajax() || ! current_user_can( 'manage_options' ) ) { | |
| 176 | + return; | |
| 177 | + } | |
| 178 | + | |
| 154 | 179 | // Only check for first activation here |
| 155 | 180 | if ( get_option( 'bdtupk_setup_wizard_completed' ) === false ) { |
| 156 | 181 | // Set the flag so it doesn't run again |
| 157 | 182 | update_option( 'bdtupk_setup_wizard_completed', true ); |
| @@ -214,12 +239,20 @@ | ||
| 214 | 239 | |
| 215 | 240 | // Enqueue necessary scripts |
| 216 | 241 | public function enqueue_scripts() { |
| 217 | 242 | |
| 243 | + // Loaded on admin_enqueue_scripts for every admin screen and every role. The | |
| 244 | + // wizard's assets and its nonce have no business outside an administrator's | |
| 245 | + // wizard/settings screen. | |
| 246 | + if ( ! current_user_can( 'manage_options' ) ) { | |
| 247 | + return; | |
| 248 | + } | |
| 249 | + | |
| 250 | + | |
| 218 | 251 | $direction_suffix = is_rtl() ? '.rtl' : ''; |
| 219 | 252 | |
| 220 | 253 | wp_enqueue_style('bdt-uikit', BDTUPK_ADMIN_ASSETS_URL . 'css/bdt-uikit' . $direction_suffix . '.css', [], '3.17.0'); |
| 221 | - wp_enqueue_script('bdt-uikit', BDTUPK_ADMIN_ASSETS_URL . 'js/bdt-uikit.min.js', ['jquery'], '3.17.0'); | |
| 254 | + wp_enqueue_script('bdt-uikit', BDTUPK_ADMIN_ASSETS_URL . 'js/bdt-uikit.min.js', ['jquery'], '3.17.0', true); | |
| 222 | 255 | |
| 223 | 256 | wp_register_script( 'upk-setup-wizard', plugins_url( 'assets/js/setup-wizard.js', __FILE__ ), array( 'jquery' ), '1.0.0', true ); |
| 224 | 257 | wp_register_style( 'upk-setup-wizard', plugins_url( 'assets/css/setup-wizard.css', __FILE__ ), array(), '1.0.0' ); |
| 225 | 258 | |
| @@ -227,12 +260,12 @@ | ||
| 227 | 260 | wp_enqueue_style( 'upk-setup-wizard' ); |
| 228 | 261 | |
| 229 | 262 | wp_localize_script( |
| 230 | 263 | 'upk-setup-wizard', |
| 231 | - 'BDT_SetupWizard', | |
| 264 | + 'UPK_SetupWizard', | |
| 232 | 265 | array( |
| 233 | 266 | 'ajax_url' => admin_url( 'admin-ajax.php' ), |
| 234 | - 'nonce' => wp_create_nonce( 'setup_wizard_nonce' ), | |
| 267 | + 'nonce' => wp_create_nonce( 'ultimate_post_kit_setup_wizard_nonce' ), | |
| 235 | 268 | 'is_fullscreen' => true |
| 236 | 269 | ) |
| 237 | 270 | ); |
| 238 | 271 | } |
| @@ -246,13 +279,110 @@ | ||
| 246 | 279 | ); |
| 247 | 280 | return $arr_obj; |
| 248 | 281 | } |
| 249 | 282 | |
| 283 | + /** | |
| 284 | + * Handle the newsletter opt-in on the welcome step. | |
| 285 | + * | |
| 286 | + * Opt-in only: nothing is sent unless the administrator ticked the box, | |
| 287 | + * which is unticked by default. The choice is recorded either way so the | |
| 288 | + * wizard can show it again on a re-run. Runs server side so the | |
| 289 | + * cross-origin POST is not subject to CORS. | |
| 290 | + */ | |
| 291 | + public function ajax_subscribe() { | |
| 292 | + check_ajax_referer( 'ultimate_post_kit_setup_wizard_nonce', 'nonce' ); | |
| 293 | + | |
| 294 | + if ( ! current_user_can( 'manage_options' ) ) { | |
| 295 | + wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'ultimate-post-kit' ) ) ); | |
| 296 | + } | |
| 297 | + | |
| 298 | + // Record the choice first, whichever way it went. | |
| 299 | + $consent = isset( $_POST['consent'] ) && 'yes' === sanitize_text_field( wp_unslash( $_POST['consent'] ) ); | |
| 300 | + | |
| 301 | + update_option( 'bdtupk_subscribe_optin', $consent ? 'yes' : 'no' ); | |
| 302 | + | |
| 303 | + if ( ! $consent ) { | |
| 304 | + // No opt-in: the choice is stored and nothing leaves the site. | |
| 305 | + wp_send_json_success( | |
| 306 | + array( | |
| 307 | + 'subscribed' => false, | |
| 308 | + 'message' => esc_html__( 'Preferences saved.', 'ultimate-post-kit' ), | |
| 309 | + ) | |
| 310 | + ); | |
| 311 | + } | |
| 312 | + | |
| 313 | + // Keep the raw value: sanitize_email() flattens anything malformed to an | |
| 314 | + // empty string, which would otherwise be indistinguishable from "left blank". | |
| 315 | + $raw_email = isset( $_POST['email'] ) ? sanitize_text_field( wp_unslash( $_POST['email'] ) ) : ''; | |
| 316 | + $email = sanitize_email( $raw_email ); | |
| 317 | + | |
| 318 | + if ( '' === trim( $raw_email ) ) { | |
| 319 | + wp_send_json_success( | |
| 320 | + array( | |
| 321 | + 'subscribed' => false, | |
| 322 | + 'message' => esc_html__( 'Preferences saved.', 'ultimate-post-kit' ), | |
| 323 | + ) | |
| 324 | + ); | |
| 325 | + } | |
| 326 | + | |
| 327 | + if ( ! is_email( $email ) ) { | |
| 328 | + wp_send_json_error( array( 'message' => esc_html__( 'Please enter a valid email address.', 'ultimate-post-kit' ) ) ); | |
| 329 | + } | |
| 330 | + | |
| 331 | + // Never subscribe the same address twice from this site. | |
| 332 | + if ( get_option( 'bdtupk_subscribed_email' ) === $email ) { | |
| 333 | + wp_send_json_success( | |
| 334 | + array( | |
| 335 | + 'subscribed' => true, | |
| 336 | + 'message' => esc_html__( 'You are already subscribed.', 'ultimate-post-kit' ), | |
| 337 | + ) | |
| 338 | + ); | |
| 339 | + } | |
| 340 | + | |
| 341 | + $current_user = wp_get_current_user(); | |
| 342 | + | |
| 343 | + $body = array( | |
| 344 | + 'customer_uid' => apply_filters( 'bdtupk/setup_wizard/subscribe_customer_uid', self::SUBSCRIBE_CUSTOMER_UID ), | |
| 345 | + 'EMAIL' => $email, | |
| 346 | + 'FIRST_NAME' => $current_user ? $current_user->first_name : '', | |
| 347 | + 'LAST_NAME' => $current_user ? $current_user->last_name : '', | |
| 348 | + ); | |
| 349 | + | |
| 350 | + $response = wp_safe_remote_post( | |
| 351 | + apply_filters( 'bdtupk/setup_wizard/subscribe_url', self::SUBSCRIBE_ENDPOINT ), | |
| 352 | + array( | |
| 353 | + 'timeout' => 15, | |
| 354 | + 'body' => apply_filters( 'bdtupk/setup_wizard/subscribe_body', $body, $email ), | |
| 355 | + 'headers' => array( 'Accept' => '*/*' ), | |
| 356 | + 'sslverify' => true, | |
| 357 | + ) | |
| 358 | + ); | |
| 359 | + | |
| 360 | + if ( is_wp_error( $response ) ) { | |
| 361 | + wp_send_json_error( array( 'message' => esc_html__( 'Could not reach the subscription service. Please try again later.', 'ultimate-post-kit' ) ) ); | |
| 362 | + } | |
| 363 | + | |
| 364 | + $code = wp_remote_retrieve_response_code( $response ); | |
| 365 | + | |
| 366 | + if ( $code < 200 || $code >= 400 ) { | |
| 367 | + wp_send_json_error( array( 'message' => esc_html__( 'The subscription service rejected the request.', 'ultimate-post-kit' ) ) ); | |
| 368 | + } | |
| 369 | + | |
| 370 | + update_option( 'bdtupk_subscribed_email', $email ); | |
| 371 | + | |
| 372 | + wp_send_json_success( | |
| 373 | + array( | |
| 374 | + 'subscribed' => true, | |
| 375 | + 'message' => esc_html__( 'Thanks for subscribing!', 'ultimate-post-kit' ), | |
| 376 | + ) | |
| 377 | + ); | |
| 378 | + } | |
| 379 | + | |
| 250 | 380 | // Install plugins |
| 251 | 381 | public function install_plugins() { |
| 252 | - check_ajax_referer( 'setup_wizard_nonce', 'nonce' ); | |
| 382 | + check_ajax_referer( 'ultimate_post_kit_setup_wizard_nonce', 'nonce' ); | |
| 253 | 383 | |
| 254 | - $plugin_slugs = isset( $_POST['plugins'] ) ? $_POST['plugins'] : array(); | |
| 384 | + $plugin_slugs = isset( $_POST['plugins'] ) ? map_deep( wp_unslash( $_POST['plugins'] ), 'sanitize_text_field' ) : array(); | |
| 255 | 385 | |
| 256 | 386 | if ( empty( $plugin_slugs ) || ! is_array( $plugin_slugs ) ) { |
| 257 | 387 | wp_send_json_error( array( 'message' => 'Invalid plugins array' ) ); |
| 258 | 388 | } |
| @@ -311,14 +441,25 @@ | ||
| 311 | 441 | continue; |
| 312 | 442 | } |
| 313 | 443 | } |
| 314 | 444 | |
| 445 | + // Activating a plugin is a separate capability from installing one, so it is | |
| 446 | + // checked on its own rather than being implied by 'install_plugins' above. | |
| 447 | + if ( ! current_user_can( 'activate_plugins' ) ) { | |
| 448 | + $results[] = array( | |
| 449 | + 'slug' => $plugin_slug, | |
| 450 | + 'success' => false, | |
| 451 | + 'message' => esc_html__( 'You do not have permission to activate plugins on this site.', 'ultimate-post-kit' ), | |
| 452 | + ); | |
| 453 | + continue; | |
| 454 | + } | |
| 455 | + | |
| 315 | 456 | // active the plugin |
| 316 | - if ( is_plugin_inactive($plugin_slug) ) { | |
| 457 | + if ( is_plugin_inactive( $plugin_slug ) ) { | |
| 317 | 458 | $activation_result = activate_plugin( $plugin_slug ); |
| 318 | 459 | if ( is_wp_error( $activation_result ) ) { |
| 319 | 460 | $results[] = array( |
| 320 | - 'slug' => $slug, | |
| 461 | + 'slug' => $plugin_slug, | |
| 321 | 462 | 'success' => false, |
| 322 | 463 | 'message' => $activation_result->get_error_message(), |
| 323 | 464 | ); |
| 324 | 465 | continue; |
| @@ -358,8 +499,15 @@ | ||
| 358 | 499 | /** |
| 359 | 500 | * Activate default widgets in setup wizard |
| 360 | 501 | */ |
| 361 | 502 | public function activate_default_widgets() { |
| 503 | + | |
| 504 | + // Also reached anonymously via admin-ajax.php, which fires admin_init before | |
| 505 | + // authentication. Enabling widget modules is an administrator action. | |
| 506 | + if ( wp_doing_ajax() || ! current_user_can( 'manage_options' ) ) { | |
| 507 | + return; | |
| 508 | + } | |
| 509 | + | |
| 362 | 510 | // List of widgets to activate by default |
| 363 | 511 | $default_active_widgets = array( |
| 364 | 512 | 'alex-grid', |
| 365 | 513 | 'alice-grid', |
| @@ -407,10 +555,10 @@ | ||
| 407 | 555 | Setup_Wizard::get_instance(); |
| 408 | 556 | |
| 409 | 557 | use Elementor\TemplateLibrary\Source_Local; |
| 410 | 558 | |
| 411 | -add_action('wp_ajax_import_elementor_template', function () { | |
| 412 | - check_ajax_referer( 'setup_wizard_nonce', 'nonce' ); | |
| 559 | +add_action('wp_ajax_ultimate_post_kit_import_elementor_template', function () { | |
| 560 | + check_ajax_referer( 'ultimate_post_kit_setup_wizard_nonce', 'nonce' ); | |
| 413 | 561 | |
| 414 | 562 | if ( ! current_user_can( 'manage_options' ) ) { |
| 415 | 563 | wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'ultimate-post-kit' ) ) ); |
| 416 | 564 | wp_die(); |
| @@ -418,10 +566,9 @@ | ||
| 418 | 566 | |
| 419 | 567 | $json_url = isset( $_POST['import_url'] ) ? esc_url_raw( wp_unslash( $_POST['import_url'] ) ) : ''; |
| 420 | 568 | |
| 421 | 569 | $response = wp_safe_remote_get($json_url, array( |
| 422 | - 'timeout' => 60, | |
| 423 | - 'sslverify' => false | |
| 570 | + 'timeout' => 60, | |
| 424 | 571 | )); |
| 425 | 572 | |
| 426 | 573 | if (is_wp_error($response)) { |
| 427 | 574 | wp_send_json_error(['message' => esc_html__('Failed to fetch template from URL.', 'ultimate-post-kit')]); |
| @@ -457,9 +604,9 @@ | ||
| 457 | 604 | |
| 458 | 605 | $template_id = $templateData[0]['template_id']; |
| 459 | 606 | $metaData = get_post_meta($template_id); |
| 460 | 607 | |
| 461 | - $page_title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : esc_html__("No Title", 'ultimate-post-kit'); | |
| 608 | + $page_title = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : esc_html__("No Title", 'ultimate-post-kit'); | |
| 462 | 609 | |
| 463 | 610 | // Validate Elementor Data |
| 464 | 611 | if (!isset($metaData['_elementor_data'][0])) { |
| 465 | 612 | wp_send_json_error(['message' => esc_html__('Elementor data not found in template.', 'ultimate-post-kit')]); |
| @@ -485,9 +632,11 @@ | ||
| 485 | 632 | update_post_meta($new_post_id, '_elementor_data', $_elementor_data); |
| 486 | 633 | |
| 487 | 634 | // Import Page Settings if available |
| 488 | 635 | if (isset($metaData['_elementor_page_settings'][0])) { |
| 489 | - $_elementor_page_settings = maybe_unserialize($metaData['_elementor_page_settings'][0]); | |
| 636 | + $_elementor_page_settings = is_serialized($metaData['_elementor_page_settings'][0]) | |
| 637 | + ? unserialize($metaData['_elementor_page_settings'][0], ['allowed_classes' => false]) | |
| 638 | + : $metaData['_elementor_page_settings'][0]; | |
| 490 | 639 | update_post_meta($new_post_id, '_elementor_page_settings', $_elementor_page_settings); |
| 491 | 640 | } |
| 492 | 641 | |
| 493 | 642 | update_post_meta($new_post_id, '_elementor_template_type', $sourceData2['type'] ?? ''); |
| @@ -502,10 +651,10 @@ | ||
| 502 | 651 | } |
| 503 | 652 | ); |
| 504 | 653 | |
| 505 | 654 | |
| 506 | -add_action('wp_ajax_import_upk_elementor_bundle_template', function () { | |
| 507 | - check_ajax_referer('setup_wizard_nonce', 'nonce'); | |
| 655 | +add_action('wp_ajax_ultimate_post_kit_import_elementor_bundle_template', function () { | |
| 656 | + check_ajax_referer('ultimate_post_kit_setup_wizard_nonce', 'nonce'); | |
| 508 | 657 | |
| 509 | 658 | if ( ! current_user_can( 'manage_options' ) ) { |
| 510 | 659 | wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'ultimate-post-kit' ) ) ); |
| 511 | 660 | wp_die(); |
| @@ -517,10 +666,9 @@ | ||
| 517 | 666 | wp_send_json_error(['message' => esc_html__('Invalid import URL', 'ultimate-post-kit')]); |
| 518 | 667 | } |
| 519 | 668 | |
| 520 | 669 | $remote_zip_request = wp_safe_remote_get($file_url, array( |
| 521 | - 'timeout' => 60, | |
| 522 | - 'sslverify' => false, | |
| 670 | + 'timeout' => 60, | |
| 523 | 671 | )); |
| 524 | 672 | |
| 525 | 673 | if (is_wp_error($remote_zip_request)) { |
| 526 | 674 | wp_send_json_error(['message' => esc_html__('Failed to fetch template from URL.', 'ultimate-post-kit')]); |
| @@ -589,9 +737,14 @@ | ||
| 589 | 737 | ]; |
| 590 | 738 | |
| 591 | 739 | $import = $import_export_module->import_kit($tmp_folder_id, $settings, true); |
| 592 | 740 | |
| 593 | - Plugin::$instance->uploads_manager->enable_unfiltered_files_upload(); | |
| 741 | + // Deliberately NOT calling | |
| 742 | + // Plugin::$instance->uploads_manager->enable_unfiltered_files_upload() here. | |
| 743 | + // That permanently sets Elementor's `elementor_unfiltered_files_upload` option, | |
| 744 | + // which Elementor itself surfaces behind an explicit security warning and an | |
| 745 | + // opt-in confirmation. Importing a template must not silently relax another | |
| 746 | + // plugin's upload filtering for the whole site. | |
| 594 | 747 | |
| 595 | 748 | wp_send_json_success($import); |
| 596 | 749 | } catch (\Throwable $e) { |
| 597 | 750 | wp_send_json_error(['message' => esc_html__('Import failed: ', 'ultimate-post-kit') . esc_html($e->getMessage())]); |
| @@ -597,10 +750,10 @@ | ||
| 597 | 750 | wp_send_json_error(['message' => esc_html__('Import failed: ', 'ultimate-post-kit') . esc_html($e->getMessage())]); |
| 598 | 751 | } |
| 599 | 752 | }); |
| 600 | 753 | |
| 601 | -add_action('wp_ajax_import_upk_elementor_bundle_runner_template', function () { | |
| 602 | - check_ajax_referer('setup_wizard_nonce', 'nonce'); | |
| 754 | +add_action('wp_ajax_ultimate_post_kit_import_elementor_bundle_runner_template', function () { | |
| 755 | + check_ajax_referer('ultimate_post_kit_setup_wizard_nonce', 'nonce'); | |
| 603 | 756 | |
| 604 | 757 | if ( ! current_user_can( 'manage_options' ) ) { |
| 605 | 758 | wp_send_json_error( array( 'message' => esc_html__( 'Unauthorized', 'ultimate-post-kit' ) ) ); |
| 606 | 759 | wp_die(); |
| @@ -618,13 +771,15 @@ | ||
| 618 | 771 | wp_send_json_error(['message' => esc_html__('Elementor app not available.', 'ultimate-post-kit')]); |
| 619 | 772 | } |
| 620 | 773 | |
| 621 | 774 | try { |
| 775 | + // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged, WordPress.PHP.IniSet.max_execution_time_Disallowed -- raise the limit only for this admin-triggered template import, which can exceed the default. | |
| 622 | 776 | @ini_set('max_execution_time', 60 * 5); |
| 623 | 777 | |
| 624 | 778 | $import_export_module = $app->get_component('import-export'); |
| 625 | 779 | $import = $import_export_module->import_kit_by_runner($sessionId, $runner); |
| 626 | 780 | |
| 781 | + // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- hooking into Elementor's own action, not a plugin-defined hook. | |
| 627 | 782 | do_action('elementor/import-export/import-kit/runner/after-run', $import); |
| 628 | 783 | wp_send_json_success($import); |
| 629 | 784 | } catch (\Throwable $throwable) { |
| 630 | 785 | wp_send_json_error(['message' => $throwable->getMessage()]); |