PluginProbe
OttoKit: All-in-One Automation Platform / 1.1.10
OttoKit: All-in-One Automation Platform v1.1.10
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 in OttoKit: All-in-One Automation Platform 1.1.10, at src/Controllers/RestController.php

635 lines 18.1 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 SaasApiToken::save( $access_key );
178 OptionController::set_option( 'connected_email_key', $connected_email_id );
179
180 return new WP_REST_Response(
181 [
182 'success' => true,
183 'data' => 'Connected successfully.',
184 ],
185 200
186 );
187 }
188
189 /**
190 * Verify user token.
191 *
192 * @param string $token Token.
193 *
194 * @return array|WP_Error $response Response.
195 */
196 public static function verify_user_token( $token = '' ) {
197 if ( empty( $token ) ) {
198 $token = SaasApiToken::get();
199 }
200 $args = [
201 'body' => [
202 'token' => $token,
203 'saas-token' => $token,
204 'base_url' => str_replace( '/wp-json/', '', get_rest_url() ),
205 ],
206 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
207 ];
208 $response = wp_remote_post( SURE_TRIGGERS_API_SERVER_URL . '/token/verify', $args );
209
210 return $response;
211 }
212
213 /**
214 * Verify connection.
215 *
216 * @return array|WP_Error $response Response.
217 */
218 public static function suretriggers_verify_wp_connection() {
219 $args = [
220 'body' => [
221 'saas-token' => SaasApiToken::get(),
222 'base_url' => str_replace( '/wp-json/', '', get_rest_url() ),
223 'plugin_version' => SURE_TRIGGERS_VER,
224 ],
225 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
226 ];
227 $response = wp_remote_post( SURE_TRIGGERS_API_SERVER_URL . '/connection/wordpress/ping', $args );
228 return $response;
229 }
230
231 /**
232 * Authenticate User for API calls.
233 *
234 * @param array|object $user USer.
235 *
236 * @return int|null|WP_Error|array|object
237 */
238 public function basic_auth_handler( $user ) {
239 // Don't authenticate twice.
240 if ( ! empty( $user ) ) {
241 return $user;
242 }
243
244 if ( ! is_ssl() ) {
245 return new WP_Error( 'insecure_connection', 'Use a secure HTTPS connection to access this resource.', [ 'status' => 403 ] );
246 }
247
248 // Check that we're trying to authenticate.
249 if ( ! isset( $_SERVER['PHP_AUTH_USER'] ) || ! isset( $_SERVER['PHP_AUTH_PW'] ) ) { //phpcs:ignore
250 return $user;
251 }
252
253 $username = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) ); //phpcs:ignore
254 $password = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_PW'] ) ); //phpcs:ignore
255
256 /**
257 * In multi-site, wp_authenticate_spam_check filter is run on authentication. This filter calls.
258 * get_currentuserinfo which in turn calls the determine_current_user filter. This leads to infinite.
259 * recursion and a stack overflow unless the current function is removed from the determine_current_user.
260 * filter during authentication.
261 */
262 remove_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );
263
264 $user = wp_authenticate( $username, $password );
265
266 add_filter( 'determine_current_user', [ $this, 'basic_auth_handler' ], 20 );
267
268 if ( is_wp_error( $user ) ) {
269 return null;
270 }
271
272 return $user->ID;
273 }
274
275 /**
276 * Authenticate user for new connection create api.
277 *
278 * @return bool
279 */
280 public function is_current_user() {
281 if ( current_user_can( 'manage_options' ) ) {
282 return true;
283 }
284 return false;
285 }
286
287 /**
288 * Execute action events.
289 *
290 * @param WP_REST_Request $request Request data.
291 * @return WP_REST_Response|object
292 */
293 public function run_action( $request ) {
294 $request->get_param( 'wp_user_id' );
295
296 $user_id = $request->get_param( 'wp_user_id' );
297 $automation_id = $request->get_param( 'automation_id' );
298 $integration = $request->get_param( 'integration' );
299 $action_type = $request->get_param( 'type_event' );
300 $selected_options = $request->get_param( 'selected_options' );
301 $context = $request->get_param( 'context' );
302 $fields = $request->get_param( 'fields' );
303
304 if ( empty( $user_id ) ) {
305 $user_id = isset( $context['pluggable_data']['wp_user_id'] ) ? sanitize_text_field( $context['pluggable_data']['wp_user_id'] ) : '';
306 }
307
308 if ( empty( $integration ) || empty( $action_type ) ) {
309 return self::error_message( 'Integration or action type is missing' );
310 }
311
312 if ( isset( $selected_options['wp_user_email'] ) && ! ( 'EDD' === $integration && 'find_user_purchased_download' === $action_type ) ) {
313 $is_valid = WordPress::validate_email( $selected_options['wp_user_email'] );
314
315 if ( ! is_object( $is_valid ) || ! property_exists( $is_valid, 'valid' ) || ! property_exists( $is_valid, 'multiple' ) ) {
316 return self::error_message( 'Invalid email validation response.' );
317 }
318
319 if ( ! $is_valid->valid ) {
320 if ( $is_valid->multiple ) {
321 return self::error_message( 'One or more email address is not valid.' );
322 } else {
323 return self::error_message( 'Email address is not valid.' );
324 }
325 }
326
327 if ( str_contains( $selected_options['wp_user_email'], ',' ) ) {
328 $email_list = explode( ',', $selected_options['wp_user_email'] );
329
330 foreach ( $email_list as $single_email ) {
331 if ( ! email_exists( trim( $single_email ) ) ) {
332 return self::error_message( 'User with email ' . $single_email . ' does not exists.' );
333 }
334 }
335 } else {
336 if ( ! email_exists( $selected_options['wp_user_email'] ) ) {
337 return self::error_message( 'User with email ' . $selected_options['wp_user_email'] . ' does not exists.' );
338 }
339 }
340 }
341 $registered_actions = EventController::get_instance()->actions;
342 $action_event = $registered_actions[ $integration ][ $action_type ];
343
344 $fully_qualified_class_name = "\SureTriggers\Integrations\\$integration\\$integration";
345
346 $fun_params = [
347 $user_id,
348 $automation_id,
349 $fields,
350 $selected_options,
351 $context,
352 ];
353
354 try {
355 // Check if integration class exists and plugin is active.
356 if ( class_exists( $fully_qualified_class_name ) ) {
357 $class_obj = new $fully_qualified_class_name();
358 $is_plugin_active = false;
359 if ( method_exists( $class_obj, 'is_plugin_installed' ) ) {
360 $is_plugin_active = $class_obj->is_plugin_installed();
361 }
362 if ( ! $is_plugin_active ) {
363 return self::error_message( $integration . ' plugin is not installed or activated.', 400 );
364 }
365 } else {
366 return self::error_message( 'Integration class not found.', 400 );
367 }
368
369 // Execute the action with error handling.
370 $result = null;
371 try {
372 $result = call_user_func_array(
373 $action_event['function'],
374 $fun_params
375 );
376
377 return self::success_message( (array) $result );
378 } catch ( InvalidArgumentException $arg_error ) {
379 return self::error_message( 'Invalid argument: ' . $arg_error->getMessage(), 400 );
380 } catch ( RuntimeException $runtime_error ) {
381 return self::error_message( 'Runtime error: ' . $runtime_error->getMessage(), 500 );
382 } catch ( Exception $action_error ) {
383 return self::error_message( 'Action execution failed: ' . $action_error->getMessage(), 400 );
384 } catch ( Throwable $php_error ) {
385 return self::error_message( 'PHP error in action: ' . $php_error->getMessage(), 500 );
386 }
387 } catch ( Exception $e ) {
388 return self::error_message( 'Error executing action: ' . $e->getMessage(), 400 );
389 }
390 }
391
392 /**
393 * Error message format.
394 *
395 * @param string $message Error message.
396 * @param int $status Error message.
397 *
398 * @return object
399 */
400 public static function error_message( $message, $status = 401 ) {
401 return new WP_REST_Response(
402 [
403 'success' => false,
404 'data' => [
405 'errors' => $message,
406 ],
407 ],
408 $status
409 );
410 }
411
412 /**
413 * Success message format.
414 *
415 * @param array $data response data to be sent.
416 *
417 * @return object
418 */
419 public static function success_message( $data = [] ) {
420 $result = [];
421
422 if ( ! empty( $data ) ) {
423 $result['result'] = $data;
424 }
425
426 return new WP_REST_Response(
427 [
428 'success' => true,
429 'data' => $result,
430 ],
431 200
432 );
433
434 }
435
436 /**
437 * Add/Remove/Update the triggers..
438 * When new/update/remove automation on Sass then execute this endpoint to update the automation.
439 *
440 * @param WP_REST_Request $request Request data.
441 * @return object
442 */
443 public function manage_triggers( $request ) {
444 $events = $request->get_param( 'events' ) ? json_decode( stripslashes( $request->get_param( 'events' ) ), true ) : [];
445
446 // Selected field data from the trigger.
447 $data = $request->get_param( 'data' ) ? json_decode( stripslashes( $request->get_param( 'data' ) ), true ) : [];
448
449 // Get the trigger data from the option and append data in trigger data option.
450 $trigger_data = OptionController::get_option( 'trigger_data' );
451 if ( empty( $trigger_data ) ) {
452 $trigger_data = [];
453 }
454
455 if ( is_array( $data ) && is_array( $events ) ) {
456 $index = array_search( $data['trigger'], array_column( $events, 'trigger' ) );
457 if ( is_array( $trigger_data ) && false !== $index && $data['integration'] === $events[ $index ]['integration'] ) {
458 $trigger_data[ $data['integration'] ][ $data['trigger'] ]['selected_options'] = $data['selected_data'];
459 }
460 }
461
462 OptionController::set_option( 'triggers', (array) $events );
463 // Set the new option for the trigger data.
464 OptionController::set_option( 'trigger_data', (array) $trigger_data );
465 $events = array_column( (array) $events, 'trigger' );
466 return self::success_message(
467 [
468 'events' => $events,
469 'data' => $trigger_data,
470 ]
471 );
472 }
473
474 /**
475 * Send response to Saas that trigger is executed.
476 *
477 * @param array $trigger_data Trigger data.
478 *
479 * @return bool
480 */
481 public function trigger_listener( $trigger_data ) {
482 // Pass unique WordPress webhook id.
483 $wordpress_webhook_uuid = str_replace( '-', '', wp_generate_uuid4() );
484 $site_url = esc_url_raw( str_replace( '/wp-json/', '', get_site_url() ) );
485 $site_url = preg_replace( '/^https?:\/\//', '', $site_url );
486 $encoded_site_url = urlencode( (string) $site_url );
487 $trigger_data['wordpress_webhook_uuid'] = $wordpress_webhook_uuid . '_' . $encoded_site_url;
488 $args = [
489 'headers' => [
490 'Authorization' => 'Bearer ' . $this->secret_key,
491 'Referer' => str_replace( '/wp-json/', '', get_site_url() ),
492 'RefererRestUrl' => str_replace( '/wp-json/', '', get_rest_url() ),
493 ],
494 'body' => json_decode( (string) wp_json_encode( $trigger_data ), true ),
495 'timeout' => 60, //phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
496 ];
497
498 /**
499 *
500 * Ignore line
501 *
502 * @phpstan-ignore-next-line
503 */
504 $response = wp_remote_post( SURE_TRIGGERS_WEBHOOK_SERVER_URL . '/wordpress/webhook', $args );
505 // Store every webhook requests.
506 $error_info = wp_remote_retrieve_body( $response );
507 if ( 405 === wp_remote_retrieve_response_code( $response ) ) {
508 $error_info = wp_remote_retrieve_response_message( $response );
509 }
510 if ( 0 === wp_remote_retrieve_response_code( $response ) ) {
511 $error_info = __( 'Service not available', 'suretriggers' );
512 }
513 unset( $args['headers']['Authorization'] );
514 WebhookRequestsController::suretriggers_log_request( (string) wp_json_encode( $args ), (int) wp_remote_retrieve_response_code( $response ), $error_info );
515
516 if ( wp_remote_retrieve_response_code( $response ) === 200 ) {
517 return true;
518 }
519
520 return false;
521 }
522
523 /**
524 * Disconnect connection
525 *
526 * @param WP_REST_Request $request Request data.
527 * @return object
528 */
529 public function connection_disconnect( $request ) {
530 SaasApiToken::save( null );
531 return self::success_message();
532 }
533
534 /**
535 * Test Trigger
536 * When test trigger is initiated on Sass then execute this endpoint to create a transient for identifying trigger event.
537 *
538 * @param WP_REST_Request $request Request data.
539 * @return void
540 */
541 public function test_triggers( $request ) {
542 $test_triggers = (array) OptionController::get_option( 'test_triggers' );
543 $event = [
544 'trigger' => $request->get_param( 'trigger' ),
545 'integration' => $request->get_param( 'integration' ),
546 ];
547
548 // if request is to delete the transient, delete it and return.
549 if ( $request->get_param( 'clear_transient_data' ) === 'yes' ) {
550 $test_triggers = array_filter(
551 $test_triggers,
552 function ( $v ) use ( $event ) {
553 return $v !== $event;
554 }
555 );
556 OptionController::set_option( 'test_triggers', $test_triggers );
557
558 return;
559 }
560
561 $test_triggers[] = $event;
562 $test_triggers = array_unique( $test_triggers, SORT_REGULAR );
563 $tmp_test_triggers = [];
564
565 foreach ( $test_triggers as $test_trigger ) {
566 if ( ! empty( $test_trigger['trigger'] ) ) {
567 $tmp_test_triggers[] = $test_trigger;
568 }
569 }
570
571 OptionController::set_option( 'test_triggers', $tmp_test_triggers );
572 }
573
574 /**
575 * OttoKit Connection Info
576 *
577 * @param array $debug_info Info data.
578 * @return array
579 */
580 public function sure_triggers_connection_info( $debug_info ) {
581 // Verify if OttoKit is connected successfully.
582 $response = self::verify_user_token();
583 $connection = ( wp_remote_retrieve_response_code( $response ) === 200 );
584 if ( $connection ) {
585 $connection_status = __( 'Connection Successfully Set', 'suretriggers' );
586 } else {
587 $connection_status = __( 'Error in Connection', 'suretriggers' );
588 }
589
590 $fields = [
591 'suretriggers_status' => [
592 'label' => __( 'OttoKit Status', 'suretriggers' ),
593 'value' => $connection_status,
594 'private' => false,
595 ],
596 'rest_url' => [
597 'label' => __( 'Rest URL', 'suretriggers' ),
598 'value' => esc_url( get_rest_url() ),
599 'private' => false,
600 ],
601 'suretriggers_version' => [
602 'label' => __( 'OttoKit Version', 'suretriggers' ),
603 'value' => SURE_TRIGGERS_VER,
604 'private' => false,
605 ],
606 ];
607
608 if ( defined( 'SURETRIGGERS_ENCRYPTION_KEY' ) ) {
609 $fields['suretriggers_encryption_key'] = [
610 'label' => __( 'Encryption Key', 'suretriggers' ),
611 'value' => __( 'Defined', 'suretriggers' ),
612 'private' => false,
613 ];
614 }
615
616 if ( defined( 'SURETRIGGERS_ENCRYPTION_SALT' ) ) {
617 $fields['suretriggers_encryption_salt'] = [
618 'label' => __( 'Encryption Salt', 'suretriggers' ),
619 'value' => __( 'Defined', 'suretriggers' ),
620 'private' => false,
621 ];
622 }
623
624 $debug_info['suretriggers'] = [
625 'label' => __( 'OttoKit', 'suretriggers' ),
626 'fields' => $fields,
627 ];
628
629 return $debug_info;
630 }
631
632 }
633
634 RestController::get_instance();
635