Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/suretriggers/functions.php
+++ b/suretriggers/functions.php
@@ -6,6 +6,25 @@
*/
/**
+ * Safely unserialize a value, blocking PHP object instantiation.
+ *
+ * Drop-in replacement for unserialize() / maybe_unserialize() on any data
+ * that originates from user input or external storage. Uses is_serialized()
+ * to detect serialized strings without calling unserialize(), then deserializes
+ * with allowed_classes => false so no PHP objects are ever instantiated.
+ *
+ * @param mixed $data Value to unserialize.
+ * @return mixed Unserialized value, or original value if not serialized.
+ */
+function st_safe_unserialize( $data ) {
+ if ( ! is_string( $data ) || ! is_serialized( $data ) ) {
+ return $data;
+ }
+ // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.unserialize_optionsFound -- allowed_classes requires PHP 7.0+; WP minimum is 7.2+
+ return unserialize( $data, [ 'allowed_classes' => false ] );
+}
+
+/**
* Get or prepare user id.
*
* @return int
--- a/suretriggers/src/Admin/Views/st-admin-outgoing-req-page.php
+++ b/suretriggers/src/Admin/Views/st-admin-outgoing-req-page.php
@@ -13,6 +13,49 @@
use SureTriggersControllersWebhookRequestsController;
global $wpdb;
+// Handle disable-logging toggle.
+if (
+ isset( $_POST['suretriggers_logging_nonce'] ) &&
+ wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['suretriggers_logging_nonce'] ) ), 'suretriggers_logging_nonce_action' ) &&
+ current_user_can( 'manage_options' )
+) {
+ $disable = isset( $_POST['st_disable_logging'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['st_disable_logging'] ) );
+ update_option( 'suretriggers_disable_request_logging', $disable );
+}
+
+$logging_disabled = (bool) get_option( 'suretriggers_disable_request_logging', false );
+$tab_url = add_query_arg(
+ [
+ 'tab' => 'st_outgoing_requests',
+ '_wpnonce' => wp_create_nonce( 'suretriggers_tab_nonce' ),
+ ],
+ admin_url( 'admin.php?page=suretriggers-status' )
+);
+?>
+<div style="margin: 16px 0; padding: 16px 20px; background: #fff; border: 1px solid #c3c4c7; border-left: 4px solid <?php echo esc_attr( $logging_disabled ? '#d63638' : '#00a32a' ); ?>; border-radius: 2px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px;">
+ <div>
+ <strong style="font-size: 14px;"><?php esc_html_e( 'Outgoing Request Logging', 'suretriggers' ); ?></strong>
+ <p style="margin: 4px 0 0; color: #50575e; font-size: 13px;">
+ <?php if ( $logging_disabled ) : ?>
+ <?php esc_html_e( 'Logging is currently disabled. No new requests are being stored. Failed requests cannot be retried automatically.', 'suretriggers' ); ?>
+ <?php else : ?>
+ <?php esc_html_e( 'Logging is enabled. Every outgoing webhook request is stored in the database for retry and history.', 'suretriggers' ); ?>
+ <?php endif; ?>
+ </p>
+ </div>
+ <form method="post" action="<?php echo esc_url( $tab_url ); ?>">
+ <?php wp_nonce_field( 'suretriggers_logging_nonce_action', 'suretriggers_logging_nonce' ); ?>
+ <?php if ( $logging_disabled ) : ?>
+ <input type="hidden" name="st_disable_logging" value="0" />
+ <button type="submit" class="button button-primary"><?php esc_html_e( 'Enable Logging', 'suretriggers' ); ?></button>
+ <?php else : ?>
+ <input type="hidden" name="st_disable_logging" value="1" />
+ <button type="submit" class="button button-secondary" style="border-color: #d63638; color: #d63638;"><?php esc_html_e( 'Disable Logging', 'suretriggers' ); ?></button>
+ <?php endif; ?>
+ </form>
+</div>
+<?php
+
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
--- a/suretriggers/src/Controllers/GlobalSearchController.php
+++ b/suretriggers/src/Controllers/GlobalSearchController.php
@@ -8543,7 +8543,7 @@
$pluggable_data = get_object_vars( $pluggable_data );
unset( $pluggable_data['activation_key'] );
if ( is_string( $pluggable_data['meta'] ) ) {
- $pluggable_data['meta'] = unserialize( $pluggable_data['meta'] );
+ $pluggable_data['meta'] = st_safe_unserialize( $pluggable_data['meta'] );
}
if ( is_array( $pluggable_data['meta'] ) ) {
unset( $pluggable_data['meta']['password'] );
@@ -14725,6 +14725,482 @@
}
/**
+ * Get SCF Custom fields list for posts.
+ *
+ * @param array $data data.
+ *
+ * @return array
+ */
+ public function search_scf_post_field_list( $data ) {
+
+ $post_id = $data['dynamic']['wp_post'];
+ $selected_post_type = $data['dynamic']['wp_post_type'];
+
+ if ( -1 === $post_id ) {
+ $args = [
+ 'numberposts' => 1,
+ 'fields' => 'ids',
+ 'orderby' => 'rand',
+ 'post_type' => $selected_post_type,
+ ];
+ $posts = get_posts( $args );
+ $post_id = $posts[0];
+ }
+
+ $args = [
+ 'post_id' => $post_id,
+ ];
+ if ( ! is_numeric( $post_id ) ) {
+ $args = [
+ 'post_type' => $post_id,
+ ];
+ }
+ $options = [];
+ if ( function_exists( 'acf_get_field_groups' ) ) {
+ $field_groups_collection = acf_get_field_groups( $args );
+ foreach ( $field_groups_collection as $field_group ) {
+ if ( function_exists( 'acf_get_fields' ) ) {
+ $field_groups[] = acf_get_fields( $field_group['key'] );
+ }
+ }
+
+ if ( ! empty( $field_groups ) && is_array( $field_groups ) ) {
+ foreach ( $field_groups as $group_fields_set ) {
+ foreach ( $group_fields_set as $field_group ) {
+ $options[] = [
+ 'value' => $field_group['name'],
+ 'label' => ! empty( $field_group['label'] ) ? $field_group['label'] : $field_group['name'],
+ ];
+ }
+ }
+ }
+ }
+
+ return [
+ 'options' => $options,
+ 'hasMore' => false,
+ ];
+ }
+
+ /**
+ * Get SCF Custom fields list for users.
+ *
+ * @param array $data data.
+ *
+ * @return array
+ */
+ public function search_scf_user_field_list( $data ) {
+
+ if ( ! function_exists( 'acf_get_fields' ) ) {
+ return [];
+ }
+ if ( ! function_exists( 'acf_get_field_groups' ) ) {
+ return [];
+ }
+ $groups_user_form = [];
+ $options = [];
+ if ( function_exists( 'acf_get_field_groups' ) ) {
+ $field_groups = acf_get_field_groups();
+ foreach ( $field_groups as $group ) {
+ if ( ! empty( $group['location'] ) ) {
+ foreach ( $group['location'] as $locations ) {
+ foreach ( $locations as $location ) {
+ if ( 'user_form' === $location['param'] || 'user_role' === $location['param'] || 'current_user' === $location['param'] || 'current_user_role' === $location['param'] ) {
+ $groups_user_form[] = $group;
+ }
+ }
+ }
+ }
+ }
+
+ if ( empty( $groups_user_form ) ) {
+ return [];
+ }
+
+ $key_values = array_map(
+ function ( $item ) {
+ return $item['key'];
+ },
+ $groups_user_form
+ );
+ $unique_keys = array_unique( $key_values );
+ $unique_array = array_intersect_key( $groups_user_form, $unique_keys );
+
+ foreach ( $unique_array as $group ) {
+ if ( function_exists( 'acf_get_fields' ) ) {
+ $group_fields = acf_get_fields( $group['key'] );
+ }
+ if ( ! empty( $group_fields ) ) {
+ foreach ( $group_fields as $field ) {
+ $options[] = [
+ 'value' => $field['name'],
+ 'label' => $field['label'],
+ ];
+ }
+ }
+ }
+ }
+
+ return [
+ 'options' => $options,
+ 'hasMore' => false,
+ ];
+ }
+
+ /**
+ * Get SCF Custom fields list for options page.
+ *
+ * @param array $data data.
+ *
+ * @return array
+ */
+ public function search_scf_options_field_list( $data ) {
+
+ if ( ! function_exists( 'acf_get_fields' ) ) {
+ return [];
+ }
+ if ( ! function_exists( 'acf_get_field_groups' ) ) {
+ return [];
+ }
+ $groups_options_form = [];
+ $options = [];
+ if ( function_exists( 'acf_get_field_groups' ) ) {
+ $field_groups = acf_get_field_groups();
+ foreach ( $field_groups as $group ) {
+ if ( ! empty( $group['location'] ) ) {
+ foreach ( $group['location'] as $locations ) {
+ foreach ( $locations as $location ) {
+ if ( 'options_page' === $location['param'] ) {
+ $groups_options_form[] = $group;
+ }
+ }
+ }
+ }
+ }
+ if ( empty( $groups_options_form ) ) {
+ return [];
+ }
+ $key_values = array_map(
+ function ( $item ) {
+ return $item['key'];
+ },
+ $groups_options_form
+ );
+ $unique_keys = array_unique( $key_values );
+ $unique_array = array_intersect_key( $groups_options_form, $unique_keys );
+ foreach ( $unique_array as $group ) {
+ if ( function_exists( 'acf_get_fields' ) ) {
+ $group_fields = acf_get_fields( $group['key'] );
+ }
+ if ( ! empty( $group_fields ) ) {
+ foreach ( $group_fields as $field ) {
+ $options[] = [
+ 'value' => $field['name'],
+ 'label' => $field['label'],
+ ];
+ }
+ }
+ }
+ }
+
+ return [
+ 'options' => $options,
+ 'hasMore' => false,
+ ];
+ }
+
+ /**
+ * Search Last Updated Field Data for SCF post fields.
+ *
+ * @param array $data data.
+ * @return array
+ */
+ public function search_scf_post_field_data( $data ) {
+ $context = [];
+ $response = [];
+
+ $field = ( isset( $data['filter']['field_id']['value'] ) ? $data['filter']['field_id']['value'] : -1 );
+
+ $post_type = $data['filter']['wp_post_type']['value'];
+ $post = $data['filter']['wp_post']['value'];
+
+ if ( -1 === $post ) {
+ $args = [
+ 'numberposts' => 1,
+ 'fields' => 'ids',
+ 'orderby' => 'rand',
+ 'post_type' => $post_type,
+ ];
+ $posts = get_posts( $args );
+ $post = $posts[0];
+ }
+ if ( -1 === $field ) {
+ $args = [
+ 'post_id' => $post,
+ ];
+ if ( function_exists( 'acf_get_field_groups' ) ) {
+ $field_groups_collection = acf_get_field_groups( $args );
+ }
+ if ( ! empty( $field_groups_collection ) ) {
+ foreach ( $field_groups_collection as $field_group ) {
+ if ( function_exists( 'acf_get_fields' ) ) {
+ $field_groups[] = acf_get_fields( $field_group['key'] );
+ }
+ }
+ }
+ $fields = [];
+ if ( ! empty( $field_groups ) && is_array( $field_groups ) ) {
+ foreach ( $field_groups as $group_fields_set ) {
+ $fields[] = $group_fields_set;
+ }
+ }
+ if ( ! empty( $fields ) ) {
+ $random_key = array_rand( $fields[0] );
+ $field_key = $fields[0][ $random_key ];
+ $field = $field_key['name'];
+ } else {
+ $result = '';
+ }
+ } else {
+ $field = $data['filter']['field_id']['value'];
+ }
+ if ( function_exists( ( 'get_field' ) ) ) {
+ $result = get_field( $field, $post );
+ }
+
+ $response = [];
+ if ( ! empty( $result ) ) {
+ $post_fields = [];
+ if ( function_exists( 'get_fields' ) ) {
+ $post_fields = get_fields( $post );
+ }
+ $response['pluggable_data'] = array_merge( [ $field => $result ], [ 'field_id' => $field ], [ 'post_fields' => $post_fields ], [ 'post' => WordPress::get_post_context( $post ) ], [ 'wp_post' => $post ], [ 'wp_post_type' => get_post_type( $post ) ] );
+ $response['response_type'] = 'live';
+ } else {
+ $response = json_decode( '{"response_type":"sample","pluggable_data":{"custom_description": "custom message", "ID": 1, "post_author": "1", "post_date": "2023-05-31 13:26:24", "post_date_gmt": "2023-05-31 13:26:24", "post_content": "", "post_title": "Test", "post_excerpt": "", "post_status": "publish", "comment_status": "open", "ping_status": "open", "post_password": "", "post_name": "test", "to_ping": "", "pinged": "", "post_modified": "2023-08-17 09:15:56", "post_modified_gmt": "2023-08-17 09:15:56", "post_content_filtered": "", "post_parent": 0, "guid": "https://example.com/?p=1", "menu_order": 0, "post_type": "post", "post_mime_type": "", "comment_count": "2", "filter": "raw"}}', true );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Search Last Updated User Field Data SCF.
+ *
+ * @param array $data data.
+ * @return array
+ */
+ public function search_scf_user_field_data( $data ) {
+ global $wpdb;
+
+ $context = [];
+
+ $field = (int) ( isset( $data['filter']['field_id']['value'] ) ? $data['filter']['field_id']['value'] : -1 );
+
+ if ( -1 === $field ) {
+ $groups_user_form = [];
+ if ( function_exists( 'acf_get_field_groups' ) ) {
+ $field_groups = acf_get_field_groups();
+ }
+ if ( ! empty( $field_groups ) ) {
+ foreach ( $field_groups as $group ) {
+ if ( ! empty( $group['location'] ) ) {
+ foreach ( $group['location'] as $locations ) {
+ foreach ( $locations as $location ) {
+ if ( 'user_form' === $location['param'] || 'user_role' === $location['param'] || 'current_user' === $location['param'] || 'current_user_role' === $location['param'] ) {
+ $groups_user_form[] = $group;
+ }
+ }
+ }
+ }
+ }
+ $field_groups = $groups_user_form;
+ }
+ if ( empty( $field_groups ) ) {
+ $result = '';
+ }
+ $fields = [];
+ if ( ! empty( $field_groups ) ) {
+ foreach ( $field_groups as $group ) {
+ if ( function_exists( 'acf_get_fields' ) ) {
+ $group_fields = acf_get_fields( $group['key'] );
+ }
+ if ( ! empty( $group_fields ) ) {
+ foreach ( $group_fields as $scf_field ) {
+ $fields[] = $scf_field;
+ }
+ }
+ }
+ }
+ if ( ! empty( $fields ) ) {
+ $random_key = array_rand( $fields );
+ $field = $fields[ $random_key ]['name'];
+ } else {
+ $result = '';
+ }
+ } else {
+ $field = $data['filter']['field_id']['value'];
+ }
+ $users = get_users(
+ [
+ 'fields' => 'ID',
+ 'meta_key' => $field,
+ ]
+ );
+
+ if ( ! empty( $users ) ) {
+ $user_random_key = array_rand( $users );
+ $selected_user_id = $users[ $user_random_key ];
+ if ( function_exists( 'get_field' ) ) {
+ $result = get_field( $field, 'user_' . $selected_user_id );
+ }
+ $response = [];
+ if ( ! empty( $result ) ) {
+ $context = [
+ 'field_id' => $field,
+ $field => $result,
+ 'user' => WordPress::get_user_context( $selected_user_id ),
+ ];
+ $response['pluggable_data'] = $context;
+ $response['response_type'] = 'live';
+ } else {
+ $response = json_decode(
+ '{
+ "response_type": "sample",
+ "pluggable_data": {
+ "field_id": "gender",
+ "user": {
+ "wp_user_id": 114,
+ "user_login": "test",
+ "display_name": "test",
+ "user_firstname": "test",
+ "user_lastname": "test",
+ "user_email": "test@test.com",
+ "user_role": [ "subscriber" ]
+ }
+ }
+ }',
+ true
+ );
+ }
+ } else {
+ $response = json_decode(
+ '{
+ "response_type": "sample",
+ "pluggable_data": {
+ "field_id": "gender",
+ "user": {
+ "wp_user_id": 114,
+ "user_login": "test",
+ "display_name": "test",
+ "user_firstname": "test",
+ "user_lastname": "test",
+ "user_email": "test@test.com",
+ "user_role": [ "subscriber" ]
+ }
+ }
+ }',
+ true
+ );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Search Last Updated Options Field Data SCF.
+ *
+ * @param array $data data.
+ * @return array
+ */
+ public function search_scf_options_field_data( $data ) {
+ global $wpdb;
+ $context = [];
+ $response = [];
+ $field = (int) ( isset( $data['filter']['field_id']['value'] ) ? $data['filter']['field_id']['value'] : -1 );
+
+ if ( -1 === $field ) {
+ $groups_options_form = [];
+ if ( function_exists( 'acf_get_field_groups' ) ) {
+ $field_groups = acf_get_field_groups();
+ }
+ if ( ! empty( $field_groups ) ) {
+ foreach ( $field_groups as $group ) {
+ if ( ! empty( $group['location'] ) ) {
+ foreach ( $group['location'] as $locations ) {
+ foreach ( $locations as $location ) {
+ if ( 'options_page' === $location['param'] ) {
+ $groups_options_form[] = $group;
+ }
+ }
+ }
+ }
+ }
+ }
+ if ( empty( $groups_options_form ) ) {
+ $result = '';
+ }
+ $key_values = array_map(
+ function ( $item ) {
+ return $item['key'];
+ },
+ $groups_options_form
+ );
+ $unique_keys = array_unique( $key_values );
+ $unique_array = array_intersect_key( $groups_options_form, $unique_keys );
+ $fields = [];
+ if ( ! empty( $unique_array ) ) {
+ foreach ( $unique_array as $group ) {
+ if ( function_exists( 'acf_get_fields' ) ) {
+ $group_fields = acf_get_fields( $group['key'] );
+ }
+ if ( ! empty( $group_fields ) ) {
+ foreach ( $group_fields as $scf_field ) {
+ $fields[] = $scf_field;
+ }
+ }
+ }
+ }
+ if ( ! empty( $fields ) ) {
+ $random_key = array_rand( $fields );
+ $field = $fields[ $random_key ]['name'];
+ } else {
+ $result = '';
+ }
+ } else {
+ $field = $data['filter']['field_id']['value'];
+ }
+ if ( function_exists( 'get_field' ) ) {
+ $option_value = get_field( $field, 'option' );
+ }
+ if ( ! empty( $option_value ) ) {
+ if ( function_exists( 'acf_get_field' ) ) {
+ $options_fields = acf_get_field( $field );
+ if ( function_exists( 'acf_maybe_get' ) ) {
+ $options_page = acf_maybe_get( $options_fields, 'parent' );
+ }
+ }
+ $context = [
+ 'field_id' => $field,
+ $field => $option_value,
+ ];
+ $response['pluggable_data'] = $context;
+ $response['response_type'] = 'live';
+ } else {
+ $response = json_decode(
+ '{
+ "response_type": "sample",
+ "pluggable_data": {
+ "field_id": "optionpage",
+ "optionpage": "newoption"
+ }
+ }',
+ true
+ );
+ }
+ return $response;
+ }
+
+ /**
* Get WP Fusion Tags list.
*
* @param array $data data.
@@ -19564,7 +20040,7 @@
}
return $data;
} elseif ( is_string( $data ) && self::is_serialized( strval( $data ) ) ) {
- return unserialize( $data );
+ return st_safe_unserialize( $data );
} else {
return $data;
}
@@ -21452,7 +21928,7 @@
$results = $wpdb->get_results( $sql, ARRAY_A );// @phpcs:ignore
if ( ! empty( $results ) ) {
- $context['pluggable_data'] = unserialize( $results[0]['posted_data'] );
+ $context['pluggable_data'] = st_safe_unserialize( $results[0]['posted_data'] );
$context['response_type'] = 'live';
} else {
$context = json_decode( '{"pluggable_data":{"final_price": "1.00","final_price_short": "1","request_timestamp": "04/10/2024 06:56:33","apps": [{"id": 1,"cancelled": "Pending","serviceindex": 0,"service": "Service 1","duration": 60,"price": 1,"date": "2024-04-13","slot": "10:00/11:00","military": 0,"field": "fieldname1","quant": 1,"sid": ""}],"app_service_1": "Service 1","app_status_1": "Pending","app_duration_1": 60,"app_price_1": 1,"app_date_1": "04/13/2024","app_slot_1": "10:00/11:00","app_starttime_1": "10:00 AM","app_endtime_1": "11:00 AM","app_quantity_1": 1,"formid": 1,"formname": "Form 1","referrer": "https://example.com/wp-admin/admin.php?page=cp_apphourbooking&addbk=1&cal=1&r=0.7819147291131667","fieldname1": " - 04/13/2024 10:00 AM - 11:00 AM (Service 1)n","email": "johnd@yopmail.com","username": "admin","itemnumber": 1},"response_type":"sample"}', true );// @phpcs:ignore
@@ -22206,9 +22682,40 @@
case 'stage_changed':
$stage_data = Stage::where( 'board_id', $result['board_id'] )->whereNull( 'archived_at' )->first();
$user_result = $wpdb->get_row( "SELECT * FROM {$wpdb->prefix}users ORDER BY id DESC LIMIT 1", ARRAY_A );
-
+
+ $raw_custom_fields = $wpdb->get_results( //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ $wpdb->prepare(
+ "SELECT bt.id, bt.title, bt.slug, bt.settings AS field_settings, r.settings AS pivot_settings
+ FROM {$wpdb->prefix}fbs_board_terms bt
+ LEFT JOIN {$wpdb->prefix}fbs_relations r
+ ON r.foreign_id = bt.id
+ AND r.object_id = %d
+ AND r.object_type = 'task_custom_field'
+ WHERE bt.board_id = %d AND bt.type = 'custom-field'
+ ORDER BY bt.position ASC",
+ $result['id'],
+ $result['board_id']
+ ),
+ ARRAY_A
+ );
+
+ $custom_fields = [];
+ if ( ! empty( $raw_custom_fields ) ) {
+ foreach ( $raw_custom_fields as $cf ) {
+ $field_settings = is_array( st_safe_unserialize( $cf['field_settings'] ) ) ? st_safe_unserialize( $cf['field_settings'] ) : [];
+ $pivot_settings = is_array( st_safe_unserialize( $cf['pivot_settings'] ) ) ? st_safe_unserialize( $cf['pivot_settings'] ) : [];
+ $custom_fields[] = [
+ 'id' => $cf['id'],
+ 'title' => $cf['title'],
+ 'slug' => $cf['slug'],
+ 'type' => isset( $field_settings['custom_field_type'] ) ? $field_settings['custom_field_type'] : '',
+ 'value' => isset( $pivot_settings['value'] ) ? $pivot_settings['value'] : '',
+ ];
+ }
+ }
+
$context['pluggable_data'] = [
- 'task' => [
+ 'task' => [
'id' => $result['id'],
'slug' => $result['slug'],
'title' => $result['title'],
@@ -22253,7 +22760,8 @@
],
],
],
- 'old_stage_id' => $stage_data->id,
+ 'old_stage_id' => $stage_data->id,
+ 'custom_fields' => $custom_fields,
];
$context['response_type'] = 'live';
break;
@@ -22409,7 +22917,7 @@
$context = json_decode( '{"pluggable_data":{"id":"1001","slug":"sample-task","title":"Sample Task","description":"This is a sample task.","type":"task","board_id":"10","stage_id":"3","position":"1","priority":"medium","created_at":"2024-03-20 12:00:00","created_by":"1","updated_at":"2024-03-20 12:30:00","settings":[],"stage":{"id":"3","slug":"sample-stage","title":"Sample Stage","type":"default","board_id":"10","position":"1","settings":[],"created_at":"2024-03-19 10:00:00","updated_at":"2024-03-20 11:00:00"}},"response_type":"sample"}', true );
break;
case 'stage_changed':
- $context = json_decode( '{"pluggable_data":{"id":"1002","slug":"changed-task","title":"Changed Task","description":"This task changed stages.","type":"task","board_id":"10","stage_id":"4","position":"2","priority":"high","created_at":"2024-04-01 10:00:00","created_by":"1","updated_at":"2024-04-01 12:00:00","settings":[],"stage":{"id":"4","slug":"new-stage","title":"New Stage","type":"default","board_id":"10","position":"2","settings":[],"created_at":"","updated_at":""},"old_stage_id":"3","watchers":[{"ID":71,"photo":"https://www.gravatar.com/avatar/e361de3380a6b977abf350619468ce4f?s=128&d=https%3A%2F%2Fui-avatars.com%2Fapi%2FJohn+Doe/128","user_url":"","user_email":"john@gmail.com","user_login":"john","user_status":0,"display_name":"John Doe","user_nicename":"john","user_registered":"2025-05-28 12:07:48","pivot":{"object_id":1002,"created_at":"2025-06-05T10:11:31+00:00","foreign_id":71,"user_id":71}}]},"response_type":"sample"}', true );
+ $context = json_decode( '{"pluggable_data":{"id":"1002","slug":"changed-task","title":"Changed Task","description":"This task changed stages.","type":"task","board_id":"10","stage_id":"4","position":"2","priority":"high","created_at":"2024-04-01 10:00:00","created_by":"1","updated_at":"2024-04-01 12:00:00","settings":[],"stage":{"id":"4","slug":"new-stage","title":"New Stage","type":"default","board_id":"10","position":"2","settings":[],"created_at":"","updated_at":""},"old_stage_id":"3","watchers":[{"ID":71,"photo":"https://www.gravatar.com/avatar/e361de3380a6b977abf350619468ce4f?s=128&d=https%3A%2F%2Fui-avatars.com%2Fapi%2FJohn+Doe/128","user_url":"","user_email":"john@gmail.com","user_login":"john","user_status":0,"display_name":"John Doe","user_nicename":"john","user_registered":"2025-05-28 12:07:48","pivot":{"object_id":1002,"created_at":"2025-06-05T10:11:31+00:00","foreign_id":71,"user_id":71}}],"custom_fields":[{"id":"1","title":"Priority Score","slug":"priority-score","type":"number","value":"8"},{"id":"2","title":"Client Approved","slug":"client-approved","type":"checkbox","value":true}]},"response_type":"sample"}', true );
break;
case 'task_updated':
$context = json_decode( '{"pluggable_data":{"task":{"id":"1001","parent_id":null,"board_id":"10","title":"Sample Task","slug":"sample-task","type":"task","status":"open","stage_id":"3","source":"web","priority":"medium","description":"<p>Updated task description</p>","position":"1","created_by":"1","created_at":"2024-03-20 12:00:00","updated_at":"2024-03-20 12:30:00","due_at":"2024-03-25 23:45:00","started_at":null,"settings":[],"stage":{"id":"3","slug":"sample-stage","title":"Sample Stage","type":"default","board_id":"10","position":"1","settings":[]}},"column":"description","old_task":{"id":"1001","parent_id":null,"board_id":"10","title":"Sample Task","slug":"sample-task","type":"task","status":"open","stage_id":"3","source":"web","priority":"medium","description":"<p>Original task description</p>","position":"1","created_by":"1","created_at":"2024-03-20 12:00:00","updated_at":"2024-03-20 12:00:00","due_at":"2024-03-25 23:45:00","started_at":null,"settings":[]}},"response_type":"sample"}', true );
@@ -26137,6 +26645,48 @@
}
/**
+ * Search Advanced Forms for ACF forms.
+ *
+ * @param array $data Search Params.
+ *
+ * @return array
+ */
+ public function search_advanced_forms( $data ) {
+ if ( ! function_exists( 'af' ) ) {
+ return [
+ 'options' => [],
+ 'hasMore' => false,
+ ];
+ }
+
+ $forms = get_posts(
+ [
+ 'post_type' => 'af_form',
+ 'post_status' => 'publish',
+ 'numberposts' => -1,
+ 'orderby' => 'title',
+ 'order' => 'ASC',
+ ]
+ );
+
+ $options = [];
+
+ if ( ! empty( $forms ) ) {
+ foreach ( $forms as $form ) {
+ $options[] = [
+ 'label' => $form->post_title,
+ 'value' => $form->post_name,
+ ];
+ }
+ }
+
+ return [
+ 'options' => $options,
+ 'hasMore' => false,
+ ];
+ }
+
+ /**
* Search Code Snippets list.
*
* @param array $data data.
@@ -26179,7 +26729,115 @@
];
}
+ /**
+ * Get CLUEVO LMS learning structure items list.
+ *
+ * Returns all items from the CLUEVO tree table (courses, chapters, modules)
+ * with pagination and optional name/path filtering. Item IDs from this list
+ * are used by the Assign User to Course action.
+ *
+ * @param array $data Search params: page, term.
+ * @return array
+ */
+ public function search_cluevo_lms_items( $data ) {
+ if ( ! function_exists( 'cluevo_get_lms_item_list' ) ) {
+ return [];
+ }
+
+ $page = $data['page'];
+ $limit = Utilities::get_search_page_limit();
+ $offset = $limit * ( $page - 1 );
+ $term = isset( $data['term'] ) ? strtolower( trim( (string) $data['term'] ) ) : '';
+
+ $all_items = cluevo_get_lms_item_list();
+
+ if ( empty( $all_items ) || ! is_array( $all_items ) ) {
+ return [
+ 'options' => [],
+ 'hasMore' => false,
+ ];
+ }
+
+ if ( ! empty( $term ) ) {
+ $all_items = array_values(
+ array_filter(
+ $all_items,
+ function ( $item ) use ( $term ) {
+ return strpos( strtolower( (string) $item->name ), $term ) !== false
+ || strpos( strtolower( (string) $item->path ), $term ) !== false;
+ }
+ )
+ );
+ }
+
+ $total = count( $all_items );
+ $page_items = array_slice( $all_items, $offset, $limit );
+
+ $options = [];
+ foreach ( $page_items as $item ) {
+ $label = ! empty( $item->path ) ? rtrim( (string) $item->path, '/' ) : (string) $item->name;
+ $options[] = [
+ 'label' => $label,
+ 'value' => $item->item_id,
+ ];
+ }
+
+ return [
+ 'options' => $options,
+ 'hasMore' => $total > ( $offset + $limit ),
+ ];
+ }
+
+ /**
+ * Get Groups by itthinx — list groups from the custom groups table.
+ *
+ * @param array $data data.
+ * @return array
+ */
+ public function search_groups_itthinx_groups( $data ) {
+ global $wpdb;
+
+ if ( ! class_exists( 'Groups_Group' ) ) {
+ return [
+ 'options' => [],
+ 'hasMore' => false,
+ ];
+ }
+
+ $page = $data['page'];
+ $limit = Utilities::get_search_page_limit();
+ $offset = $limit * ( $page - 1 );
+ $search_term = isset( $data['search_term'] ) ? sanitize_text_field( $data['search_term'] ) : '';
+
+ $group_table = $wpdb->prefix . 'groups_group'; //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+
+ if ( ! empty( $search_term ) ) {
+ $like = '%' . $wpdb->esc_like( $search_term ) . '%';
+ $results = $wpdb->get_results( $wpdb->prepare( "SELECT group_id, name FROM {$group_table} WHERE name LIKE %s ORDER BY name ASC LIMIT %d OFFSET %d", $like, $limit, $offset ) ); //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ $total = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$group_table} WHERE name LIKE %s", $like ) ); //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ } else {
+ $results = $wpdb->get_results( $wpdb->prepare( "SELECT group_id, name FROM {$group_table} ORDER BY name ASC LIMIT %d OFFSET %d", $limit, $offset ) ); //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ $total = $wpdb->get_var( "SELECT COUNT(*) FROM {$group_table}" ); //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ }
+
+ $options = [];
+ if ( ! empty( $results ) ) {
+ foreach ( $results as $group ) {
+ $options[] = [
+ 'label' => is_string( $group->name ) ? $group->name : '',
+ 'value' => $group->group_id,
+ ];
+ }
+ }
+
+ $total = is_numeric( $total ) ? (int) $total : 0;
+
+ return [
+ 'options' => $options,
+ 'hasMore' => $total > ( $offset + $limit ),
+ ];
+ }
}
--- a/suretriggers/src/Controllers/WebhookRequestsController.php
+++ b/suretriggers/src/Controllers/WebhookRequestsController.php
@@ -144,10 +144,32 @@
* @param string $data Request data.
* @param int $response_code Response Code.
* @param string $error_info Error Info.
- *
+ *
* @return void
*/
public static function suretriggers_log_request( $data, $response_code, $error_info ) {
+ // Allow disabling logging via constant in wp-config.php.
+ if ( defined( 'SURETRIGGERS_DISABLE_LOGGING' ) && SURETRIGGERS_DISABLE_LOGGING ) {
+ return;
+ }
+
+ // Allow disabling logging via the Settings UI option.
+ if ( get_option( 'suretriggers_disable_request_logging', false ) ) {
+ return;
+ }
+
+ /**
+ * Filter to disable webhook request logging programmatically.
+ * Return false to stop logging.
+ *
+ * @param bool $enable Whether to log. Default true.
+ * @param int $response_code HTTP response code.
+ * @param string $error_info Error message if any.
+ */
+ if ( ! apply_filters( 'suretriggers_enable_request_logging', true, $response_code, $error_info ) ) {
+ return;
+ }
+
global $wpdb;
// Store the data in request logs.
$wpdb->insert(
--- a/suretriggers/src/Integrations/CluevoLms/actions/assign-user-to-course.php
+++ b/suretriggers/src/Integrations/CluevoLms/actions/assign-user-to-course.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * AssignUserToCourse.
+ * php version 5.6
+ *
+ * @category AssignUserToCourse
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+
+namespace SureTriggersIntegrationsCluevoLmsActions;
+
+use SureTriggersIntegrationsAutomateAction;
+use SureTriggersIntegrationsCluevoLmsCluevoLms;
+use SureTriggersTraitsSingletonLoader;
+
+/**
+ * AssignUserToCourse
+ *
+ * @category AssignUserToCourse
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+class AssignUserToCourse extends AutomateAction {
+
+ /**
+ * Integration type.
+ *
+ * @var string
+ */
+ public $integration = 'CluevoLms';
+
+ /**
+ * Action name.
+ *
+ * @var string
+ */
+ public $action = 'cluevo_assign_user_to_course';
+
+ use SingletonLoader;
+
+ /**
+ * Register an action.
+ *
+ * @param array $actions actions.
+ * @return array
+ */
+ public function register( $actions ) {
+ $actions[ $this->integration ][ $this->action ] = [
+ 'label' => __( 'Assign User to Course', 'suretriggers' ),
+ 'action' => $this->action,
+ 'function' => [ $this, 'action_listener' ],
+ ];
+
+ return $actions;
+ }
+
+ /**
+ * Action listener.
+ *
+ * @param int $user_id WordPress user ID.
+ * @param int $automation_id Automation ID.
+ * @param array $fields Action field definitions.
+ * @param array $selected_options Selected options.
+ *
+ * @return array
+ */
+ public function _action_listener( $user_id, $automation_id, $fields, $selected_options ) {
+ if ( ! function_exists( 'cluevo_add_user_perms_to_item' ) ||
+ ! function_exists( 'cluevo_is_lms_user' ) ||
+ ! function_exists( 'cluevo_make_lms_user' ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'CLUEVO LMS functions are not available.', 'suretriggers' ),
+ ];
+ }
+
+ if ( empty( $user_id ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'User not found.', 'suretriggers' ),
+ ];
+ }
+
+ $item_id = isset( $selected_options['cluevo_item_id'] ) ? (int) $selected_options['cluevo_item_id'] : 0;
+
+ if ( empty( $item_id ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'No CLUEVO course/item selected.', 'suretriggers' ),
+ ];
+ }
+
+ if ( ! cluevo_is_lms_user( $user_id ) ) {
+ $made = cluevo_make_lms_user( $user_id );
+ if ( false === $made ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'Could not create CLUEVO LMS user record.', 'suretriggers' ),
+ ];
+ }
+ }
+
+ // Level 1 = learner read access. Uses ON DUPLICATE KEY UPDATE so safe to call repeatedly.
+ cluevo_add_user_perms_to_item( $item_id, $user_id, 1 );
+
+ return array_merge(
+ CluevoLms::get_user_context( $user_id ),
+ CluevoLms::get_item_context( $item_id ),
+ [ 'access_granted' => true ]
+ );
+ }
+}
+
+AssignUserToCourse::get_instance();
--- a/suretriggers/src/Integrations/CluevoLms/cluevo-lms.php
+++ b/suretriggers/src/Integrations/CluevoLms/cluevo-lms.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * CluevoLms core integrations file
+ *
+ * @since 1.0.0
+ * @package SureTrigger
+ */
+
+namespace SureTriggersIntegrationsCluevoLms;
+
+use SureTriggersControllersIntegrationsController;
+use SureTriggersIntegrationsIntegrations;
+use SureTriggersTraitsSingletonLoader;
+
+/**
+ * Class CluevoLms
+ *
+ * @package SureTriggersIntegrationsCluevoLms
+ */
+class CluevoLms extends Integrations {
+
+ use SingletonLoader;
+
+ /**
+ * ID
+ *
+ * @var string
+ */
+ protected $id = 'CluevoLms';
+
+ /**
+ * SureTrigger constructor.
+ */
+ public function __construct() {
+ $this->name = __( 'CLUEVO LMS', 'suretriggers' );
+ $this->description = __( 'A WordPress LMS with native SCORM support.', 'suretriggers' );
+ $this->icon_url = SURE_TRIGGERS_URL . 'assets/icons/cluevo-lms.png';
+ parent::__construct();
+ }
+
+ /**
+ * Returns context data for a learning structure item.
+ *
+ * @param int $item_id The CLUEVO tree item ID.
+ * @return array
+ */
+ public static function get_item_context( $item_id ) {
+ if ( ! function_exists( 'cluevo_get_lms_item_list' ) ) {
+ return [ 'cluevo_item_id' => $item_id ];
+ }
+
+ $items = cluevo_get_lms_item_list();
+ if ( ! is_array( $items ) ) {
+ return [ 'cluevo_item_id' => $item_id ];
+ }
+
+ foreach ( $items as $item ) {
+ if ( (int) $item->item_id === (int) $item_id ) {
+ return [
+ 'cluevo_item_id' => $item->item_id,
+ 'cluevo_item_name' => $item->name,
+ 'cluevo_item_path' => $item->path,
+ ];
+ }
+ }
+
+ return [ 'cluevo_item_id' => $item_id ];
+ }
+
+ /**
+ * Returns standard WP user context.
+ *
+ * @param int $user_id WordPress user ID.
+ * @return array
+ */
+ public static function get_user_context( $user_id ) {
+ $user = get_userdata( (int) $user_id );
+ if ( ! $user instanceof WP_User ) {
+ return [];
+ }
+
+ return [
+ 'user_id' => $user->ID,
+ 'user_email' => $user->user_email,
+ 'display_name' => $user->display_name,
+ 'user_login' => $user->user_login,
+ 'first_name' => $user->first_name,
+ 'last_name' => $user->last_name,
+ ];
+ }
+
+ /**
+ * Is Plugin depended plugin is installed or not.
+ *
+ * @return bool
+ */
+ public function is_plugin_installed() {
+ return class_exists( 'Cluevo' );
+ }
+}
+
+IntegrationsController::register( CluevoLms::class );
--- a/suretriggers/src/Integrations/advanced-forms/advanced-forms.php
+++ b/suretriggers/src/Integrations/advanced-forms/advanced-forms.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * AdvancedForms core integrations file
+ *
+ * @since 1.0.0
+ * @package SureTrigger
+ */
+
+namespace SureTriggersIntegrationsAdvancedForms;
+
+use SureTriggersControllersIntegrationsController;
+use SureTriggersIntegrationsIntegrations;
+use SureTriggersTraitsSingletonLoader;
+
+/**
+ * Class SureTrigger
+ *
+ * @package SureTriggersIntegrationsAdvancedForms
+ */
+class AdvancedForms extends Integrations {
+
+ use SingletonLoader;
+
+ /**
+ * ID
+ *
+ * @var string
+ */
+ protected $id = 'AdvancedForms';
+
+ /**
+ * SureTrigger constructor.
+ */
+ public function __construct() {
+ $this->name = __( 'Advanced Forms for ACF', 'suretriggers' );
+ $this->description = __( 'Advanced Forms for ACF lets you create powerful front-end forms using Advanced Custom Fields.', 'suretriggers' );
+ $this->icon_url = SURE_TRIGGERS_URL . 'assets/icons/advanced-forms.svg';
+ parent::__construct();
+ }
+
+ /**
+ * Is Plugin depended plugin is installed or not.
+ *
+ * @return bool
+ */
+ public function is_plugin_installed() {
+ return function_exists( 'af' );
+ }
+
+}
+
+IntegrationsController::register( AdvancedForms::class );
--- a/suretriggers/src/Integrations/advanced-forms/triggers/form-submitted.php
+++ b/suretriggers/src/Integrations/advanced-forms/triggers/form-submitted.php
@@ -0,0 +1,140 @@
+<?php
+/**
+ * FormSubmitted.
+ * php version 5.6
+ *
+ * @category FormSubmitted
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+
+namespace SureTriggersIntegrationsAdvancedFormsTriggers;
+
+use SureTriggersControllersAutomationController;
+use SureTriggersTraitsSingletonLoader;
+
+if ( ! class_exists( 'FormSubmitted' ) ) :
+
+ /**
+ * FormSubmitted
+ *
+ * @category FormSubmitted
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ *
+ * @psalm-suppress UndefinedTrait
+ */
+ class FormSubmitted {
+
+ /**
+ * Integration type.
+ *
+ * @var string
+ */
+ public $integration = 'AdvancedForms';
+
+ /**
+ * Trigger name.
+ *
+ * @var string
+ */
+ public $trigger = 'advanced_forms_form_submitted';
+
+ use SingletonLoader;
+
+ /**
+ * Constructor
+ *
+ * @since 1.0.0
+ */
+ public function __construct() {
+ add_filter( 'sure_trigger_register_trigger', [ $this, 'register' ] );
+ }
+
+ /**
+ * Register action.
+ *
+ * @param array $triggers trigger data.
+ * @return array
+ */
+ public function register( $triggers ) {
+ $triggers[ $this->integration ][ $this->trigger ] = [
+ 'label' => __( 'Form Submitted', 'suretriggers' ),
+ 'action' => $this->trigger,
+ 'common_action' => 'af/form/submission',
+ 'function' => [ $this, 'trigger_listener' ],
+ 'priority' => 10,
+ 'accepted_args' => 3,
+ ];
+
+ return $triggers;
+ }
+
+ /**
+ * Trigger listener
+ *
+ * @param array $form Form settings array including key and title.
+ * @param array $fields Submitted field data indexed by field key.
+ * @param array $args Arguments passed to the form render function.
+ * @since 1.0.0
+ *
+ * @return void
+ */
+ public function trigger_listener( $form, $fields, $args ) {
+ if ( empty( $form ) ) {
+ return;
+ }
+
+ $user_id = get_current_user_id();
+
+ $context = [];
+ $context['form_key'] = isset( $form['key'] ) ? $form['key'] : '';
+ $context['form_title'] = isset( $form['title'] ) ? $form['title'] : '';
+ $context['post_id'] = isset( $args['post_id'] ) ? (int) $args['post_id'] : 0;
+
+ if ( is_array( $fields ) ) {
+ foreach ( $fields as $field ) {
+ if ( ! is_array( $field ) ) {
+ continue;
+ }
+
+ $label = isset( $field['label'] ) ? $field['label'] : '';
+ $key = isset( $field['name'] ) ? $field['name'] : '';
+
+ if ( '' === $key ) {
+ continue;
+ }
+
+ $value = isset( $field['value'] ) ? $field['value'] : '';
+
+ if ( '' !== $label ) {
+ $context[ $label ] = $value;
+ } else {
+ $context[ $key ] = $value;
+ }
+ }
+ }
+ AutomationController::sure_trigger_handle_trigger(
+ [
+ 'trigger' => $this->trigger,
+ 'wp_user_id' => $user_id,
+ 'context' => $context,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Ignore false positive
+ *
+ * @psalm-suppress UndefinedMethod
+ */
+ FormSubmitted::get_instance();
+
+endif;
--- a/suretriggers/src/Integrations/appointment-hour-booking/triggers/booking-status-updated.php
+++ b/suretriggers/src/Integrations/appointment-hour-booking/triggers/booking-status-updated.php
@@ -95,8 +95,13 @@
$id
)
); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
- $posted_data = unserialize( $events[0]->posted_data );
- $context = $posted_data;
+ $posted_data = st_safe_unserialize( $events[0]->posted_data );
+
+ if ( ! is_array( $posted_data ) ) {
+ return;
+ }
+
+ $context = $posted_data;
AutomationController::sure_trigger_handle_trigger(
[
--- a/suretriggers/src/Integrations/buddyboss/Triggers/user-account-activated.php
+++ b/suretriggers/src/Integrations/buddyboss/Triggers/user-account-activated.php
@@ -107,7 +107,7 @@
$context = get_object_vars( $context );
unset( $context['activation_key'] );
if ( is_string( $context['meta'] ) ) {
- $context['meta'] = unserialize( $context['meta'] );
+ $context['meta'] = st_safe_unserialize( $context['meta'] );
}
if ( is_array( $context['meta'] ) ) {
unset( $context['meta']['password'] );
--- a/suretriggers/src/Integrations/fluent-boards/triggers/stage-changed.php
+++ b/suretriggers/src/Integrations/fluent-boards/triggers/stage-changed.php
@@ -91,14 +91,17 @@
if ( empty( $task ) || ! is_object( $task ) || empty( $old_stage_id ) ) {
return;
}
-
-
+
+ if ( method_exists( $task, 'load' ) && class_exists( 'FluentBoardsProAppServicesConstant' ) ) {
+ $task->load( 'customFields' );
+ }
+
$context = [
- 'task' => $task,
- 'old_stage_id' => $old_stage_id,
-
+ 'task' => $task,
+ 'old_stage_id' => $old_stage_id,
+ 'custom_fields' => $this->parse_custom_fields( $task ),
];
-
+
AutomationController::sure_trigger_handle_trigger(
[
'trigger' => $this->trigger,
@@ -106,6 +109,45 @@
]
);
}
+
+ /**
+ * Parse custom fields into a clean key-value format.
+ *
+ * @param object $task Task object with customFields loaded.
+ * @return array
+ */
+ private function parse_custom_fields( $task ) {
+ if ( ! method_exists( $task, 'relationLoaded' ) || ! $task->relationLoaded( 'customFields' ) ) {
+ return [];
+ }
+ if ( ! method_exists( $task, 'getRelation' ) ) {
+ return [];
+ }
+
+ $loaded_fields = $task->getRelation( 'customFields' );
+ if ( empty( $loaded_fields ) ) {
+ return [];
+ }
+
+ $result = [];
+ foreach ( $loaded_fields as $field ) {
+ $raw_pivot = isset( $field->pivot->settings ) ? maybe_unserialize( $field->pivot->settings ) : [];
+ $pivot_settings = is_array( $raw_pivot ) ? $raw_pivot : [];
+ $field_settings = isset( $field->settings ) && is_array( $field->settings )
+ ? $field->settings
+ : [];
+
+ $result[] = [
+ 'id' => $field->id,
+ 'title' => $field->title,
+ 'slug' => $field->slug,
+ 'type' => isset( $field_settings['custom_field_type'] ) ? $field_settings['custom_field_type'] : '',
+ 'value' => isset( $pivot_settings['value'] ) ? $pivot_settings['value'] : '',
+ ];
+ }
+
+ return $result;
+ }
}
/**
--- a/suretriggers/src/Integrations/formidable-forms/triggers/formidable-form-submit.php
+++ b/suretriggers/src/Integrations/formidable-forms/triggers/formidable-form-submit.php
@@ -105,12 +105,7 @@
foreach ( $metas as $meta ) {
$field_id = $meta->field_id;
$field_name = $wpdb->get_var( $wpdb->prepare( 'SELECT name FROM ' . $wpdb->prefix . 'frm_fields WHERE id=%d', $field_id ) );
- $meta_data = unserialize( $meta->meta_value );
- if ( false !== $meta_data ) {
- $data_val = unserialize( $meta->meta_value );
- } else {
- $data_val = $meta->meta_value;
- }
+ $data_val = st_safe_unserialize( $meta->meta_value );
if ( is_array( $data_val ) ) {
foreach ( $data_val as $key => $val ) {
$data[ $key ] = $val;
--- a/suretriggers/src/Integrations/formidable-forms/triggers/user-updates-entry-in-form.php
+++ b/suretriggers/src/Integrations/formidable-forms/triggers/user-updates-entry-in-form.php
@@ -104,12 +104,7 @@
foreach ( $metas as $meta ) {
$field_id = $meta->field_id;
$field_name = $wpdb->get_var( $wpdb->prepare( 'SELECT name FROM ' . $wpdb->prefix . 'frm_fields WHERE id=%d', $field_id ) );
- $meta_data = unserialize( $meta->meta_value );
- if ( false !== $meta_data ) {
- $data_val = unserialize( $meta->meta_value );
- } else {
- $data_val = $meta->meta_value;
- }
+ $data_val = st_safe_unserialize( $meta->meta_value );
if ( is_array( $data_val ) ) {
foreach ( $data_val as $key => $val ) {
$data[ $key ] = $val;
--- a/suretriggers/src/Integrations/groups-itthinx/actions/add-user-to-group.php
+++ b/suretriggers/src/Integrations/groups-itthinx/actions/add-user-to-group.php
@@ -0,0 +1,128 @@
+<?php
+/**
+ * AddUserToGroupItthinx.
+ * php version 5.6
+ *
+ * @category AddUserToGroupItthinx
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+
+namespace SureTriggersIntegrationsGroupsItthinxActions;
+
+use SureTriggersIntegrationsAutomateAction;
+use SureTriggersIntegrationsWordPressWordPress;
+use SureTriggersTraitsSingletonLoader;
+
+/**
+ * AddUserToGroupItthinx
+ *
+ * @category AddUserToGroupItthinx
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+class AddUserToGroupItthinx extends AutomateAction {
+
+ /**
+ * Integration type.
+ *
+ * @var string
+ */
+ public $integration = 'GroupsItthinx';
+
+ /**
+ * Action name.
+ *
+ * @var string
+ */
+ public $action = 'groups_itthinx_add_user_to_group';
+
+ use SingletonLoader;
+
+ /**
+ * Register action.
+ *
+ * @param array $actions actions.
+ * @return array
+ */
+ public function register( $actions ) {
+ $actions[ $this->integration ][ $this->action ] = [
+ 'label' => __( 'Add User to Group', 'suretriggers' ),
+ 'action' => $this->action,
+ 'function' => [ $this, 'action_listener' ],
+ ];
+ return $actions;
+ }
+
+ /**
+ * Action listener.
+ *
+ * @param int $user_id User ID.
+ * @param int $automation_id Automation ID.
+ * @param array $fields Fields.
+ * @param array $selected_options Selected options.
+ * @psalm-suppress UndefinedMethod
+ *
+ * @return array|bool|void
+ */
+ public function _action_listener( $user_id, $automation_id, $fields, $selected_options ) {
+ if ( ! class_exists( 'Groups_User_Group' ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'Groups plugin is not active.', 'suretriggers' ),
+ ];
+ }
+
+ if ( empty( $user_id ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'User not found.', 'suretriggers' ),
+ ];
+ }
+
+ $group_id = isset( $selected_options['groups_itthinx_group_id'] ) ? absint( $selected_options['groups_itthinx_group_id'] ) : 0;
+
+ if ( empty( $group_id ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'Group ID is required.', 'suretriggers' ),
+ ];
+ }
+
+ $result = Groups_User_Group::create(
+ [
+ 'user_id' => $user_id,
+ 'group_id' => $group_id,
+ ]
+ );
+
+ if ( ! $result ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'Failed to add user to group. The user may already be a member.', 'suretriggers' ),
+ ];
+ }
+
+ $context = WordPress::get_user_context( $user_id );
+
+ $context['group_id'] = $group_id;
+
+ if ( class_exists( 'Groups_Group' ) ) {
+ $group = Groups_Group::read( $group_id );
+ if ( $group ) {
+ $context['group_name'] = isset( $group->name ) ? $group->name : '';
+ $context['group_description'] = isset( $group->description ) ? $group->description : '';
+ }
+ }
+
+ return $context;
+ }
+}
+
+AddUserToGroupItthinx::get_instance();
--- a/suretriggers/src/Integrations/groups-itthinx/actions/remove-user-from-group.php
+++ b/suretriggers/src/Integrations/groups-itthinx/actions/remove-user-from-group.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * RemoveUserFromGroupItthinx.
+ * php version 5.6
+ *
+ * @category RemoveUserFromGroupItthinx
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+
+namespace SureTriggersIntegrationsGroupsItthinxActions;
+
+use SureTriggersIntegrationsAutomateAction;
+use SureTriggersIntegrationsWordPressWordPress;
+use SureTriggersTraitsSingletonLoader;
+
+/**
+ * RemoveUserFromGroupItthinx
+ *
+ * @category RemoveUserFromGroupItthinx
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+class RemoveUserFromGroupItthinx extends AutomateAction {
+
+ /**
+ * Integration type.
+ *
+ * @var string
+ */
+ public $integration = 'GroupsItthinx';
+
+ /**
+ * Action name.
+ *
+ * @var string
+ */
+ public $action = 'groups_itthinx_remove_user_from_group';
+
+ use SingletonLoader;
+
+ /**
+ * Register action.
+ *
+ * @param array $actions actions.
+ * @return array
+ */
+ public function register( $actions ) {
+ $actions[ $this->integration ][ $this->action ] = [
+ 'label' => __( 'Remove User from Group', 'suretriggers' ),
+ 'action' => $this->action,
+ 'function' => [ $this, 'action_listener' ],
+ ];
+ return $actions;
+ }
+
+ /**
+ * Action listener.
+ *
+ * @param int $user_id User ID.
+ * @param int $automation_id Automation ID.
+ * @param array $fields Fields.
+ * @param array $selected_options Selected options.
+ * @psalm-suppress UndefinedMethod
+ *
+ * @return array|bool|void
+ */
+ public function _action_listener( $user_id, $automation_id, $fields, $selected_options ) {
+ if ( ! class_exists( 'Groups_User_Group' ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'Groups plugin is not active.', 'suretriggers' ),
+ ];
+ }
+
+ if ( empty( $user_id ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'User not found.', 'suretriggers' ),
+ ];
+ }
+
+ $group_id = isset( $selected_options['groups_itthinx_group_id'] ) ? absint( $selected_options['groups_itthinx_group_id'] ) : 0;
+
+ if ( empty( $group_id ) ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'Group ID is required.', 'suretriggers' ),
+ ];
+ }
+
+ $result = Groups_User_Group::delete( $user_id, $group_id );
+
+ if ( ! $result ) {
+ return [
+ 'status' => 'error',
+ 'message' => __( 'Failed to remove user from group. The user may not be a member.', 'suretriggers' ),
+ ];
+ }
+
+ $context = WordPress::get_user_context( $user_id );
+
+ $context['group_id'] = $group_id;
+
+ if ( class_exists( 'Groups_Group' ) ) {
+ $group = Groups_Group::read( $group_id );
+ if ( $group ) {
+ $context['group_name'] = isset( $group->name ) ? $group->name : '';
+ $context['group_description'] = isset( $group->description ) ? $group->description : '';
+ }
+ }
+
+ return $context;
+ }
+}
+
+RemoveUserFromGroupItthinx::get_instance();
--- a/suretriggers/src/Integrations/groups-itthinx/groups-itthinx.php
+++ b/suretriggers/src/Integrations/groups-itthinx/groups-itthinx.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * GroupsItthinx integration class file
+ *
+ * @package SureTriggers
+ * @since 1.0.0
+ */
+
+namespace SureTriggersIntegrationsGroupsItthinx;
+
+use SureTriggersControllersIntegrationsController;
+use SureTriggersIntegrationsIntegrations;
+use SureTriggersTraitsSingletonLoader;
+
+/**
+ * Class GroupsItthinx
+ *
+ * @package SureTriggersIntegrationsGroupsItthinx
+ */
+class GroupsItthinx extends Integrations {
+
+ use SingletonLoader;
+
+ /**
+ * ID of the integration
+ *
+ * @var string
+ */
+ protected $id = 'GroupsItthinx';
+
+ /**
+ * SureTrigger constructor.
+ */
+ public function __construct() {
+ $this->name = __( 'Groups by itthinx', 'suretriggers' );
+ $this->description = __( 'Group-based user membership management and content access control.', 'suretriggers' );
+ $this->icon_url = SURE_TRIGGERS_URL . 'assets/icons/groups-itthinx.svg';
+
+ parent::__construct();
+ }
+
+ /**
+ * Check plugin is installed.
+ *
+ * @return bool
+ */
+ public function is_plugin_installed() {
+ return class_exists( 'Groups_Group' );
+ }
+}
+
+IntegrationsController::register( GroupsItthinx::class );
--- a/suretriggers/src/Integrations/groups-itthinx/triggers/group-created.php
+++ b/suretriggers/src/Integrations/groups-itthinx/triggers/group-created.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * GroupCreatedItthinx.
+ * php version 5.6
+ *
+ * @category GroupCreatedItthinx
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+
+namespace SureTriggersIntegrationsGroupsItthinxTriggers;
+
+use SureTriggersControllersAutomationController;
+use SureTriggersTraitsSingletonLoader;
+
+if ( ! class_exists( 'GroupCreatedItthinx' ) ) :
+
+ /**
+ * GroupCreatedItthinx
+ *
+ * @category GroupCreatedItthinx
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ *
+ * @psalm-suppress UndefinedTrait
+ */
+ class GroupCreatedItthinx {
+
+ /**
+ * Integration type.
+ *
+ * @var string
+ */
+ public $integration = 'GroupsItthinx';
+
+ /**
+ * Trigger name.
+ *
+ * @var string
+ */
+ public $trigger = 'groups_itthinx_group_created';
+
+ use SingletonLoader;
+
+ /**
+ * Constructor
+ *
+ * @since 1.0.0
+ */
+ public function __construct() {
+ add_filter( 'sure_trigger_register_trigger', [ $this, 'register' ] );
+ }
+
+ /**
+ * Register trigger.
+ *
+ * @param array $triggers triggers.
+ * @return array
+ */
+ public function register( $triggers ) {
+ $triggers[ $this->integration ][ $this->trigger ] = [
+ 'label' => __( 'Group Created', 'suretriggers' ),
+ 'action' => $this->trigger,
+ 'common_action' => 'groups_created_group',
+ 'function' => [ $this, 'trigger_listener' ],
+ 'priority' => 10,
+ 'accepted_args' => 1,
+ ];
+
+ return $triggers;
+ }
+
+ /**
+ * Trigger listener
+ *
+ * @param mixed $group_id Group ID.
+ * @return void
+ */
+ public function trigger_listener( $group_id ) {
+ if ( empty( $group_id ) || ! is_numeric( $group_id ) ) {
+ return;
+ }
+
+ $group_id = (int) $group_id;
+ $context = [
+ 'group_id' => $group_id,
+ ];
+
+ if ( class_exists( 'Groups_Group' ) ) {
+ $group = Groups_Group::read( $group_id );
+ if ( $group ) {
+ $context['group_name'] = isset( $group->name ) ? $group->name : '';
+ $context['group_description'] = isset( $group->description ) ? $group->description : '';
+ $context['group_datetime'] = isset( $group->datetime ) ? $group->datetime : '';
+ }
+ }
+
+ AutomationController::sure_trigger_handle_trigger(
+ [
+ 'trigger' => $this->trigger,
+ 'context' => $context,
+ ]
+ );
+ }
+ }
+
+ /**
+ * Ignore false positive
+ *
+ * @psalm-suppress UndefinedMethod
+ */
+ GroupCreatedItthinx::get_instance();
+
+endif;
--- a/suretriggers/src/Integrations/groups-itthinx/triggers/user-added-to-group.php
+++ b/suretriggers/src/Integrations/groups-itthinx/triggers/user-added-to-group.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * UserAddedToGroup.
+ * php version 5.6
+ *
+ * @category UserAddedToGroup
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ */
+
+namespace SureTriggersIntegrationsGroupsItthinxTriggers;
+
+use SureTriggersControllersAutomationController;
+use SureTriggersIntegrationsWordPressWordPress;
+use SureTriggersTraitsSingletonLoader;
+
+if ( ! class_exists( 'UserAddedToGroupItthinx' ) ) :
+
+ /**
+ * UserAddedToGroupItthinx
+ *
+ * @category UserAddedToGroupItthinx
+ * @package SureTriggers
+ * @author BSF <username@example.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
+ * @link https://www.brainstormforce.com/
+ * @since 1.0.0
+ *
+ * @psalm-suppress UndefinedTrait
+ */
+ class UserAddedToGroupItthinx {
+
+ /**
+ * Integration type.
+ *
+ * @var string
+ */
+ public $integration = 'GroupsItthinx';
+
+ /**
+ * Trigger name.
+ *
+ * @var string
+ */
+ public $trigger = 'groups_itthinx_user_added_to_group';
+
+ use SingletonLoader;
+
+ /**
+ * Constructor
+ *
+ * @since 1.0.0
+ */
+ public function __construct() {
+ add_filter( 'sure_trigger_register_trigger', [ $this, 'register' ] );
+ }
+
+ /**
+ * Register trigger.
+ *
+ * @param array $triggers triggers.
+ * @return array
+ */
+ public function register( $triggers ) {
+ $triggers[ $this->integration ][ $this->trigger ] = [
+ 'label' => __( 'User Added to Group', 'suretriggers' ),
+ 'action' => $this->trigger,
+ 'common_action' => 'groups_created_user_group',
+ 'function' => [ $this, 'trigger_listener' ],
+ 'priority' => 10,
+ 'accepted_args' => 2,
+ ];
+
+ return $triggers;
+ }
+
+ /**
+ * Trigger listener
+ *
+ * @param int $user_id User ID.
+ * @param int $group_id Group ID.
+ * @return void
+ */
+ public function trigger_listener( $user_id, $group_id ) {
+ if ( empty( $user_id ) || empty( $group_id ) ) {
+ return;
+ }
+
+ $context = WordPress::get_user_context( (int) $user_id );
+ $context['group_id'] = (int) $group_id;
+
+ if ( class_exists( 'Groups_Group' ) ) {
+ $group = Groups_Group::read( (int) $group_id );
+ if ( $group ) {
+ $context['group_name'] = isset( $group->name ) ? $group->name : '';
+ $context['group_description'] = isset( $group->description ) ? $group->description : '';
+ }
+ }
+
+ AutomationController::sure_trig