PluginProbe
Elementor Website Builder – more than just a page builder / 3.35.0-dev2
Elementor Website Builder – more than just a page builder v3.35.0-dev2
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.0-dev2, at app/modules/onboarding/module.php

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