PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.8
Elementor Website Builder – more than just a page builder v3.35.8
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / app / modules / onboarding / module.php

module.php in Elementor Website Builder – more than just a page builder 3.35.8, at app/modules/onboarding/module.php

565 lines 17.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\App\Modules\Onboarding;
3
4 use Automatic_Upgrader_Skin;
5 use Elementor\Core\Base\Module as BaseModule;
6 use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
7 use Elementor\Core\Common\Modules\Connect\Apps\Library;
8 use Elementor\Core\Files\Uploads_Manager;
9 use Elementor\Includes\EditorAssetsAPI;
10 use Elementor\Plugin;
11 use Elementor\Utils;
12 use Plugin_Upgrader;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Onboarding Module
20 *
21 * Responsible for initializing Elementor App functionality
22 *
23 * @since 3.6.0
24 */
25 class Module extends BaseModule {
26
27 const VERSION = '1.0.0';
28 const ONBOARDING_OPTION = 'elementor_onboarded';
29
30 const EXPERIMENT_EMPHASIZE_CONNECT_BENEFITS = 'emphasizeConnectBenefits101';
31 const EXPERIMENT_EMPHASIZE_THEME_VALUE_AUDIENCE_202 = 'emphasizeThemeValueAudience202';
32 const EXPERIMENT_UPDATE_COPY_VISUALS = 'updateCopyVisuals401';
33 const EXPERIMENT_REDUCE_HIERARCHY_BLANK_OPTION = 'reduceHierarchyBlankOption402';
34
35 private ?API $editor_assets_api = null;
36
37 /**
38 * Get name.
39 *
40 * @since 3.6.0
41 * @access public
42 *
43 * @return string
44 */
45 public function get_name() {
46 return 'onboarding';
47 }
48
49 private function is_experiment_enabled( string $experiment_key ) {
50 $editor_assets_api = $this->get_editor_assets_api();
51
52 if ( null === $editor_assets_api ) {
53 return false;
54 }
55
56 return $editor_assets_api->is_experiment_enabled( $experiment_key );
57 }
58
59 private function is_hello_theme_activated(): bool {
60 $current_theme = get_option( 'template' );
61 $hello_theme_variants = [ 'hello-elementor', 'hello-biz', 'hello-commerce', 'hello-theme' ];
62
63 return in_array( $current_theme, $hello_theme_variants, true );
64 }
65
66 private function get_editor_assets_api(): ?API {
67 if ( null !== $this->editor_assets_api ) {
68 return $this->editor_assets_api;
69 }
70
71 $editor_assets_api_instance = new EditorAssetsAPI( $this->get_editor_assets_api_config() );
72 $this->editor_assets_api = new API( $editor_assets_api_instance );
73
74 return $this->editor_assets_api;
75 }
76
77 private function get_editor_assets_api_config(): array {
78 return [
79 EditorAssetsAPI::ASSETS_DATA_URL => 'https://assets.elementor.com/ab-testing/v1/ab-testing.json',
80 EditorAssetsAPI::ASSETS_DATA_TRANSIENT_KEY => '_elementor_ab_testing_data',
81 EditorAssetsAPI::ASSETS_DATA_KEY => 'ab-testing',
82 ];
83 }
84
85 /**
86 * Set Onboarding Settings
87 *
88 * Creates an array of module settings that is localized into the JS App config.
89 *
90 * @since 3.6.0
91 */
92 private function set_onboarding_settings() {
93 if ( ! Plugin::$instance->common ) {
94 return;
95 }
96
97 // Get the published pages and posts
98 $pages_and_posts = new \WP_Query( [
99 'post_type' => [ 'page', 'post' ],
100 'post_status' => 'publish',
101 'update_post_meta_cache' => false,
102 'update_post_term_cache' => false,
103 'no_found_rows' => true,
104 ] );
105
106 $custom_site_logo_id = get_theme_mod( 'custom_logo' );
107 $custom_logo_src = wp_get_attachment_image_src( $custom_site_logo_id, 'full' );
108 $site_name = get_option( 'blogname', '' );
109
110 $hello_theme = wp_get_theme( 'hello-elementor' );
111 $hello_theme_errors = is_object( $hello_theme->errors() ) ? $hello_theme->errors()->errors : [];
112
113 /** @var Library $library */
114 $library = Plugin::$instance->common->get_component( 'connect' )->get_app( 'library' );
115 $is_editor_one_active = Plugin::$instance->experiments->is_feature_active( 'e_editor_one' );
116
117 Plugin::$instance->app->set_settings( 'onboarding', [
118 'eventPlacement' => 'Onboarding wizard',
119 'onboardingAlreadyRan' => get_option( self::ONBOARDING_OPTION ),
120 'onboardingVersion' => self::VERSION,
121 'isLibraryConnected' => $library->is_connected(),
122 // Used to check if the Hello Elementor theme is installed but not activated.
123 'helloInstalled' => empty( $hello_theme_errors['theme_not_found'] ),
124 'helloActivated' => $this->is_hello_theme_activated(),
125 // The "Use Hello theme on my site" checkbox should be checked by default only if this condition is met.
126 'helloOptOut' => count( $pages_and_posts->posts ) < 5,
127 'siteName' => esc_html( $site_name ),
128 'isUnfilteredFilesEnabled' => Uploads_Manager::are_unfiltered_uploads_enabled(),
129 'isEditorOneActive' => $is_editor_one_active,
130 'urls' => [
131 'kitLibrary' => Plugin::$instance->app->get_base_url() . '&source=onboarding#/kit-library?order[direction]=desc&order[by]=featuredIndex',
132 'sitePlanner' => add_query_arg( [
133 'type' => 'editor',
134 'siteUrl' => esc_url( home_url() ),
135 'siteName' => esc_html( $site_name ),
136 'siteDescription' => esc_html( get_bloginfo( 'description' ) ),
137 'siteLanguage' => get_locale(),
138 ], 'https://planner.elementor.com/onboarding.html' ),
139 'createNewPage' => Plugin::$instance->documents->get_create_new_post_url(),
140 'connect' => $library->get_admin_url( 'authorize', [
141 'utm_source' => 'onboarding-wizard',
142 'utm_campaign' => 'connect-account',
143 'utm_medium' => 'wp-dash',
144 'utm_term' => self::VERSION,
145 'source' => 'generic',
146 ] ),
147 'upgrade' => 'https://go.elementor.com/go-pro-onboarding-wizard-upgrade/',
148 'signUp' => $library->get_admin_url( 'authorize', [
149 'utm_source' => 'onboarding-wizard',
150 'utm_campaign' => 'connect-account',
151 'utm_medium' => 'wp-dash',
152 'utm_term' => self::VERSION,
153 'source' => 'generic',
154 'screen_hint' => 'signup',
155 ] ),
156 'uploadPro' => Plugin::$instance->app->get_base_url() . '#/onboarding/uploadAndInstallPro?mode=popup',
157 ],
158 'siteLogo' => [
159 'id' => $custom_site_logo_id,
160 'url' => $custom_logo_src ? $custom_logo_src[0] : '',
161 ],
162 'utms' => [
163 'connectTopBar' => '&utm_content=top-bar',
164 'connectCta' => '&utm_content=cta-button',
165 'connectCtaLink' => '&utm_content=cta-link',
166 'downloadPro' => '?utm_source=onboarding-wizard&utm_campaign=my-account-subscriptions&utm_medium=wp-dash&utm_content=import-pro-plugin&utm_term=' . self::VERSION,
167 ],
168 'nonce' => wp_create_nonce( 'onboarding' ),
169 'experiment' => true,
170 'isExperiment101Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_EMPHASIZE_CONNECT_BENEFITS ),
171 'isExperiment202Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_EMPHASIZE_THEME_VALUE_AUDIENCE_202 ),
172 'isExperiment401Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_UPDATE_COPY_VISUALS ),
173 'isExperiment402Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_REDUCE_HIERARCHY_BLANK_OPTION ),
174 'experimentNames' => [
175 '101' => self::EXPERIMENT_EMPHASIZE_CONNECT_BENEFITS,
176 '202' => self::EXPERIMENT_EMPHASIZE_THEME_VALUE_AUDIENCE_202,
177 '401' => self::EXPERIMENT_UPDATE_COPY_VISUALS,
178 '402' => self::EXPERIMENT_REDUCE_HIERARCHY_BLANK_OPTION,
179 ],
180 'pageSubheading' => $is_editor_one_active ? __( 'Choose the capabilities you need to bring your vision to life', 'elementor' ) : __( 'Which Elementor Pro features do you need to bring your creative vision to life?', 'elementor' ),
181 'pageHeading' => $is_editor_one_active ? __( ' Elevate your website with additional features.', 'elementor' ) : __( 'Elevate your website with additional Pro features.', 'elementor' ),
182 ] );
183 }
184
185 /**
186 * Get Permission Error Response
187 *
188 * Returns the response that is returned when the user's capabilities are not sufficient for performing an action.
189 *
190 * @since 3.6.4
191 *
192 * @return array
193 */
194 private function get_permission_error_response() {
195 return [
196 'status' => 'error',
197 'payload' => [
198 'error_message' => esc_html__( 'You do not have permission to perform this action.', 'elementor' ),
199 ],
200 ];
201 }
202
203 /**
204 * Maybe Update Site Logo
205 *
206 * If a new name is provided, it will be updated as the Site Name.
207 *
208 * @since 3.6.0
209 *
210 * @return array
211 */
212 private function maybe_update_site_name() {
213 $problem_error = [
214 'status' => 'error',
215 'payload' => [
216 'error_message' => esc_html__( 'There was a problem setting your site name.', 'elementor' ),
217 ],
218 ];
219
220 // phpcs:ignore WordPress.Security.NonceVerification.Missing
221 if ( empty( $_POST['data'] ) ) {
222 return $problem_error;
223 }
224
225 // phpcs:ignore WordPress.Security.NonceVerification.Missing
226 $data = json_decode( Utils::get_super_global_value( $_POST, 'data' ), true );
227
228 if ( ! isset( $data['siteName'] ) ) {
229 return $problem_error;
230 }
231
232 /**
233 * Onboarding Site Name
234 *
235 * Filters the new site name passed by the user to update in Elementor's onboarding process.
236 * Elementor runs `esc_html()` on the Site Name passed by the user for security reasons. If a user wants to
237 * include special characters in their site name, they can use this filter to override it.
238 *
239 * @since 3.6.0
240 *
241 * @param string Escaped new site name
242 */
243 $new_site_name = apply_filters( 'elementor/onboarding/site-name', $data['siteName'] );
244
245 // The site name is sanitized in `update_options()`
246 update_option( 'blogname', $new_site_name );
247
248 return [
249 'status' => 'success',
250 'payload' => [
251 'siteNameUpdated' => true,
252 ],
253 ];
254 }
255
256 /**
257 * Maybe Update Site Logo
258 *
259 * If an image attachment ID is provided, it will be updated as the Site Logo Theme Mod.
260 *
261 * @since 3.6.0
262 *
263 * @return array
264 */
265 private function maybe_update_site_logo() {
266 if ( ! current_user_can( 'edit_theme_options' ) ) {
267 return $this->get_permission_error_response();
268 }
269
270 $data_error = [
271 'status' => 'error',
272 'payload' => [
273 'error_message' => esc_html__( 'There was a problem setting your site logo.', 'elementor' ),
274 ],
275 ];
276
277 // phpcs:ignore WordPress.Security.NonceVerification.Missing
278 if ( empty( $_POST['data'] ) ) {
279 return $data_error;
280 }
281
282 // phpcs:ignore WordPress.Security.NonceVerification.Missing
283 $data = json_decode( Utils::get_super_global_value( $_POST, 'data' ), true );
284
285 // If there is no attachment ID passed or it is not a valid ID, exit here.
286 if ( empty( $data['attachmentId'] ) ) {
287 return $data_error;
288 }
289
290 $absint_attachment_id = absint( $data['attachmentId'] );
291
292 if ( 0 === $absint_attachment_id ) {
293 return $data_error;
294 }
295
296 $attachment_url = wp_get_attachment_url( $data['attachmentId'] );
297
298 // Check if the attachment exists. If it does not, exit here.
299 if ( ! $attachment_url ) {
300 return $data_error;
301 }
302
303 set_theme_mod( 'custom_logo', $absint_attachment_id );
304
305 return [
306 'status' => 'success',
307 'payload' => [
308 'siteLogoUpdated' => true,
309 ],
310 ];
311 }
312
313 /**
314 * Maybe Upload Logo Image
315 *
316 * If an image file upload is provided, and it passes validation, it will be uploaded to the site's Media Library.
317 *
318 * @since 3.6.0
319 *
320 * @return array
321 */
322 private function maybe_upload_logo_image() {
323 $error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' );
324
325 $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
326
327 // phpcs:ignore WordPress.Security.NonceVerification.Missing
328 if ( ! is_array( $file ) || empty( $file['type'] ) ) {
329 return [
330 'status' => 'error',
331 'payload' => [
332 'error_message' => $error_message,
333 ],
334 ];
335 }
336
337 // If the user has allowed it, set the Request's state as an "Elementor Upload" request, in order to add
338 // support for non-standard file uploads.
339 if ( 'image/svg+xml' === $file['type'] ) {
340 if ( Uploads_Manager::are_unfiltered_uploads_enabled() ) {
341 Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
342 } else {
343 wp_send_json_error( 'To upload SVG files, you must allow uploading unfiltered files.' );
344 }
345 }
346
347 // If the image is an SVG file, sanitation is performed during the import (upload) process.
348 $image_attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( $file );
349
350 if ( 'image/svg+xml' === $file['type'] && Uploads_Manager::are_unfiltered_uploads_enabled() ) {
351 // Reset Upload state.
352 Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
353 }
354
355 if ( $image_attachment && ! is_wp_error( $image_attachment ) ) {
356 $result = [
357 'status' => 'success',
358 'payload' => [
359 'imageAttachment' => $image_attachment,
360 ],
361 ];
362 } else {
363 $result = [
364 'status' => 'error',
365 'payload' => [
366 'error_message' => $error_message,
367 ],
368 ];
369 }
370
371 return $result;
372 }
373
374 /**
375 * Activate Hello Theme
376 *
377 * @since 3.6.0
378 *
379 * @return array
380 */
381 private function maybe_activate_hello_theme() {
382 if ( ! current_user_can( 'switch_themes' ) ) {
383 return $this->get_permission_error_response();
384 }
385
386 $theme_slug = Utils::get_super_global_value( $_POST, 'theme_slug' ) ?? 'hello-biz'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
387 $allowed_themes = [ 'hello-elementor', 'hello-biz' ];
388 if ( ! in_array( $theme_slug, $allowed_themes, true ) ) {
389 $theme_slug = 'hello-biz';
390 }
391
392 switch_theme( $theme_slug );
393
394 return [
395 'status' => 'success',
396 'payload' => [
397 'helloThemeActivated' => true,
398 ],
399 ];
400 }
401
402 /**
403 * Upload and Install Elementor Pro
404 *
405 * @since 3.6.0
406 *
407 * @return array
408 */
409 private function upload_and_install_pro() {
410 if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) {
411 return $this->get_permission_error_response();
412 }
413
414 $error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' );
415
416 $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ) ?? []; // phpcs:ignore WordPress.Security.NonceVerification.Missing
417
418 // phpcs:ignore WordPress.Security.NonceVerification.Missing
419 if ( ! is_array( $file ) || empty( $file['type'] ) ) {
420 return [
421 'status' => 'error',
422 'payload' => [
423 'error_message' => $error_message,
424 ],
425 ];
426 }
427
428 $result = [];
429
430 if ( ! class_exists( 'Automatic_Upgrader_Skin' ) ) {
431 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
432 }
433
434 $skin = new Automatic_Upgrader_Skin();
435 $upgrader = new Plugin_Upgrader( $skin );
436 $upload_result = $upgrader->install( $file['tmp_name'], [ 'overwrite_package' => false ] );
437
438 if ( ! $upload_result || is_wp_error( $upload_result ) ) {
439 $result = [
440 'status' => 'error',
441 'payload' => [
442 'error_message' => $error_message,
443 ],
444 ];
445 } else {
446 $activated = activate_plugin( WP_PLUGIN_DIR . '/elementor-pro/elementor-pro.php', false, false, true );
447
448 if ( ! is_wp_error( $activated ) ) {
449 $result = [
450 'status' => 'success',
451 'payload' => [
452 'elementorProInstalled' => true,
453 ],
454 ];
455 } else {
456 $result = [
457 'status' => 'error',
458 'payload' => [
459 'error_message' => $error_message,
460 'elementorProInstalled' => false,
461 ],
462 ];
463 }
464 }
465
466 return $result;
467 }
468
469 private function maybe_update_onboarding_db_option() {
470 $db_option = get_option( self::ONBOARDING_OPTION );
471
472 if ( ! $db_option ) {
473 update_option( self::ONBOARDING_OPTION, true );
474 }
475
476 return [
477 'status' => 'success',
478 'payload' => 'onboarding DB',
479 ];
480 }
481
482 /**
483 * Maybe Handle Ajax
484 *
485 * This method checks if there are any AJAX actions being
486 *
487 * @since 3.6.0
488 */
489 private function maybe_handle_ajax() {
490 $result = [];
491
492 // phpcs:ignore WordPress.Security.NonceVerification.Missing
493 switch ( Utils::get_super_global_value( $_POST, 'action' ) ) {
494 case 'elementor_update_site_name':
495 // If no value is passed for any reason, no need to update the site name.
496 $result = $this->maybe_update_site_name();
497 break;
498 case 'elementor_update_site_logo':
499 $result = $this->maybe_update_site_logo();
500 break;
501 case 'elementor_upload_site_logo':
502 $result = $this->maybe_upload_logo_image();
503 break;
504 case 'elementor_activate_hello_theme':
505 $result = $this->maybe_activate_hello_theme();
506 break;
507 case 'elementor_upload_and_install_pro':
508 $result = $this->upload_and_install_pro();
509 break;
510 case 'elementor_update_onboarding_option':
511 $result = $this->maybe_update_onboarding_db_option();
512 break;
513 case 'elementor_save_onboarding_features':
514 // phpcs:ignore WordPress.Security.NonceVerification.Missing
515 $result = $this->get_component( 'features_usage' )->save_onboarding_features( Utils::get_super_global_value( $_POST, 'data' ) ?? [] );
516 }
517
518 if ( ! empty( $result ) ) {
519 if ( 'success' === $result['status'] ) {
520 wp_send_json_success( $result['payload'] );
521 } else {
522 wp_send_json_error( $result['payload'] );
523 }
524 }
525 }
526
527 public function __construct() {
528 $this->add_component( 'features_usage', new Features_Usage() );
529
530 add_action( 'elementor/init', function() {
531 // Only load when viewing the onboarding app.
532 if ( Plugin::$instance->app->is_current() ) {
533 $this->set_onboarding_settings();
534 // Needed for installing the Hello Elementor theme.
535 wp_enqueue_script( 'updates' );
536 // Needed for uploading Logo from WP Media Library.
537 wp_enqueue_media();
538 }
539 }, 12 );
540
541 // Needed for uploading Logo from WP Media Library. The 'admin_menu' hook is used because it runs before
542 // 'admin_init', and the App triggers printing footer scripts on 'admin_init' at priority 0.
543 add_action( 'admin_menu', function () {
544 add_action( 'wp_print_footer_scripts', function () {
545 if ( function_exists( 'wp_print_media_templates' ) ) {
546 wp_print_media_templates();
547 }
548 } );
549 } );
550
551 add_action( 'admin_init', function() {
552 if ( wp_doing_ajax() &&
553 isset( $_POST['action'] ) &&
554 isset( $_POST['_nonce'] ) &&
555 wp_verify_nonce( Utils::get_super_global_value( $_POST, '_nonce' ), Ajax::NONCE_KEY ) &&
556 current_user_can( 'manage_options' )
557 ) {
558 $this->maybe_handle_ajax();
559 }
560 } );
561
562 $this->get_component( 'features_usage' )->register();
563 }
564 }
565