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

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

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