PluginProbe ʕ •ᴥ•ʔ
OttoKit: All-in-One Automation Platform / trunk
OttoKit: All-in-One Automation Platform vtrunk
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 1.0.21 1.0.22 1.0.23 1.0.24 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 1.0.39 1.0.40 1.0.41 1.0.42 1.0.43 1.0.44 1.0.45 1.0.46 1.0.47 1.0.48 1.0.49 1.0.50 1.0.51 1.0.52 1.0.53 1.0.54 1.0.55 1.0.56 1.0.57 1.0.58 1.0.59 1.0.60 1.0.61 1.0.62 1.0.63 1.0.64 1.0.65 1.0.66 1.0.67 1.0.68 1.0.69 1.0.7 1.0.70 1.0.71 1.0.72 1.0.73 1.0.74 1.0.75 1.0.76 1.0.77 1.0.78 1.0.79 1.0.8 1.0.80 1.0.81 1.0.82 1.0.83 1.0.84 1.0.85 1.0.86 1.0.87 1.0.88 1.0.89 1.0.9 1.0.90 1.1.0 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 1.1.20 1.1.21 1.1.22 1.1.23 1.1.24 1.1.25 1.1.26 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8
suretriggers / src / Controllers / WebhookRequestsController.php
suretriggers / src / Controllers Last commit date
AuthController.php 3 months ago AutomationController.php 11 months ago EventController.php 11 months ago GlobalSearchController.php 2 weeks ago IntegrationsController.php 11 months ago OptionController.php 3 years ago RestController.php 4 weeks ago RoutesController.php 1 year ago SettingsController.php 10 months ago WebhookRequestsController.php 1 month ago
WebhookRequestsController.php
377 lines
1 <?php
2 /**
3 * WebhookRequestsController.
4 * php version 5.6
5 *
6 * @category WebhookRequestsController
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 SureTriggers\Traits\SingletonLoader;
17 use SureTriggers\Models\SaasApiToken;
18 use SureTriggers\Controllers\RestController;
19
20 /**
21 * WebhookRequestsController- Store Webhook requests and retry for failed.
22 *
23 * @category WebhookRequestsController
24 * @package SureTriggers
25 * @author BSF <username@example.com>
26 * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
27 * @link https://www.brainstormforce.com/
28 * @since 1.0.0
29 *
30 * @psalm-suppress UndefinedTrait
31 */
32 class WebhookRequestsController {
33
34 use SingletonLoader;
35
36 /**
37 * Webhook Requests Table name.
38 *
39 * @var string
40 */
41 protected static $name = 'suretriggers_webhook_requests';
42
43 /**
44 * Initialise data.
45 */
46 public function __construct() {
47 add_action( 'suretriggers_retry_failed_requests', [ $this, 'suretriggers_retry_failed_trigger_requests' ] );
48 add_action( 'suretriggers_webhook_requests_cleanup_logs', [ $this, 'suretriggers_cleanup_requests_logs' ] );
49 add_action( 'suretriggers_verify_api_connection', [ $this, 'suretriggers_verify_api_wp_connection' ] );
50 add_filter( 'cron_schedules', [ $this, 'suretriggers_custom_cron_schedule' ] );
51 }
52
53 /**
54 * Get the table name.
55 *
56 * @return string
57 */
58 public static function get_table_name() {
59 global $wpdb;
60 return $wpdb->prefix . self::$name;
61 }
62
63 /**
64 * Adds a custom cron schedule for every 30 minutes.
65 *
66 * @param array $schedules An array of non-default cron schedules.
67 * @return array Filtered array of non-default cron schedules.
68 */
69 public static function suretriggers_custom_cron_schedule( $schedules ) {
70 $schedules['suretriggers_retry_cron_schedule'] = [
71 'interval' => 30 * MINUTE_IN_SECONDS,
72 'display' => __( 'Every 30 minutes', 'suretriggers' ),
73 ];
74 $schedules['suretriggers_verify_connection_cron_schedule'] = [
75 'interval' => 6 * HOUR_IN_SECONDS,
76 'display' => __( 'Every 6 hours', 'suretriggers' ),
77 ];
78 return $schedules;
79 }
80
81 /**
82 * Custom table for storing of webhook requests logs.
83 *
84 * @return void
85 */
86 public static function suretriggers_webhook_request_log_table() {
87 global $wpdb;
88 $table_name = self::get_table_name();
89 $charset_collate = $wpdb->get_charset_collate();
90
91 $sql = "CREATE TABLE $table_name (
92 id mediumint(9) NOT NULL AUTO_INCREMENT,
93 request_method varchar(255) NULL,
94 request_url varchar(255) NOT NULL,
95 request_data longtext NOT NULL,
96 response_code int(3) NOT NULL,
97 status varchar(20) NOT NULL,
98 error_info varchar(255) NOT NULL,
99 retry_attempts int(3) DEFAULT 0,
100 processed_at datetime NULL,
101 created_at datetime,
102 updated_at datetime ON UPDATE CURRENT_TIMESTAMP,
103 PRIMARY KEY (id)
104 ) $charset_collate;";
105
106 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
107 dbDelta( $sql );
108 }
109
110 /**
111 * Setup cron to retry failed webhook requests and cleanup logs for Triggers.
112 *
113 * @return void
114 */
115 public static function suretriggers_setup_custom_cron() {
116 // Retry failed requests.
117 if ( ! wp_next_scheduled( 'suretriggers_retry_failed_requests' ) ) {
118 wp_schedule_event( time(), 'suretriggers_retry_cron_schedule', 'suretriggers_retry_failed_requests' );
119 }
120
121 // Clean up log requests that are older than 15 days.
122 if ( ! wp_next_scheduled( 'suretriggers_webhook_requests_cleanup_logs' ) ) {
123 wp_schedule_event( time(), 'daily', 'suretriggers_webhook_requests_cleanup_logs' );
124 }
125
126 // Verify the API connection every 6 hours to keep the connection alive.
127 if ( ! wp_next_scheduled( 'suretriggers_verify_api_connection' ) ) {
128 wp_schedule_event( time(), 'suretriggers_verify_connection_cron_schedule', 'suretriggers_verify_api_connection' );
129 } else {
130 // Reschedule the event that was twice daily before to every 6 hours.
131 $get_scheduled_event = wp_get_scheduled_event( 'suretriggers_verify_api_connection' );
132 if ( $get_scheduled_event ) {
133 if ( 21600 !== $get_scheduled_event->interval ) {
134 wp_clear_scheduled_hook( 'suretriggers_verify_api_connection' );
135 wp_reschedule_event( time(), 'suretriggers_verify_connection_cron_schedule', 'suretriggers_verify_api_connection' );
136 }
137 }
138 }
139 }
140
141 /**
142 * Log Request handler.
143 *
144 * @param string $data Request data.
145 * @param int $response_code Response Code.
146 * @param string $error_info Error Info.
147 *
148 * @return void
149 */
150 public static function suretriggers_log_request( $data, $response_code, $error_info ) {
151 // Allow disabling logging via constant in wp-config.php.
152 if ( defined( 'SURETRIGGERS_DISABLE_LOGGING' ) && SURETRIGGERS_DISABLE_LOGGING ) {
153 return;
154 }
155
156 // Allow disabling logging via the Settings UI option.
157 if ( get_option( 'suretriggers_disable_request_logging', false ) ) {
158 return;
159 }
160
161 /**
162 * Filter to disable webhook request logging programmatically.
163 * Return false to stop logging.
164 *
165 * @param bool $enable Whether to log. Default true.
166 * @param int $response_code HTTP response code.
167 * @param string $error_info Error message if any.
168 */
169 if ( ! apply_filters( 'suretriggers_enable_request_logging', true, $response_code, $error_info ) ) {
170 return;
171 }
172
173 global $wpdb;
174 // Store the data in request logs.
175 $wpdb->insert(
176 self::get_table_name(),
177 [
178 'request_method' => 'POST',
179 'request_url' => SURE_TRIGGERS_WEBHOOK_SERVER_URL . '/wordpress/webhook',
180 'request_data' => $data,
181 'response_code' => $response_code,
182 'status' => ( 200 === $response_code ) ? 'success' : 'failed',
183 'error_info' => $error_info,
184 'retry_attempts' => 0,
185 'processed_at' => null,
186 'created_at' => current_time( 'mysql' ),
187 'updated_at' => current_time( 'mysql' ),
188 ]
189 );
190 }
191
192 /**
193 * Update Failed Webhook Request handler via cron.
194 *
195 * @return void
196 */
197 public static function suretriggers_retry_failed_trigger_requests() {
198 global $wpdb;
199 $table_name = self::get_table_name();
200
201 // Select all failed requests that haven't exceeded retry attempts.
202 $failed_requests = $wpdb->get_results(
203 $wpdb->prepare(
204 "SELECT * FROM {$table_name} WHERE status = %s AND retry_attempts < %d", //phpcs:ignore
205 'failed',
206 5
207 ),
208 ARRAY_A
209 );
210
211 foreach ( $failed_requests as $request ) {
212 $data = json_decode( $request['request_data'], true );
213 if ( is_array( $data ) ) {
214 $data['headers']['Authorization'] = 'Bearer ' . SaasApiToken::get();
215 $response = wp_remote_post( $request['request_url'], $data );
216 $response_code = wp_remote_retrieve_response_code( $response );
217 $error_info = wp_remote_retrieve_body( $response );
218 if ( 405 === $response_code ) {
219 $error_info = wp_remote_retrieve_response_message( $response );
220 }
221 if ( 0 === $response_code ) {
222 $error_info = __( 'Service not available', 'suretriggers' );
223 }
224 // Update the request if failed with the new response.
225 $wpdb->update(
226 $table_name,
227 [
228 'request_method' => $request['request_method'],
229 'request_url' => $request['request_url'],
230 'request_data' => $request['request_data'],
231 'response_code' => $response_code,
232 'status' => ( 200 === $response_code ) ? 'success' : 'failed',
233 'error_info' => $error_info,
234 'retry_attempts' => $request['retry_attempts'] + 1,
235 'processed_at' => current_time( 'mysql' ),
236 'updated_at' => current_time( 'mysql' ),
237 ],
238 [ 'id' => $request['id'] ]
239 );
240 }
241 }
242 }
243
244 /**
245 * Update Failed Webhook Request handler via Retry button.
246 *
247 * @param int $id ID.
248 *
249 * @return bool
250 */
251 public static function suretriggers_retry_trigger_request( $id ) {
252 global $wpdb;
253 $table_name = self::get_table_name();
254 $failed_requests = $wpdb->get_row(
255 $wpdb->prepare(
256 "SELECT * FROM {$table_name} WHERE id = %d", //phpcs:ignore
257 $id
258 ),
259 ARRAY_A
260 );
261
262 $data = json_decode( $failed_requests['request_data'], true );
263 if ( is_array( $data ) ) {
264 $data['headers']['Authorization'] = 'Bearer ' . SaasApiToken::get();
265
266 // Generate a new UUID so the SaaS server doesn't deduplicate this as a replay.
267 if ( isset( $data['body']['wordpress_webhook_uuid'] ) ) {
268 $new_uuid = str_replace( '-', '', wp_generate_uuid4() );
269 $site_url = esc_url_raw( str_replace( '/wp-json/', '', get_site_url() ) );
270 $site_url = preg_replace( '/^https?:\/\//', '', $site_url );
271 $encoded_site_url = urlencode( (string) $site_url );
272 $data['body']['wordpress_webhook_uuid'] = $new_uuid . '_' . $encoded_site_url;
273 }
274
275 $response = wp_remote_post( $failed_requests['request_url'], $data );
276 $response_code = wp_remote_retrieve_response_code( $response );
277 $error_info = wp_remote_retrieve_body( $response );
278 if ( 405 === wp_remote_retrieve_response_code( $response ) ) {
279 $error_info = wp_remote_retrieve_response_message( $response );
280 }
281 if ( 0 === wp_remote_retrieve_response_code( $response ) ) {
282 $error_info = __( 'Service not available', 'suretriggers' );
283 }
284 $wpdb->update(
285 $table_name,
286 [
287 'request_method' => $failed_requests['request_method'],
288 'request_url' => $failed_requests['request_url'],
289 'request_data' => (string) wp_json_encode( $data ),
290 'response_code' => $response_code,
291 'status' => ( 200 === $response_code ) ? 'success' : 'failed',
292 'error_info' => $error_info,
293 'retry_attempts' => $failed_requests['retry_attempts'] + 1,
294 'processed_at' => current_time( 'mysql' ),
295 'updated_at' => current_time( 'mysql' ),
296 ],
297 [ 'id' => $id ]
298 );
299 return true;
300 }
301 return false;
302 }
303
304 /**
305 * Delete failed webhook requests log that are 60 days older.
306 * Delete success webhook requests log that are 30 days older.
307 *
308 * @return void
309 */
310 public static function suretriggers_cleanup_requests_logs() {
311 global $wpdb;
312 $table_name = self::get_table_name();
313 $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE status = %s AND created_at < NOW() - INTERVAL %d DAY", 'failed', 60 ) ); //phpcs:ignore
314 $wpdb->query( $wpdb->prepare( "DELETE FROM {$table_name} WHERE status = %s AND created_at < NOW() - INTERVAL %d DAY", 'success', 30 ) ); //phpcs:ignore
315 }
316
317 /**
318 * Verify WordPress connection with SureTriggers API to check the connection status twice daily.
319 *
320 * @return void
321 */
322 public static function suretriggers_verify_api_wp_connection() {
323 $response = RestController::suretriggers_verify_wp_connection();
324 // Check if the response is valid.
325 if ( is_wp_error( $response ) ) {
326 update_option( 'suretriggers_verify_connection', 'suretriggers_connection_wp_error' );
327 } else {
328 $status_code = wp_remote_retrieve_response_code( $response );
329 if ( 200 !== $status_code ) {
330 update_option( 'suretriggers_verify_connection', 'suretriggers_connection_error' );
331 } else {
332 update_option( 'suretriggers_verify_connection', 'suretriggers_connection_successful' );
333 }
334 }
335 }
336
337 /**
338 * Unschedule the event on plugin deletion.
339 *
340 * @return void
341 */
342 public static function suretriggers_remove_table_retry_cron() {
343 // Clear custom scheduled cron created.
344 wp_clear_scheduled_hook( 'suretriggers_retry_cron_schedule' );
345
346 // Remove retry cron schedule on plugin deletion.
347 $retry_failed_requests = wp_next_scheduled( 'suretriggers_retry_failed_requests' );
348 if ( $retry_failed_requests ) {
349 wp_unschedule_event( $retry_failed_requests, 'suretriggers_retry_failed_requests' );
350 }
351
352 // Remove clean up cron schedule.
353 $webhook_requests_cleanup = wp_next_scheduled( 'suretriggers_webhook_requests_cleanup_logs' );
354 if ( $webhook_requests_cleanup ) {
355 wp_unschedule_event( $webhook_requests_cleanup, 'suretriggers_webhook_requests_cleanup_logs' );
356 }
357
358 // Remove connection verification cron schedule.
359 $webhook_requests_cleanup = wp_next_scheduled( 'suretriggers_verify_api_connection' );
360 if ( $webhook_requests_cleanup ) {
361 wp_unschedule_event( $webhook_requests_cleanup, 'suretriggers_verify_api_connection' );
362 }
363
364 // Delete table on plugin delete.
365 global $wpdb;
366 $table_name = self::get_table_name();
367 // Drop the custom table.
368 $table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table_name ) );
369 if ( $table_exists ) {
370 $wpdb->query( "DROP TABLE IF EXISTS $table_name" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.SchemaChange
371 }
372 }
373
374 }
375
376 WebhookRequestsController::get_instance();
377