PluginProbe
OttoKit: All-in-One Automation Platform / 1.1.20
OttoKit: All-in-One Automation Platform v1.1.20
1.1.38 1.1.37 1.1.36 1.1.35 1.1.34 1.1.33 1.1.32 1.1.31 1.1.30 1.1.29 1.1.28 1.1.27 1.1.9 trunk 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.19 1.0.20 All 124 releases
suretriggers / src / Controllers / RestController.php
RestController.php
648 lines 18.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * RestController.
4 * php version 5.6
5 *
6 * @category RestController
7 * @package SureTriggers
8 * @author BSF <username@example.com>
9 * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
10 * @link https://www.brainstormforce.com/
11 * @since 1.0.0
12 */
13
14 namespace SureTriggers\Controllers;
15
16 use Exception;
17 use SureTriggers\Integrations\WordPress\WordPress;
18 use SureTriggers\Traits\SingletonLoader;
19 use SureTriggers\Models\SaasApiToken;
20 use WP_REST_Request;
21 use WP_REST_Response;
22 use WP_Error;
23 use Throwable;
24 use RuntimeException;
25 use InvalidArgumentException;
26
27 /**
28 * RestController
29 *
30 * @category RestController
31 * @package SureTriggers
32 * @author BSF <username@example.com>
33 * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
34 * @link https://www.brainstormforce.com/
35 * @since 1.0.0
36 */
37 class RestController {
38
39 /**
40 * Access token for authentication.
41 *
42 * @var string $acccess_token
43 */
44 private $secret_key;
45
46 use SingletonLoader;
47
48 /**
49 * Initialize data.
50 */
51 public function __construct() {
52 $this->secret_key = SaasApiToken::get();
53 add_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );
54 add_filter( 'debug_information', [ $this, 'sure_triggers_connection_info' ] );
55 }
56
57 /**
58 * Permission callback for rest api after determination of current user.
59 *
60 * @param WP_REST_Request $request Request.
61 *
62 * @return bool
63 */
64 public function autheticate_user( $request ) {
65 $secret_key = $request->get_header( 'st_authorization' );
66
67 if ( ! is_string( $secret_key ) || empty( $secret_key ) || empty( $this->secret_key ) ) {
68 return false;
69 }
70
71 $parsed = sscanf( $secret_key, 'Bearer %s' );
72 if ( is_array( $parsed ) ) {
73 list( $secret_key ) = $parsed;
74 }
75
76 if ( empty( $secret_key ) ) {
77 return false;
78 }
79
80 if ( $this->secret_key !== $secret_key ) {
81 return false;
82 }
83
84 return hash_equals( $this->secret_key, $secret_key );
85 }
86
87 /**
88 * Create WP Connection.
89 *
90 * @param WP_REST_Request $request Request data.
91 * @return WP_REST_Response
92 */
93 public function create_wp_connection( $request ) {
94
95 $user_agent = $request->get_header( 'user-agent' );
96 $allowed_agents = [ 'OttoKit', 'SureTriggers' ];
97 if ( ! in_array( $user_agent, $allowed_agents, true ) ) {
98 return new WP_REST_Response(
99 [
100 'success' => false,
101 'data' => 'Unauthorized',
102 ],
103 403
104 );
105 }
106 $params = wp_unslash( $request->get_json_params() );
107
108 $username = isset( $params['wp-username'] ) ? sanitize_user( $params['wp-username'] ) : '';
109 $password = isset( $params['wp-password'] ) ? $params['wp-password'] : '';
110
111 if ( empty( $username ) || empty( $password ) ) {
112 return new WP_REST_Response(
113 [
114 'success' => false,
115 'data' => 'Authentication failed.',
116 ],
117 401
118 );
119 }
120
121 $user = wp_authenticate_application_password( null, $username, $password );
122
123 if ( ! ( $user instanceof \WP_User ) ) {
124 return new WP_REST_Response(
125 [
126 'success' => false,
127 'data' => 'Authentication failed.',
128 ],
129 403
130 );
131 }
132
133 if ( ! user_can( $user, 'administrator' ) ) {
134 return new WP_REST_Response(
135 [
136 'success' => false,
137 'data' => 'Not allowed to perform this action.',
138 ],
139 403
140 );
141 }
142
143 $connection_status = isset( $params['connection-status'] ) ? sanitize_text_field( $params['connection-status'] ) : false;
144 $access_key = isset( $params['sure-triggers-access-key'] ) ? sanitize_text_field( $params['sure-triggers-access-key'] ) : '';
145 $connected_email_id = isset( $params['connected_email'] ) ? sanitize_email( $params['connected_email'] ) : '';
146
147 if ( empty( $connection_status ) ) {
148 return new WP_REST_Response(
149 [
150 'success' => false,
151 'data' => 'Connection denied.',
152 ],
153 403
154 );
155 }
156
157 if ( empty( $access_key ) ) {
158 return new WP_REST_Response(
159 [
160 'success' => false,
161 'data' => 'Invalid access key.',
162 ],
163 403
164 );
165 }
166
167 $response = self::verify_user_token( $access_key );
168 if ( empty( $response ) || is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
169 return new WP_REST_Response(
170 [
171 'success' => false,
172 'data' => 'Verification failed.',
173 ],
174 403
175 );
176 }
177
178 SaasApiToken::save( $access_key );
179 OptionController::set_option( 'connected_email_key', $connected_email_id );
180
181 return new WP_REST_Response(
182 [
183 'success' => true,
184 'data' => 'Connected successfully.',
185 ],
186 200
187 );
188 }
189
190 /**
191 * Verify user token.
192 *
193 * @param string $token Token.
194 *
195 * @return array|WP_Error $response Response.
196 */
197 public static function verify_user_token( $token = '' ) {
198 if ( empty( $token ) ) {
199 $token = SaasApiToken::get();
200 }
201 $args = [
202 'body' => [
203 'token' => $token,
204 'saas-token' => $token,
205 'base_url' => str_replace( '/wp-json/', '', get_rest_url() ),
206 ],
207 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
208 ];
209 $response = wp_remote_post( SURE_TRIGGERS_API_SERVER_URL . '/token/verify', $args );
210 if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) {
211 $response_body = wp_remote_retrieve_body( $response );
212 $data = json_decode( $response_body, true );
213 if ( is_array( $data ) && isset( $data['plan_id'] ) ) {
214 // Save plan_id to database.
215 $plan_data = [
216 'plan_id' => sanitize_text_field( $data['plan_id'] ),
217 ];
218
219 update_option( 'suretriggers_lifetime_user_plan_data', $plan_data );
220 }
221 }
222
223 return $response;
224 }
225
226 /**
227 * Verify connection.
228 *
229 * @return array|WP_Error $response Response.
230 */
231 public static function suretriggers_verify_wp_connection() {
232 $args = [
233 'body' => [
234 'saas-token' => SaasApiToken::get(),
235 'base_url' => str_replace( '/wp-json/', '', get_rest_url() ),
236 'plugin_version' => SURE_TRIGGERS_VER,
237 ],
238 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
239 ];
240 $response = wp_remote_post( SURE_TRIGGERS_API_SERVER_URL . '/connection/wordpress/ping', $args );
241 return $response;
242 }
243
244 /**
245 * Authenticate User for API calls.
246 *
247 * @param array|object $user USer.
248 *
249 * @return int|null|WP_Error|array|object
250 */
251 public function basic_auth_handler( $user ) {
252 // Don't authenticate twice.
253 if ( ! empty( $user ) ) {
254 return $user;
255 }
256
257 if ( ! is_ssl() ) {
258 return new WP_Error( 'insecure_connection', 'Use a secure HTTPS connection to access this resource.', [ 'status' => 403 ] );
259 }
260
261 // Check that we're trying to authenticate.
262 if ( ! isset( $_SERVER['PHP_AUTH_USER'] ) || ! isset( $_SERVER['PHP_AUTH_PW'] ) ) { //phpcs:ignore
263 return $user;
264 }
265
266 $username = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ); //phpcs:ignore
267 $password = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); //phpcs:ignore
268
269 /**
270 * In multi-site, wp_authenticate_spam_check filter is run on authentication. This filter calls.
271 * get_currentuserinfo which in turn calls the determine_current_user filter. This leads to infinite.
272 * recursion and a stack overflow unless the current function is removed from the determine_current_user.
273 * filter during authentication.
274 */
275 remove_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );
276
277 $user = wp_authenticate( $username, $password );
278
279 add_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );
280
281 if ( is_wp_error( $user ) ) {
282 return null;
283 }
284
285 return $user->ID;
286 }
287
288 /**
289 * Authenticate user for new connection create api.
290 *
291 * @return bool
292 */
293 public function is_current_user() {
294 if ( current_user_can( 'manage_options' ) ) {
295 return true;
296 }
297 return false;
298 }
299
300 /**
301 * Execute action events.
302 *
303 * @param WP_REST_Request $request Request data.
304 * @return WP_REST_Response|object
305 */
306 public function run_action( $request ) {
307 $request->get_param( 'wp_user_id' );
308
309 $user_id = $request->get_param( 'wp_user_id' );
310 $automation_id = $request->get_param( 'automation_id' );
311 $integration = $request->get_param( 'integration' );
312 $action_type = $request->get_param( 'type_event' );
313 $selected_options = $request->get_param( 'selected_options' );
314 $context = $request->get_param( 'context' );
315 $fields = $request->get_param( 'fields' );
316
317 if ( empty( $user_id ) ) {
318 $user_id = isset( $context['pluggable_data']['wp_user_id'] ) ? sanitize_text_field( $context['pluggable_data']['wp_user_id'] ) : '';
319 }
320
321 if ( empty( $integration ) || empty( $action_type ) ) {
322 return self::error_message( 'Integration or action type is missing' );
323 }
324
325 if ( isset( $selected_options['wp_user_email'] ) && ! ( 'EDD' === $integration && 'find_user_purchased_download' === $action_type ) ) {
326 $is_valid = WordPress::validate_email( $selected_options['wp_user_email'] );
327
328 if ( ! is_object( $is_valid ) || ! property_exists( $is_valid, 'valid' ) || ! property_exists( $is_valid, 'multiple' ) ) {
329 return self::error_message( 'Invalid email validation response.' );
330 }
331
332 if ( ! $is_valid->valid ) {
333 if ( $is_valid->multiple ) {
334 return self::error_message( 'One or more email address is not valid.' );
335 } else {
336 return self::error_message( 'Email address is not valid.' );
337 }
338 }
339
340 if ( str_contains( $selected_options['wp_user_email'], ',' ) ) {
341 $email_list = explode( ',', $selected_options['wp_user_email'] );
342
343 foreach ( $email_list as $single_email ) {
344 if ( ! email_exists( trim( $single_email ) ) ) {
345 return self::error_message( 'User with email ' . $single_email . ' does not exists.' );
346 }
347 }
348 } else {
349 if ( ! email_exists( $selected_options['wp_user_email'] ) ) {
350 return self::error_message( 'User with email ' . $selected_options['wp_user_email'] . ' does not exists.' );
351 }
352 }
353 }
354 $registered_actions = EventController::get_instance()->actions;
355 $action_event = $registered_actions[ $integration ][ $action_type ];
356
357 $fully_qualified_class_name = "\SureTriggers\Integrations\\$integration\\$integration";
358
359 $fun_params = [
360 $user_id,
361 $automation_id,
362 $fields,
363 $selected_options,
364 $context,
365 ];
366
367 try {
368 // Check if integration class exists and plugin is active.
369 if ( class_exists( $fully_qualified_class_name ) ) {
370 $class_obj = new $fully_qualified_class_name();
371 $is_plugin_active = false;
372 if ( method_exists( $class_obj, 'is_plugin_installed' ) ) {
373 $is_plugin_active = $class_obj->is_plugin_installed();
374 }
375 if ( ! $is_plugin_active ) {
376 return self::error_message( $integration . ' plugin is not installed or activated.', 400 );
377 }
378 } else {
379 return self::error_message( 'Integration class not found.', 400 );
380 }
381
382 // Execute the action with error handling.
383 $result = null;
384 try {
385 $result = call_user_func_array(
386 $action_event['function'],
387 $fun_params
388 );
389
390 return self::success_message( (array) $result );
391 } catch ( InvalidArgumentException $arg_error ) {
392 return self::error_message( 'Invalid argument: ' . $arg_error->getMessage(), 400 );
393 } catch ( RuntimeException $runtime_error ) {
394 return self::error_message( 'Runtime error: ' . $runtime_error->getMessage(), 500 );
395 } catch ( Exception $action_error ) {
396 return self::error_message( 'Action execution failed: ' . $action_error->getMessage(), 400 );
397 } catch ( Throwable $php_error ) {
398 return self::error_message( 'PHP error in action: ' . $php_error->getMessage(), 500 );
399 }
400 } catch ( Exception $e ) {
401 return self::error_message( 'Error executing action: ' . $e->getMessage(), 400 );
402 }
403 }
404
405 /**
406 * Error message format.
407 *
408 * @param string $message Error message.
409 * @param int $status Error message.
410 *
411 * @return object
412 */
413 public static function error_message( $message, $status = 401 ) {
414 return new WP_REST_Response(
415 [
416 'success' => false,
417 'data' => [
418 'errors' => $message,
419 ],
420 ],
421 $status
422 );
423 }
424
425 /**
426 * Success message format.
427 *
428 * @param array $data response data to be sent.
429 *
430 * @return object
431 */
432 public static function success_message( $data = [] ) {
433 $result = [];
434
435 if ( ! empty( $data ) ) {
436 $result['result'] = $data;
437 }
438
439 return new WP_REST_Response(
440 [
441 'success' => true,
442 'data' => $result,
443 ],
444 200
445 );
446
447 }
448
449 /**
450 * Add/Remove/Update the triggers..
451 * When new/update/remove automation on Sass then execute this endpoint to update the automation.
452 *
453 * @param WP_REST_Request $request Request data.
454 * @return object
455 */
456 public function manage_triggers( $request ) {
457 $events = $request->get_param( 'events' ) ? json_decode( stripslashes( $request->get_param( 'events' ) ), true ) : [];
458
459 // Selected field data from the trigger.
460 $data = $request->get_param( 'data' ) ? json_decode( stripslashes( $request->get_param( 'data' ) ), true ) : [];
461
462 // Get the trigger data from the option and append data in trigger data option.
463 $trigger_data = OptionController::get_option( 'trigger_data' );
464 if ( empty( $trigger_data ) ) {
465 $trigger_data = [];
466 }
467
468 if ( is_array( $data ) && is_array( $events ) ) {
469 $index = array_search( $data['trigger'], array_column( $events, 'trigger' ) );
470 if ( is_array( $trigger_data ) && false !== $index && $data['integration'] === $events[ $index ]['integration'] ) {
471 $trigger_data[ $data['integration'] ][ $data['trigger'] ]['selected_options'] = $data['selected_data'];
472 }
473 }
474
475 OptionController::set_option( 'triggers', (array) $events );
476 // Set the new option for the trigger data.
477 OptionController::set_option( 'trigger_data', (array) $trigger_data );
478 $events = array_column( (array) $events, 'trigger' );
479 return self::success_message(
480 [
481 'events' => $events,
482 'data' => $trigger_data,
483 ]
484 );
485 }
486
487 /**
488 * Send response to Saas that trigger is executed.
489 *
490 * @param array $trigger_data Trigger data.
491 *
492 * @return bool
493 */
494 public function trigger_listener( $trigger_data ) {
495 // Pass unique WordPress webhook id.
496 $wordpress_webhook_uuid = str_replace( '-', '', wp_generate_uuid4() );
497 $site_url = esc_url_raw( str_replace( '/wp-json/', '', get_site_url() ) );
498 $site_url = preg_replace( '/^https?:\/\//', '', $site_url );
499 $encoded_site_url = urlencode( (string) $site_url );
500 $trigger_data['wordpress_webhook_uuid'] = $wordpress_webhook_uuid . '_' . $encoded_site_url;
501 $args = [
502 'headers' => [
503 'Authorization' => 'Bearer ' . $this->secret_key,
504 'Referer' => str_replace( '/wp-json/', '', get_site_url() ),
505 'RefererRestUrl' => str_replace( '/wp-json/', '', get_rest_url() ),
506 ],
507 'body' => json_decode( (string) wp_json_encode( $trigger_data ), true ),
508 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
509 ];
510
511 /**
512 *
513 * Ignore line
514 *
515 * @phpstan-ignore-next-line
516 */
517 $response = wp_remote_post( SURE_TRIGGERS_WEBHOOK_SERVER_URL . '/wordpress/webhook', $args );
518 // Store every webhook requests.
519 $error_info = wp_remote_retrieve_body( $response );
520 if ( 405 === wp_remote_retrieve_response_code( $response ) ) {
521 $error_info = wp_remote_retrieve_response_message( $response );
522 }
523 if ( 0 === wp_remote_retrieve_response_code( $response ) ) {
524 $error_info = __( 'Service not available', 'suretriggers' );
525 }
526 unset( $args['headers']['Authorization'] );
527 WebhookRequestsController::suretriggers_log_request( (string) wp_json_encode( $args ), (int) wp_remote_retrieve_response_code( $response ), $error_info );
528
529 if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
530 return true;
531 }
532
533 return false;
534 }
535
536 /**
537 * Disconnect connection
538 *
539 * @param WP_REST_Request $request Request data.
540 * @return object
541 */
542 public function connection_disconnect( $request ) {
543 SaasApiToken::save( null );
544 return self::success_message();
545 }
546
547 /**
548 * Test Trigger
549 * When test trigger is initiated on Sass then execute this endpoint to create a transient for identifying trigger event.
550 *
551 * @param WP_REST_Request $request Request data.
552 * @return void
553 */
554 public function test_triggers( $request ) {
555 $test_triggers = (array) OptionController::get_option( 'test_triggers' );
556 $event = [
557 'trigger' => $request->get_param( 'trigger' ),
558 'integration' => $request->get_param( 'integration' ),
559 ];
560
561 // if request is to delete the transient, delete it and return.
562 if ( $request->get_param( 'clear_transient_data' ) === 'yes' ) {
563 $test_triggers = array_filter(
564 $test_triggers,
565 function ( $v ) use ( $event ) {
566 return $v !== $event;
567 }
568 );
569 OptionController::set_option( 'test_triggers', $test_triggers );
570
571 return;
572 }
573
574 $test_triggers[] = $event;
575 $test_triggers = array_unique( $test_triggers, SORT_REGULAR );
576 $tmp_test_triggers = [];
577
578 foreach ( $test_triggers as $test_trigger ) {
579 if ( ! empty( $test_trigger['trigger'] ) ) {
580 $tmp_test_triggers[] = $test_trigger;
581 }
582 }
583
584 OptionController::set_option( 'test_triggers', $tmp_test_triggers );
585 }
586
587 /**
588 * OttoKit Connection Info
589 *
590 * @param array $debug_info Info data.
591 * @return array
592 */
593 public function sure_triggers_connection_info( $debug_info ) {
594 // Verify if OttoKit is connected successfully.
595 $response = self::verify_user_token();
596 $connection = ( wp_remote_retrieve_response_code( $response ) === 200 );
597 if ( $connection ) {
598 $connection_status = __( 'Connection Successfully Set', 'suretriggers' );
599 } else {
600 $connection_status = __( 'Error in Connection', 'suretriggers' );
601 }
602
603 $fields = [
604 'suretriggers_status' => [
605 'label' => __( 'OttoKit Status', 'suretriggers' ),
606 'value' => $connection_status,
607 'private' => false,
608 ],
609 'rest_url' => [
610 'label' => __( 'Rest URL', 'suretriggers' ),
611 'value' => esc_url( get_rest_url() ),
612 'private' => false,
613 ],
614 'suretriggers_version' => [
615 'label' => __( 'OttoKit Version', 'suretriggers' ),
616 'value' => SURE_TRIGGERS_VER,
617 'private' => false,
618 ],
619 ];
620
621 if ( defined( 'SURETRIGGERS_ENCRYPTION_KEY' ) ) {
622 $fields['suretriggers_encryption_key'] = [
623 'label' => __( 'Encryption Key', 'suretriggers' ),
624 'value' => __( 'Defined', 'suretriggers' ),
625 'private' => false,
626 ];
627 }
628
629 if ( defined( 'SURETRIGGERS_ENCRYPTION_SALT' ) ) {
630 $fields['suretriggers_encryption_salt'] = [
631 'label' => __( 'Encryption Salt', 'suretriggers' ),
632 'value' => __( 'Defined', 'suretriggers' ),
633 'private' => false,
634 ];
635 }
636
637 $debug_info['suretriggers'] = [
638 'label' => __( 'OttoKit', 'suretriggers' ),
639 'fields' => $fields,
640 ];
641
642 return $debug_info;
643 }
644
645 }
646
647 RestController::get_instance();
648