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

564 lines 16.9 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
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 'urls' => [
130 'kitLibrary' => Plugin::$instance->app->get_base_url() . '&source=onboarding#/kit-library?order[direction]=desc&order[by]=featuredIndex',
131 'sitePlanner' => add_query_arg( [
132 'type' => 'editor',
133 'siteUrl' => esc_url( home_url() ),
134 'siteName' => esc_html( $site_name ),
135 'siteDescription' => esc_html( get_bloginfo( 'description' ) ),
136 'siteLanguage' => get_locale(),
137 ], 'https://planner.elementor.com/onboarding.html' ),
138 'createNewPage' => Plugin::$instance->documents->get_create_new_post_url(),
139 'connect' => $library->get_admin_url( 'authorize', [
140 'utm_source' => 'onboarding-wizard',
141 'utm_campaign' => 'connect-account',
142 'utm_medium' => 'wp-dash',
143 'utm_term' => self::VERSION,
144 'source' => 'generic',
145 ] ),
146 'upgrade' => 'https://go.elementor.com/go-pro-onboarding-wizard-upgrade/',
147 'signUp' => $library->get_admin_url( 'authorize', [
148 'utm_source' => 'onboarding-wizard',
149 'utm_campaign' => 'connect-account',
150 'utm_medium' => 'wp-dash',
151 'utm_term' => self::VERSION,
152 'source' => 'generic',
153 'screen_hint' => 'signup',
154 ] ),
155 'uploadPro' => Plugin::$instance->app->get_base_url() . '#/onboarding/uploadAndInstallPro?mode=popup',
156 ],
157 'siteLogo' => [
158 'id' => $custom_site_logo_id,
159 'url' => $custom_logo_src ? $custom_logo_src[0] : '',
160 ],
161 'utms' => [
162 'connectTopBar' => '&utm_content=top-bar',
163 'connectCta' => '&utm_content=cta-button',
164 'connectCtaLink' => '&utm_content=cta-link',
165 'downloadPro' => '?utm_source=onboarding-wizard&utm_campaign=my-account-subscriptions&utm_medium=wp-dash&utm_content=import-pro-plugin&utm_term=' . self::VERSION,
166 ],
167 'nonce' => wp_create_nonce( 'onboarding' ),
168 'experiment' => true,
169 'isExperiment101Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_EMPHASIZE_CONNECT_BENEFITS ),
170 'isExperiment201Enabled' => $this->is_experiment_enabled( self::EXPERIMENT_OFFER_THEME_CHOICES_HELLO_BIZ ),
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 '201' => self::EXPERIMENT_OFFER_THEME_CHOICES_HELLO_BIZ,
177 '202' => self::EXPERIMENT_EMPHASIZE_THEME_VALUE_AUDIENCE_202,
178 '401' => self::EXPERIMENT_UPDATE_COPY_VISUALS,
179 '402' => self::EXPERIMENT_REDUCE_HIERARCHY_BLANK_OPTION,
180 ],
181 ] );
182 }
183
184 /**
185 * Get Permission Error Response
186 *
187 * Returns the response that is returned when the user's capabilities are not sufficient for performing an action.
188 *
189 * @since 3.6.4
190 *
191 * @return array
192 */
193 private function get_permission_error_response() {
194 return [
195 'status' => 'error',
196 'payload' => [
197 'error_message' => esc_html__( 'You do not have permission to perform this action.', 'elementor' ),
198 ],
199 ];
200 }
201
202 /**
203 * Maybe Update Site Logo
204 *
205 * If a new name is provided, it will be updated as the Site Name.
206 *
207 * @since 3.6.0
208 *
209 * @return array
210 */
211 private function maybe_update_site_name() {
212 $problem_error = [
213 'status' => 'error',
214 'payload' => [
215 'error_message' => esc_html__( 'There was a problem setting your site name.', 'elementor' ),
216 ],
217 ];
218
219 // phpcs:ignore WordPress.Security.NonceVerification.Missing
220 if ( empty( $_POST['data'] ) ) {
221 return $problem_error;
222 }
223
224 // phpcs:ignore WordPress.Security.NonceVerification.Missing
225 $data = json_decode( Utils::get_super_global_value( $_POST, 'data' ), true );
226
227 if ( ! isset( $data['siteName'] ) ) {
228 return $problem_error;
229 }
230
231 /**
232 * Onboarding Site Name
233 *
234 * Filters the new site name passed by the user to update in Elementor's onboarding process.
235 * Elementor runs `esc_html()` on the Site Name passed by the user for security reasons. If a user wants to
236 * include special characters in their site name, they can use this filter to override it.
237 *
238 * @since 3.6.0
239 *
240 * @param string Escaped new site name
241 */
242 $new_site_name = apply_filters( 'elementor/onboarding/site-name', $data['siteName'] );
243
244 // The site name is sanitized in `update_options()`
245 update_option( 'blogname', $new_site_name );
246
247 return [
248 'status' => 'success',
249 'payload' => [
250 'siteNameUpdated' => true,
251 ],
252 ];
253 }
254
255 /**
256 * Maybe Update Site Logo
257 *
258 * If an image attachment ID is provided, it will be updated as the Site Logo Theme Mod.
259 *
260 * @since 3.6.0
261 *
262 * @return array
263 */
264 private function maybe_update_site_logo() {
265 if ( ! current_user_can( 'edit_theme_options' ) ) {
266 return $this->get_permission_error_response();
267 }
268
269 $data_error = [
270 'status' => 'error',
271 'payload' => [
272 'error_message' => esc_html__( 'There was a problem setting your site logo.', 'elementor' ),
273 ],
274 ];
275
276 // phpcs:ignore WordPress.Security.NonceVerification.Missing
277 if ( empty( $_POST['data'] ) ) {
278 return $data_error;
279 }
280
281 // phpcs:ignore WordPress.Security.NonceVerification.Missing
282 $data = json_decode( Utils::get_super_global_value( $_POST, 'data' ), true );
283
284 // If there is no attachment ID passed or it is not a valid ID, exit here.
285 if ( empty( $data['attachmentId'] ) ) {
286 return $data_error;
287 }
288
289 $absint_attachment_id = absint( $data['attachmentId'] );
290
291 if ( 0 === $absint_attachment_id ) {
292 return $data_error;
293 }
294
295 $attachment_url = wp_get_attachment_url( $data['attachmentId'] );
296
297 // Check if the attachment exists. If it does not, exit here.
298 if ( ! $attachment_url ) {
299 return $data_error;
300 }
301
302 set_theme_mod( 'custom_logo', $absint_attachment_id );
303
304 return [
305 'status' => 'success',
306 'payload' => [
307 'siteLogoUpdated' => true,
308 ],
309 ];
310 }
311
312 /**
313 * Maybe Upload Logo Image
314 *
315 * If an image file upload is provided, and it passes validation, it will be uploaded to the site's Media Library.
316 *
317 * @since 3.6.0
318 *
319 * @return array
320 */
321 private function maybe_upload_logo_image() {
322 $error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' );
323
324 $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
325
326 // phpcs:ignore WordPress.Security.NonceVerification.Missing
327 if ( ! is_array( $file ) || empty( $file['type'] ) ) {
328 return [
329 'status' => 'error',
330 'payload' => [
331 'error_message' => $error_message,
332 ],
333 ];
334 }
335
336 // If the user has allowed it, set the Request's state as an "Elementor Upload" request, in order to add
337 // support for non-standard file uploads.
338 if ( 'image/svg+xml' === $file['type'] ) {
339 if ( Uploads_Manager::are_unfiltered_uploads_enabled() ) {
340 Plugin::$instance->uploads_manager->set_elementor_upload_state( true );
341 } else {
342 wp_send_json_error( 'To upload SVG files, you must allow uploading unfiltered files.' );
343 }
344 }
345
346 // If the image is an SVG file, sanitation is performed during the import (upload) process.
347 $image_attachment = Plugin::$instance->templates_manager->get_import_images_instance()->import( $file );
348
349 if ( 'image/svg+xml' === $file['type'] && Uploads_Manager::are_unfiltered_uploads_enabled() ) {
350 // Reset Upload state.
351 Plugin::$instance->uploads_manager->set_elementor_upload_state( false );
352 }
353
354 if ( $image_attachment && ! is_wp_error( $image_attachment ) ) {
355 $result = [
356 'status' => 'success',
357 'payload' => [
358 'imageAttachment' => $image_attachment,
359 ],
360 ];
361 } else {
362 $result = [
363 'status' => 'error',
364 'payload' => [
365 'error_message' => $error_message,
366 ],
367 ];
368 }
369
370 return $result;
371 }
372
373 /**
374 * Activate Hello Theme
375 *
376 * @since 3.6.0
377 *
378 * @return array
379 */
380 private function maybe_activate_hello_theme() {
381 if ( ! current_user_can( 'switch_themes' ) ) {
382 return $this->get_permission_error_response();
383 }
384
385 $theme_slug = Utils::get_super_global_value( $_POST, 'theme_slug' ) ?? 'hello-biz'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
386 $allowed_themes = [ 'hello-elementor', 'hello-biz' ];
387 if ( ! in_array( $theme_slug, $allowed_themes, true ) ) {
388 $theme_slug = 'hello-biz';
389 }
390
391 switch_theme( $theme_slug );
392
393 return [
394 'status' => 'success',
395 'payload' => [
396 'helloThemeActivated' => true,
397 ],
398 ];
399 }
400
401 /**
402 * Upload and Install Elementor Pro
403 *
404 * @since 3.6.0
405 *
406 * @return array
407 */
408 private function upload_and_install_pro() {
409 if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) {
410 return $this->get_permission_error_response();
411 }
412
413 $error_message = esc_html__( 'There was a problem uploading your file.', 'elementor' );
414
415 $file = Utils::get_super_global_value( $_FILES, 'fileToUpload' ) ?? []; // phpcs:ignore WordPress.Security.NonceVerification.Missing
416
417 // phpcs:ignore WordPress.Security.NonceVerification.Missing
418 if ( ! is_array( $file ) || empty( $file['type'] ) ) {
419 return [
420 'status' => 'error',
421 'payload' => [
422 'error_message' => $error_message,
423 ],
424 ];
425 }
426
427 $result = [];
428
429 if ( ! class_exists( 'Automatic_Upgrader_Skin' ) ) {
430 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
431 }
432
433 $skin = new Automatic_Upgrader_Skin();
434 $upgrader = new Plugin_Upgrader( $skin );
435 $upload_result = $upgrader->install( $file['tmp_name'], [ 'overwrite_package' => false ] );
436
437 if ( ! $upload_result || is_wp_error( $upload_result ) ) {
438 $result = [
439 'status' => 'error',
440 'payload' => [
441 'error_message' => $error_message,
442 ],
443 ];
444 } else {
445 $activated = activate_plugin( WP_PLUGIN_DIR . '/elementor-pro/elementor-pro.php', false, false, true );
446
447 if ( ! is_wp_error( $activated ) ) {
448 $result = [
449 'status' => 'success',
450 'payload' => [
451 'elementorProInstalled' => true,
452 ],
453 ];
454 } else {
455 $result = [
456 'status' => 'error',
457 'payload' => [
458 'error_message' => $error_message,
459 'elementorProInstalled' => false,
460 ],
461 ];
462 }
463 }
464
465 return $result;
466 }
467
468 private function maybe_update_onboarding_db_option() {
469 $db_option = get_option( self::ONBOARDING_OPTION );
470
471 if ( ! $db_option ) {
472 update_option( self::ONBOARDING_OPTION, true );
473 }
474
475 return [
476 'status' => 'success',
477 'payload' => 'onboarding DB',
478 ];
479 }
480
481 /**
482 * Maybe Handle Ajax
483 *
484 * This method checks if there are any AJAX actions being
485 *
486 * @since 3.6.0
487 */
488 private function maybe_handle_ajax() {
489 $result = [];
490
491 // phpcs:ignore WordPress.Security.NonceVerification.Missing
492 switch ( Utils::get_super_global_value( $_POST, 'action' ) ) {
493 case 'elementor_update_site_name':
494 // If no value is passed for any reason, no need to update the site name.
495 $result = $this->maybe_update_site_name();
496 break;
497 case 'elementor_update_site_logo':
498 $result = $this->maybe_update_site_logo();
499 break;
500 case 'elementor_upload_site_logo':
501 $result = $this->maybe_upload_logo_image();
502 break;
503 case 'elementor_activate_hello_theme':
504 $result = $this->maybe_activate_hello_theme();
505 break;
506 case 'elementor_upload_and_install_pro':
507 $result = $this->upload_and_install_pro();
508 break;
509 case 'elementor_update_onboarding_option':
510 $result = $this->maybe_update_onboarding_db_option();
511 break;
512 case 'elementor_save_onboarding_features':
513 // phpcs:ignore WordPress.Security.NonceVerification.Missing
514 $result = $this->get_component( 'features_usage' )->save_onboarding_features( Utils::get_super_global_value( $_POST, 'data' ) ?? [] );
515 }
516
517 if ( ! empty( $result ) ) {
518 if ( 'success' === $result['status'] ) {
519 wp_send_json_success( $result['payload'] );
520 } else {
521 wp_send_json_error( $result['payload'] );
522 }
523 }
524 }
525
526 public function __construct() {
527 $this->add_component( 'features_usage', new Features_Usage() );
528
529 add_action( 'elementor/init', function() {
530 // Only load when viewing the onboarding app.
531 if ( Plugin::$instance->app->is_current() ) {
532 $this->set_onboarding_settings();
533 // Needed for installing the Hello Elementor theme.
534 wp_enqueue_script( 'updates' );
535 // Needed for uploading Logo from WP Media Library.
536 wp_enqueue_media();
537 }
538 }, 12 );
539
540 // Needed for uploading Logo from WP Media Library. The 'admin_menu' hook is used because it runs before
541 // 'admin_init', and the App triggers printing footer scripts on 'admin_init' at priority 0.
542 add_action( 'admin_menu', function () {
543 add_action( 'wp_print_footer_scripts', function () {
544 if ( function_exists( 'wp_print_media_templates' ) ) {
545 wp_print_media_templates();
546 }
547 } );
548 } );
549
550 add_action( 'admin_init', function() {
551 if ( wp_doing_ajax() &&
552 isset( $_POST['action'] ) &&
553 isset( $_POST['_nonce'] ) &&
554 wp_verify_nonce( Utils::get_super_global_value( $_POST, '_nonce' ), Ajax::NONCE_KEY ) &&
555 current_user_can( 'manage_options' )
556 ) {
557 $this->maybe_handle_ajax();
558 }
559 } );
560
561 $this->get_component( 'features_usage' )->register();
562 }
563 }
564