PluginProbe
Elementor Website Builder – more than just a page builder / 3.2.5
Elementor Website Builder – more than just a page builder v3.2.5
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 / core / common / modules / connect / apps / base-app.php

base-app.php in Elementor Website Builder – more than just a page builder 3.2.5, at core/common/modules/connect/apps/base-app.php

639 lines 13.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Core\Common\Modules\Connect\Apps;
3
4 use Elementor\Core\Admin\Admin_Notices;
5 use Elementor\Core\Common\Modules\Connect\Admin;
6 use Elementor\Plugin;
7 use Elementor\Tracker;
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 abstract class Base_App {
14
15 const OPTION_NAME_PREFIX = 'elementor_connect_';
16
17 const SITE_URL = 'https://my.elementor.com/connect/v1';
18
19 const API_URL = 'https://my.elementor.com/api/connect/v1';
20
21 protected $data = [];
22
23 protected $auth_mode = '';
24
25 /**
26 * @since 2.3.0
27 * @access protected
28 * @abstract
29 * TODO: make it public.
30 */
31 abstract protected function get_slug();
32
33 /**
34 * @since 2.8.0
35 * @access public
36 * TODO: make it abstract.
37 */
38 public function get_title() {
39 return $this->get_slug();
40 }
41
42 /**
43 * @since 2.3.0
44 * @access protected
45 * @abstract
46 */
47 abstract protected function update_settings();
48
49 /**
50 * @since 2.3.0
51 * @access public
52 * @static
53 */
54 public static function get_class_name() {
55 return get_called_class();
56 }
57
58 /**
59 * @access public
60 * @abstract
61 */
62 public function render_admin_widget() {
63 echo '<h2>' . $this->get_title() . '</h2>';
64
65 if ( $this->is_connected() ) {
66 $remote_user = $this->get( 'user' );
67 $title = sprintf( __( 'Connected as %s', 'elementor' ), '<strong>' . $remote_user->email . '</strong>' );
68 $label = __( 'Disconnect', 'elementor' );
69 $url = $this->get_admin_url( 'disconnect' );
70 $attr = '';
71
72 echo sprintf( '%s <a %s href="%s">%s</a>', $title, $attr, esc_attr( $url ), esc_html( $label ) );
73 } else {
74 echo 'Not Connected';
75 }
76
77 echo '<hr>';
78
79 $this->print_app_info();
80
81 if ( current_user_can( 'manage_options' ) ) {
82 printf( '<div><a href="%s">%s</a></div>', $this->get_admin_url( 'reset' ), __( 'Reset Data', 'elementor' ) );
83 }
84
85 echo '<hr>';
86 }
87
88
89 /**
90 * @since 2.3.0
91 * @access protected
92 */
93 protected function get_option_name() {
94 return static::OPTION_NAME_PREFIX . $this->get_slug();
95 }
96
97 /**
98 * @since 2.3.0
99 * @access public
100 */
101 public function admin_notice() {
102 $notices = $this->get( 'notices' );
103
104 if ( ! $notices ) {
105 return;
106 }
107
108 $this->print_notices( $notices );
109
110 $this->delete( 'notices' );
111 }
112
113
114 public function get_app_token_from_cli_token( $cli_token ) {
115 $response = $this->request( 'get_app_token_from_cli_token', [
116 'cli_token' => $cli_token,
117 ] );
118
119 if ( is_wp_error( $response ) ) {
120 wp_die( $response, $response->get_error_message() );
121 }
122
123 // Use state as usual.
124 $_REQUEST['state'] = $this->get( 'state' );
125 $_REQUEST['code'] = $response->code;
126 }
127 /**
128 * @since 2.3.0
129 * @access public
130 */
131 public function action_authorize() {
132 if ( $this->is_connected() ) {
133 $this->add_notice( __( 'Already connected.', 'elementor' ), 'info' );
134 $this->redirect_to_admin_page();
135 return;
136 }
137
138 $this->set_client_id();
139 $this->set_request_state();
140
141 $this->redirect_to_remote_authorize_url();
142 }
143
144 public function action_reset() {
145 delete_user_option( get_current_user_id(), 'elementor_connect_common_data' );
146
147 if ( current_user_can( 'manage_options' ) ) {
148 delete_option( 'elementor_connect_site_key' );
149 delete_option( 'elementor_remote_info_library' );
150 }
151
152 $this->redirect_to_admin_page();
153 }
154
155 /**
156 * @since 2.3.0
157 * @access public
158 */
159 public function action_get_token() {
160 if ( $this->is_connected() ) {
161 $this->redirect_to_admin_page();
162 }
163
164 if ( empty( $_REQUEST['state'] ) || $_REQUEST['state'] !== $this->get( 'state' ) ) {
165 $this->add_notice( 'Get Token: Invalid Request.', 'error' );
166 $this->redirect_to_admin_page();
167 }
168
169 $response = $this->request( 'get_token', [
170 'grant_type' => 'authorization_code',
171 'code' => $_REQUEST['code'],
172 'redirect_uri' => rawurlencode( $this->get_admin_url( 'get_token' ) ),
173 'client_id' => $this->get( 'client_id' ),
174 ] );
175
176 if ( is_wp_error( $response ) ) {
177 $notice = 'Cannot Get Token:' . $response->get_error_message();
178 $this->add_notice( $notice, 'error' );
179 $this->redirect_to_admin_page();
180 }
181
182 if ( ! empty( $response->data_share_opted_in ) && current_user_can( 'manage_options' ) ) {
183 Tracker::set_opt_in( true );
184 }
185
186 $this->delete( 'state' );
187 $this->set( (array) $response );
188
189 $this->after_connect();
190
191 // Add the notice *after* the method `after_connect`, so an app can redirect without the notice.
192 $this->add_notice( __( 'Connected Successfully.', 'elementor' ) );
193
194 $this->redirect_to_admin_page();
195 }
196
197 /**
198 * @since 2.3.0
199 * @access public
200 */
201 public function action_disconnect() {
202 if ( $this->is_connected() ) {
203 $this->disconnect();
204 $this->add_notice( __( 'Disconnected Successfully.', 'elementor' ) );
205 }
206
207 $this->redirect_to_admin_page();
208 }
209
210 /**
211 * @since 2.8.0
212 * @access public
213 */
214 public function action_reconnect() {
215 $this->disconnect();
216
217 $this->action_authorize();
218 }
219
220 /**
221 * @since 2.3.0
222 * @access public
223 */
224 public function get_admin_url( $action, $params = [] ) {
225 $params = [
226 'app' => $this->get_slug(),
227 'action' => $action,
228 'nonce' => wp_create_nonce( $this->get_slug() . $action ),
229 ] + $params;
230
231 // Encode base url, the encode is limited to 64 chars.
232 $admin_url = \Requests_IDNAEncoder::encode( get_admin_url() );
233
234 $admin_url .= 'admin.php?page=' . Admin::PAGE_ID;
235
236 return add_query_arg( $params, $admin_url );
237 }
238
239 /**
240 * @since 2.3.0
241 * @access public
242 */
243 public function is_connected() {
244 return (bool) $this->get( 'access_token' );
245 }
246
247 /**
248 * @since 2.3.0
249 * @access protected
250 */
251 protected function init() {}
252
253 /**
254 * @since 2.3.0
255 * @access protected
256 */
257 protected function init_data() {}
258
259 /**
260 * @since 2.3.0
261 * @access protected
262 */
263 protected function after_connect() {}
264
265 /**
266 * @since 2.3.0
267 * @access public
268 */
269 public function get( $key, $default = null ) {
270 $this->init_data();
271
272 return isset( $this->data[ $key ] ) ? $this->data[ $key ] : $default;
273 }
274
275 /**
276 * @since 2.3.0
277 * @access protected
278 */
279 protected function set( $key, $value = null ) {
280 $this->init_data();
281
282 if ( is_array( $key ) ) {
283 $this->data = array_replace_recursive( $this->data, $key );
284 } else {
285 $this->data[ $key ] = $value;
286 }
287
288 $this->update_settings();
289 }
290
291 /**
292 * @since 2.3.0
293 * @access protected
294 */
295 protected function delete( $key = null ) {
296 $this->init_data();
297
298 if ( $key ) {
299 unset( $this->data[ $key ] );
300 } else {
301 $this->data = [];
302 }
303
304 $this->update_settings();
305 }
306
307 /**
308 * @since 2.3.0
309 * @access protected
310 */
311 protected function add( $key, $value, $default = '' ) {
312 $new_value = $this->get( $key, $default );
313
314 if ( is_array( $new_value ) ) {
315 $new_value[] = $value;
316 } elseif ( is_string( $new_value ) ) {
317 $new_value .= $value;
318 } elseif ( is_numeric( $new_value ) ) {
319 $new_value += $value;
320 }
321
322 $this->set( $key, $new_value );
323 }
324
325 /**
326 * @since 2.3.0
327 * @access protected
328 */
329 protected function add_notice( $content, $type = 'success' ) {
330 $this->add( 'notices', compact( 'content', 'type' ), [] );
331 }
332
333 /**
334 * @since 2.3.0
335 * @access protected
336 */
337 protected function request( $action, $request_body = [], $as_array = false ) {
338 $request_body = [
339 'app' => $this->get_slug(),
340 'access_token' => $this->get( 'access_token' ),
341 'client_id' => $this->get( 'client_id' ),
342 'local_id' => get_current_user_id(),
343 'site_key' => $this->get_site_key(),
344 'home_url' => trailingslashit( home_url() ),
345 ] + $request_body;
346
347 $headers = [];
348
349 if ( $this->is_connected() ) {
350 $headers['X-Elementor-Signature'] = hash_hmac( 'sha256', wp_json_encode( $request_body, JSON_NUMERIC_CHECK ), $this->get( 'access_token_secret' ) );
351 }
352
353 $response = wp_remote_post( $this->get_api_url() . '/' . $action, [
354 'body' => $request_body,
355 'headers' => $headers,
356 'timeout' => 25,
357 ] );
358
359 if ( is_wp_error( $response ) ) {
360 wp_die( $response, [
361 'back_link' => true,
362 ] );
363 }
364
365 $body = wp_remote_retrieve_body( $response );
366 $response_code = (int) wp_remote_retrieve_response_code( $response );
367
368 if ( ! $response_code ) {
369 return new \WP_Error( 500, 'No Response' );
370
371 }
372
373 // Server sent a success message without content.
374 if ( 'null' === $body ) {
375 $body = true;
376 }
377
378 $body = json_decode( $body, $as_array );
379
380 if ( false === $body ) {
381 return new \WP_Error( 422, 'Wrong Server Response' );
382 }
383
384 if ( 200 !== $response_code ) {
385 // In case $as_array = true.
386 $body = (object) $body;
387
388 $message = isset( $body->message ) ? $body->message : wp_remote_retrieve_response_message( $response );
389 $code = (int) ( isset( $body->code ) ? $body->code : $response_code );
390
391 if ( 401 === $code ) {
392 $this->delete();
393 $this->action_authorize();
394 }
395
396 return new \WP_Error( $code, $message );
397 }
398
399 return $body;
400 }
401
402 /**
403 * @since 2.3.0
404 * @access protected
405 */
406 protected function get_api_url() {
407 return static::API_URL . '/' . $this->get_slug();
408 }
409
410 /**
411 * @since 2.3.0
412 * @access protected
413 */
414 protected function get_remote_site_url() {
415 return static::SITE_URL . '/' . $this->get_slug();
416 }
417
418 /**
419 * @since 2.3.0
420 * @access protected
421 */
422 protected function get_remote_authorize_url() {
423 $redirect_uri = $this->get_auth_redirect_uri();
424
425 $url = add_query_arg( [
426 'action' => 'authorize',
427 'response_type' => 'code',
428 'client_id' => $this->get( 'client_id' ),
429 'auth_secret' => $this->get( 'auth_secret' ),
430 'state' => $this->get( 'state' ),
431 'redirect_uri' => rawurlencode( $redirect_uri ),
432 'may_share_data' => current_user_can( 'manage_options' ) && ! Tracker::is_allow_track(),
433 'reconnect_nonce' => wp_create_nonce( $this->get_slug() . 'reconnect' ),
434 ], $this->get_remote_site_url() );
435
436 return $url;
437 }
438
439 /**
440 * @since 2.3.0
441 * @access protected
442 */
443 protected function redirect_to_admin_page( $url = '' ) {
444 if ( ! $url ) {
445 $url = Admin::$url;
446 }
447
448 switch ( $this->auth_mode ) {
449 case 'popup':
450 $this->print_popup_close_script( $url );
451 break;
452
453 case 'cli':
454 $this->admin_notice();
455 die;
456
457 default:
458 wp_safe_redirect( $url );
459 die;
460 }
461 }
462
463 /**
464 * @since 2.3.0
465 * @access protected
466 */
467 protected function set_client_id() {
468 if ( $this->get( 'client_id' ) ) {
469 return;
470 }
471
472 $response = $this->request( 'get_client_id' );
473
474 if ( is_wp_error( $response ) ) {
475 wp_die( $response, $response->get_error_message() );
476 }
477
478 $this->set( 'client_id', $response->client_id );
479 $this->set( 'auth_secret', $response->auth_secret );
480 }
481
482 /**
483 * @since 2.3.0
484 * @access protected
485 */
486 protected function set_request_state() {
487 $this->set( 'state', wp_generate_password( 12, false ) );
488 }
489
490 /**
491 * @since 2.3.0
492 * @access protected
493 */
494 protected function print_popup_close_script( $url ) {
495 ?>
496 <script>
497 if ( opener && opener !== window ) {
498 opener.jQuery( 'body' ).trigger( 'elementor/connect/success/<?php echo esc_attr( $_REQUEST['callback_id'] ); ?>' );
499 window.close();
500 opener.focus();
501 } else {
502 location = '<?php echo $url; ?>';
503 }
504 </script>
505 <?php
506 die;
507 }
508
509 /**
510 * @since 2.3.0
511 * @access protected
512 */
513 protected function disconnect() {
514 if ( $this->is_connected() ) {
515 // Try update the server, but not needed to handle errors.
516 $this->request( 'disconnect' );
517 }
518
519 $this->delete();
520 }
521
522 /**
523 * @since 2.3.0
524 * @access protected
525 */
526 protected function get_site_key() {
527 $site_key = get_option( 'elementor_connect_site_key' );
528
529 if ( ! $site_key ) {
530 $site_key = md5( uniqid( wp_generate_password() ) );
531 update_option( 'elementor_connect_site_key', $site_key );
532 }
533
534 return $site_key;
535 }
536
537 protected function redirect_to_remote_authorize_url() {
538 switch ( $this->auth_mode ) {
539 case 'cli':
540 $this->get_app_token_from_cli_token( $_REQUEST['token'] );
541 return;
542 default:
543 wp_redirect( $this->get_remote_authorize_url() );
544 die;
545 }
546 }
547
548 protected function get_auth_redirect_uri() {
549 $redirect_uri = $this->get_admin_url( 'get_token' );
550
551 switch ( $this->auth_mode ) {
552 case 'popup':
553 $redirect_uri = add_query_arg( [
554 'mode' => 'popup',
555 'callback_id' => esc_attr( $_REQUEST['callback_id'] ),
556 ], $redirect_uri );
557 break;
558 }
559
560 return $redirect_uri;
561 }
562
563
564 protected function print_notices( $notices ) {
565 switch ( $this->auth_mode ) {
566 case 'cli':
567 foreach ( $notices as $notice ) {
568 printf( '[%s] %s', $notice['type'], $notice['content'] );
569 }
570 break;
571 default:
572 /**
573 * @var Admin_Notices $admin_notices
574 */
575 $admin_notices = Plugin::$instance->admin->get_component( 'admin-notices' );
576
577 foreach ( $notices as $notice ) {
578 $options = [
579 'description' => wp_kses_post( wpautop( $notice['content'] ) ),
580 'type' => $notice['type'],
581 'icon' => false,
582 ];
583
584 $admin_notices->print_admin_notice( $options );
585 }
586 }
587 }
588
589 protected function get_app_info() {
590 return [];
591 }
592
593 protected function print_app_info() {
594 $app_info = $this->get_app_info();
595
596 foreach ( $app_info as $key => $item ) {
597 if ( $item['value'] ) {
598 $status = 'Exist';
599 $color = 'green';
600 } else {
601 $status = 'Empty';
602 $color = 'red';
603 }
604
605 printf( '%s: <strong style="color:%s">%s</strong><br>', $item['label'], $color, $status );
606 }
607
608 }
609
610 /**
611 * @since 2.3.0
612 * @access public
613 */
614 public function __construct() {
615 add_action( 'admin_notices', [ $this, 'admin_notice' ] );
616
617 if ( isset( $_REQUEST['mode'] ) ) { // phpcs:ignore -- nonce validation is not require here.
618 $allowed_auth_modes = [
619 'popup',
620 ];
621
622 if ( defined( 'WP_CLI' ) && WP_CLI ) {
623 $allowed_auth_modes[] = 'cli';
624 }
625
626 $mode = $_REQUEST['mode']; // phpcs:ignore -- nonce validation is not require here.
627
628 if ( in_array( $mode, $allowed_auth_modes, true ) ) {
629 $this->auth_mode = $mode;
630 }
631 }
632
633 /**
634 * Allow extended apps to customize the __construct without call parent::__construct.
635 */
636 $this->init();
637 }
638 }
639