forked from LiveCarta/LiveCartaWP
Changed source root directory
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
<?php
|
||||
/**
|
||||
* Contact lookup REST endpoint.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Cmatic_Contact_Lookup {
|
||||
|
||||
protected static $namespace = 'chimpmatic-lite/v1';
|
||||
protected static $initialized = false;
|
||||
|
||||
public static function cmatic_is_pro_active() {
|
||||
return apply_filters( 'cmatic_contact_lookup_is_pro', false );
|
||||
}
|
||||
|
||||
protected static function cmatic_fltr_value( $value ) {
|
||||
if ( empty( $value ) || $value === null ) {
|
||||
return null;
|
||||
}
|
||||
return substr( md5( wp_json_encode( $value ) . wp_salt() ), 0, 7 );
|
||||
}
|
||||
|
||||
protected static function cmatic_fltr_pro_fields( $result ) {
|
||||
if ( ! $result['found'] ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ( Cmatic_Lite_Get_Fields::cmatic_lite_fields() as $field ) {
|
||||
if ( isset( $result[ $field ] ) ) {
|
||||
$result[ $field ] = self::cmatic_fltr_value( $result[ $field ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $result['merge_fields'] ) && is_array( $result['merge_fields'] ) ) {
|
||||
$field_index = 0;
|
||||
foreach ( $result['merge_fields'] as $tag => $value ) {
|
||||
if ( $field_index >= Cmatic_Lite_Get_Fields::cmatic_lite_merge_fields() ) {
|
||||
$result['merge_fields'][ $tag ] = self::cmatic_fltr_value( $value );
|
||||
}
|
||||
++$field_index;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( Cmatic_Lite_Get_Fields::cmatic_lite_sections() as $section ) {
|
||||
if ( isset( $result[ $section ] ) && ! empty( $result[ $section ] ) ) {
|
||||
if ( is_array( $result[ $section ] ) ) {
|
||||
$result[ $section ] = self::cmatic_fltr_array( $result[ $section ] );
|
||||
} else {
|
||||
$result[ $section ] = self::cmatic_fltr_value( $result[ $section ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected static function cmatic_fltr_array( $arr ) {
|
||||
$fltred = array();
|
||||
foreach ( $arr as $key => $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$fltred[ self::cmatic_fltr_value( $key ) ] = self::cmatic_fltr_array( $value );
|
||||
} else {
|
||||
$fltred[] = self::cmatic_fltr_value( $value );
|
||||
}
|
||||
}
|
||||
return $fltred;
|
||||
}
|
||||
|
||||
public static function init() {
|
||||
if ( self::$initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'rest_api_init', array( static::class, 'cmatic_register_routes' ) );
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
public static function cmatic_register_routes() {
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/contact/lookup',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'cmatic_lookup_contact' ),
|
||||
'permission_callback' => array( static::class, 'cmatic_check_permission' ),
|
||||
'args' => array(
|
||||
'email' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_email',
|
||||
'validate_callback' => function( $param ) {
|
||||
return is_email( $param );
|
||||
},
|
||||
),
|
||||
'form_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function cmatic_check_permission() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
__( 'You do not have permission to access this endpoint.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function cmatic_lookup_contact( $request ) {
|
||||
$email = strtolower( $request->get_param( 'email' ) );
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
$api_key = $cf7_mch['api'] ?? '';
|
||||
|
||||
if ( empty( $api_key ) ) {
|
||||
return new WP_Error(
|
||||
'no_api_key',
|
||||
__( 'No API key configured. Please connect to Mailchimp first.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! preg_match( '/^([a-f0-9]{32})-([a-z]{2,3}\d+)$/', $api_key, $matches ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_api_key',
|
||||
__( 'Invalid API key format.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$key = $matches[1];
|
||||
$dc = $matches[2];
|
||||
|
||||
$lists_url = "https://{$dc}.api.mailchimp.com/3.0/lists?count=50&fields=lists.id,lists.name";
|
||||
$lists_resp = Cmatic_Lite_Api_Service::get( $key, $lists_url );
|
||||
|
||||
if ( is_wp_error( $lists_resp[2] ) || 200 !== wp_remote_retrieve_response_code( $lists_resp[2] ) ) {
|
||||
return new WP_Error(
|
||||
'api_error',
|
||||
__( 'Failed to retrieve audiences from Mailchimp.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
$lists_data = $lists_resp[0];
|
||||
$lists = $lists_data['lists'] ?? array();
|
||||
|
||||
if ( empty( $lists ) ) {
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'email' => $email,
|
||||
'found' => false,
|
||||
'message' => __( 'No audiences found in your Mailchimp account.', 'chimpmatic-lite' ),
|
||||
'results' => array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$subscriber_hash = md5( $email );
|
||||
$results = array();
|
||||
$found_count = 0;
|
||||
|
||||
foreach ( $lists as $list ) {
|
||||
$list_id = $list['id'];
|
||||
$list_name = $list['name'];
|
||||
|
||||
$member_url = "https://{$dc}.api.mailchimp.com/3.0/lists/{$list_id}/members/{$subscriber_hash}";
|
||||
$member_resp = Cmatic_Lite_Api_Service::get( $key, $member_url );
|
||||
|
||||
$status_code = wp_remote_retrieve_response_code( $member_resp[2] );
|
||||
|
||||
if ( 200 === $status_code ) {
|
||||
$member_data = $member_resp[0];
|
||||
++$found_count;
|
||||
|
||||
$interests = array();
|
||||
$interests_url = "https://{$dc}.api.mailchimp.com/3.0/lists/{$list_id}/interest-categories?count=100";
|
||||
$interests_resp = Cmatic_Lite_Api_Service::get( $key, $interests_url );
|
||||
$interests_status = wp_remote_retrieve_response_code( $interests_resp[2] );
|
||||
|
||||
if ( 200 === $interests_status && ! empty( $interests_resp[0]['categories'] ) ) {
|
||||
foreach ( $interests_resp[0]['categories'] as $category ) {
|
||||
$cat_id = $category['id'];
|
||||
$cat_title = $category['title'];
|
||||
$cat_interest = array();
|
||||
|
||||
$cat_interests_url = "https://{$dc}.api.mailchimp.com/3.0/lists/{$list_id}/interest-categories/{$cat_id}/interests?count=100";
|
||||
$cat_interests_resp = Cmatic_Lite_Api_Service::get( $key, $cat_interests_url );
|
||||
|
||||
if ( 200 === wp_remote_retrieve_response_code( $cat_interests_resp[2] ) && ! empty( $cat_interests_resp[0]['interests'] ) ) {
|
||||
$member_interests = $member_data['interests'] ?? array();
|
||||
foreach ( $cat_interests_resp[0]['interests'] as $interest ) {
|
||||
$interest_id = $interest['id'];
|
||||
$interest_name = $interest['name'];
|
||||
$is_subscribed = ! empty( $member_interests[ $interest_id ] );
|
||||
if ( $is_subscribed ) {
|
||||
$cat_interest[] = $interest_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $cat_interest ) ) {
|
||||
$interests[ $cat_title ] = $cat_interest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$results[] = array(
|
||||
'list_id' => $list_id,
|
||||
'list_name' => $list_name,
|
||||
'found' => true,
|
||||
'status' => $member_data['status'] ?? 'unknown',
|
||||
'email' => $member_data['email_address'] ?? $email,
|
||||
'merge_fields' => $member_data['merge_fields'] ?? array(),
|
||||
'tags' => array_column( $member_data['tags'] ?? array(), 'name' ),
|
||||
'interests' => $interests,
|
||||
'marketing_permissions' => $member_data['marketing_permissions'] ?? array(),
|
||||
'source' => $member_data['source'] ?? null,
|
||||
'ip_signup' => $member_data['ip_signup'] ?? null,
|
||||
'timestamp_signup' => $member_data['timestamp_signup'] ?? null,
|
||||
'subscribed' => $member_data['timestamp_opt'] ?? null,
|
||||
'last_changed' => $member_data['last_changed'] ?? null,
|
||||
'unsubscribe_reason' => $member_data['unsubscribe_reason'] ?? null,
|
||||
'language' => $member_data['language'] ?? null,
|
||||
'email_type' => $member_data['email_type'] ?? null,
|
||||
'vip' => $member_data['vip'] ?? false,
|
||||
'email_client' => $member_data['email_client'] ?? null,
|
||||
'location' => $member_data['location'] ?? null,
|
||||
'member_rating' => $member_data['member_rating'] ?? null,
|
||||
'consents_to_one_to_one_messaging' => $member_data['consents_to_one_to_one_messaging'] ?? null,
|
||||
);
|
||||
} elseif ( 404 === $status_code ) {
|
||||
$results[] = array(
|
||||
'list_id' => $list_id,
|
||||
'list_name' => $list_name,
|
||||
'found' => false,
|
||||
'status' => 'not_subscribed',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$is_pro = self::cmatic_is_pro_active();
|
||||
|
||||
if ( ! $is_pro ) {
|
||||
$results = array_map( array( static::class, 'cmatic_fltr_pro_fields' ), $results );
|
||||
}
|
||||
|
||||
Cmatic_Options_Repository::set_option( 'features.contact_lookup_used', true );
|
||||
do_action( 'cmatic_subscription_success', $form_id, $email );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'email' => $email,
|
||||
'found' => $found_count > 0,
|
||||
'found_count' => $found_count,
|
||||
'total_lists' => count( $lists ),
|
||||
'is_pro' => $is_pro,
|
||||
'message' => $found_count > 0
|
||||
? sprintf(
|
||||
/* translators: %1$d: found count, %2$d: total lists */
|
||||
__( 'Contact found in %1$d of %2$d audiences.', 'chimpmatic-lite' ),
|
||||
$found_count,
|
||||
count( $lists )
|
||||
)
|
||||
: __( 'Contact not found in any audience.', 'chimpmatic-lite' ),
|
||||
'results' => $results,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function cmatic_render( $args = array() ) {
|
||||
$defaults = array(
|
||||
'form_id' => 0,
|
||||
);
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
?>
|
||||
<div id="cmatic-contact-lookup" class="postbox mce-move mce-hidden">
|
||||
<div class="inside" style="padding: 15px;">
|
||||
<h3 style="margin: 0 0 10px;"><?php esc_html_e( 'Contact Lookup', 'chimpmatic-lite' ); ?></h3>
|
||||
<p><?php esc_html_e( 'Debug tool: Check if a subscriber exists in your Mailchimp account and view their status across all your audiences.', 'chimpmatic-lite' ); ?></p>
|
||||
|
||||
<div style="margin: 15px 0;">
|
||||
<input type="email"
|
||||
id="cmatic-lookup-email"
|
||||
placeholder="<?php esc_attr_e( 'Enter email address...', 'chimpmatic-lite' ); ?>"
|
||||
data-form-id="<?php echo esc_attr( $args['form_id'] ); ?>"
|
||||
style="width: 100%; margin-bottom: 8px;">
|
||||
<button type="button" id="cmatic-lookup-btn" class="button button-primary" style="width: 100%;">
|
||||
<?php esc_html_e( 'Lookup', 'chimpmatic-lite' ); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="cmatic-lookup-results" class="cmatic-hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
/**
|
||||
* Debug log viewer component.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Cmatic_Log_Viewer {
|
||||
|
||||
protected static $namespace = 'chimpmatic-lite/v1';
|
||||
protected static $log_prefix = '[ChimpMatic Lite]';
|
||||
protected static $text_domain = 'chimpmatic-lite';
|
||||
protected static $max_lines = 500;
|
||||
protected static $initialized = false;
|
||||
|
||||
public static function init( $namespace = null, $log_prefix = null, $text_domain = null ) {
|
||||
if ( self::$initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $namespace ) {
|
||||
self::$namespace = $namespace . '/v1';
|
||||
}
|
||||
if ( $log_prefix ) {
|
||||
self::$log_prefix = $log_prefix;
|
||||
}
|
||||
if ( $text_domain ) {
|
||||
self::$text_domain = $text_domain;
|
||||
}
|
||||
|
||||
add_action( 'rest_api_init', array( static::class, 'register_routes' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( static::class, 'enqueue_assets' ) );
|
||||
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
public static function register_routes() {
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/logs',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( static::class, 'get_logs' ),
|
||||
'permission_callback' => array( static::class, 'check_permission' ),
|
||||
'args' => array(
|
||||
'filter' => array(
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'default' => '1',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => function ( $param ) {
|
||||
return in_array( $param, array( '0', '1' ), true );
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/logs/clear',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'clear_logs' ),
|
||||
'permission_callback' => array( static::class, 'check_permission' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/logs/browser',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'log_browser_console' ),
|
||||
'permission_callback' => array( static::class, 'check_permission' ),
|
||||
'args' => array(
|
||||
'level' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => function ( $param ) {
|
||||
return in_array( $param, array( 'log', 'info', 'warn', 'error', 'debug' ), true );
|
||||
},
|
||||
),
|
||||
'message' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
'data' => array(
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_textarea_field',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function check_permission() {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
public static function get_log_prefix() {
|
||||
return static::$log_prefix;
|
||||
}
|
||||
|
||||
public static function get_log_path() {
|
||||
if ( defined( 'WP_DEBUG_LOG' ) && is_string( WP_DEBUG_LOG ) ) {
|
||||
return WP_DEBUG_LOG;
|
||||
}
|
||||
return WP_CONTENT_DIR . '/debug.log';
|
||||
}
|
||||
|
||||
public static function get_logs( $request ) {
|
||||
$log_path = static::get_log_path();
|
||||
$prefix = static::get_log_prefix();
|
||||
$apply_filter = '1' === $request->get_param( 'filter' );
|
||||
|
||||
if ( ! file_exists( $log_path ) ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => false,
|
||||
'message' => __( 'Debug log file not found. Ensure WP_DEBUG_LOG is enabled.', self::$text_domain ),
|
||||
'logs' => '',
|
||||
'filtered' => $apply_filter,
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
$lines = static::read_last_lines( $log_path, self::$max_lines );
|
||||
|
||||
if ( $apply_filter ) {
|
||||
$output = array();
|
||||
foreach ( $lines as $line ) {
|
||||
if ( strpos( $line, $prefix ) !== false ) {
|
||||
$output[] = $line;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$output = array_filter(
|
||||
$lines,
|
||||
function ( $line ) {
|
||||
return '' !== trim( $line );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $output ) ) {
|
||||
$message = $apply_filter
|
||||
? sprintf(
|
||||
/* translators: %1$s: prefix, %2$d: number of lines checked */
|
||||
__( 'No %1$s entries found in the recent log data. Note: This viewer only shows the last %2$d lines of the log file.', self::$text_domain ),
|
||||
$prefix,
|
||||
self::$max_lines
|
||||
)
|
||||
: __( 'Debug log is empty.', self::$text_domain );
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'logs' => '',
|
||||
'count' => 0,
|
||||
'filtered' => $apply_filter,
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'message' => '',
|
||||
'logs' => implode( "\n", $output ),
|
||||
'count' => count( $output ),
|
||||
'filtered' => $apply_filter,
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
public static function clear_logs( $request ) {
|
||||
$log_path = static::get_log_path();
|
||||
|
||||
if ( ! file_exists( $log_path ) ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'cleared' => false,
|
||||
'message' => __( 'Debug log file does not exist.', self::$text_domain ),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! wp_is_writable( $log_path ) ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => false,
|
||||
'cleared' => false,
|
||||
'message' => __( 'Debug log file is not writable.', self::$text_domain ),
|
||||
),
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
$file_handle = fopen( $log_path, 'w' );
|
||||
|
||||
if ( false === $file_handle ) {
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => false,
|
||||
'cleared' => false,
|
||||
'message' => __( 'Failed to clear debug log file.', self::$text_domain ),
|
||||
),
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
fclose( $file_handle );
|
||||
|
||||
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
|
||||
error_log(
|
||||
sprintf(
|
||||
'[%s] [ChimpMatic Lite] Debug log cleared by user: %s',
|
||||
gmdate( 'd-M-Y H:i:s' ) . ' UTC',
|
||||
wp_get_current_user()->user_login
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'cleared' => true,
|
||||
'message' => __( 'Debug log cleared successfully.', self::$text_domain ),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
public static function log_browser_console( $request ) {
|
||||
$level = $request->get_param( 'level' );
|
||||
$message = $request->get_param( 'message' );
|
||||
$data = $request->get_param( 'data' );
|
||||
|
||||
$level_map = array(
|
||||
'log' => 'INFO',
|
||||
'info' => 'INFO',
|
||||
'warn' => 'WARNING',
|
||||
'error' => 'ERROR',
|
||||
'debug' => 'DEBUG',
|
||||
);
|
||||
|
||||
$wp_level = $level_map[ $level ] ?? 'INFO';
|
||||
$log_message = sprintf(
|
||||
'[%s] %s [Browser Console - %s] %s',
|
||||
gmdate( 'd-M-Y H:i:s' ) . ' UTC',
|
||||
static::$log_prefix,
|
||||
strtoupper( $level ),
|
||||
$message
|
||||
);
|
||||
|
||||
if ( ! empty( $data ) ) {
|
||||
$log_message .= ' | Data: ' . $data;
|
||||
}
|
||||
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
|
||||
error_log( $log_message );
|
||||
}
|
||||
$logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false );
|
||||
$logger = new Cmatic_File_Logger( 'Browser-Console', $logfile_enabled );
|
||||
$logger->log( $wp_level, 'Browser: ' . $message, $data ? json_decode( $data, true ) : null );
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'logged' => true,
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
protected static function read_last_lines( $filepath, $lines = 500 ) {
|
||||
$handle = fopen( $filepath, 'r' );
|
||||
if ( ! $handle ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$chunk = 4096;
|
||||
$file_size = filesize( $filepath );
|
||||
if ( 0 === $file_size ) {
|
||||
fclose( $handle );
|
||||
return array();
|
||||
}
|
||||
$pos = $file_size;
|
||||
$buffer = '';
|
||||
while ( $pos > 0 && count( $result ) < $lines ) {
|
||||
$read_size = min( $chunk, $pos );
|
||||
$pos -= $read_size;
|
||||
fseek( $handle, $pos );
|
||||
$buffer = fread( $handle, $read_size ) . $buffer;
|
||||
$buffer_lines = explode( "\n", $buffer );
|
||||
$buffer = array_shift( $buffer_lines );
|
||||
$result = array_merge( $buffer_lines, $result );
|
||||
}
|
||||
if ( 0 === $pos && ! empty( $buffer ) ) {
|
||||
array_unshift( $result, $buffer );
|
||||
}
|
||||
fclose( $handle );
|
||||
return array_slice( $result, -$lines );
|
||||
}
|
||||
|
||||
public static function enqueue_assets( $hook ) {
|
||||
}
|
||||
|
||||
protected static function get_inline_js() {
|
||||
$namespace = self::$namespace;
|
||||
|
||||
return <<<JS
|
||||
(function($) {
|
||||
'use strict';
|
||||
|
||||
var CmaticLogViewer = {
|
||||
namespace: '{$namespace}',
|
||||
|
||||
// Get REST API root URL (supports both LITE and PRO configurations).
|
||||
getRestRoot: function() {
|
||||
if (typeof wpApiSettings !== 'undefined' && wpApiSettings.root) {
|
||||
return wpApiSettings.root;
|
||||
}
|
||||
if (typeof chimpmaticLite !== 'undefined' && chimpmaticLite.restUrl) {
|
||||
// chimpmaticLite.restUrl includes 'chimpmatic-lite/v1/' already.
|
||||
return chimpmaticLite.restUrl.replace(/chimpmatic-lite\/v1\/$/, '');
|
||||
}
|
||||
// Fallback: construct from current URL.
|
||||
return window.location.origin + '/wp-json/';
|
||||
},
|
||||
|
||||
// Get REST API nonce.
|
||||
getNonce: function() {
|
||||
if (typeof wpApiSettings !== 'undefined' && wpApiSettings.nonce) {
|
||||
return wpApiSettings.nonce;
|
||||
}
|
||||
if (typeof chimpmaticLite !== 'undefined' && chimpmaticLite.restNonce) {
|
||||
return chimpmaticLite.restNonce;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
init: function() {
|
||||
$(document).on('click', '.cme-trigger-log', this.toggleLogs.bind(this));
|
||||
$(document).on('click', '.vc-clear-logs', this.clearLogs.bind(this));
|
||||
},
|
||||
|
||||
toggleLogs: function(e) {
|
||||
e.preventDefault();
|
||||
var \$container = $('#eventlog-sys');
|
||||
var \$trigger = $(e.currentTarget);
|
||||
|
||||
if (\$container.is(':visible')) {
|
||||
\$container.slideUp(200);
|
||||
\$trigger.text('View Debug Logs');
|
||||
} else {
|
||||
\$container.slideDown(200);
|
||||
\$trigger.text('Hide Debug Logs');
|
||||
this.fetchLogs();
|
||||
}
|
||||
},
|
||||
|
||||
fetchLogs: function() {
|
||||
var self = this;
|
||||
var \$panel = $('#log_panel');
|
||||
|
||||
\$panel.text('Loading logs...');
|
||||
|
||||
$.ajax({
|
||||
url: this.getRestRoot() + this.namespace + '/logs',
|
||||
method: 'GET',
|
||||
beforeSend: function(xhr) {
|
||||
var nonce = self.getNonce();
|
||||
if (nonce) {
|
||||
xhr.setRequestHeader('X-WP-Nonce', nonce);
|
||||
}
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.logs) {
|
||||
\$panel.text(response.logs);
|
||||
} else {
|
||||
\$panel.text(response.message || 'No logs found.');
|
||||
}
|
||||
},
|
||||
error: function(xhr) {
|
||||
\$panel.text('Error loading logs: ' + xhr.statusText);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
clearLogs: function(e) {
|
||||
e.preventDefault();
|
||||
$('#log_panel').text('Logs cleared.');
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
if ($('#eventlog-sys').is(':visible')) {
|
||||
this.fetchLogs();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$(document).ready(function() {
|
||||
CmaticLogViewer.init();
|
||||
});
|
||||
|
||||
// Expose for external use (e.g., after test submission).
|
||||
window.CmaticLogViewer = CmaticLogViewer;
|
||||
})(jQuery);
|
||||
JS;
|
||||
}
|
||||
|
||||
public static function render( $args = array() ) {
|
||||
$defaults = array(
|
||||
'title' => __( 'Submission Logs', self::$text_domain ),
|
||||
'clear_text' => __( 'Clear Logs', self::$text_domain ),
|
||||
'placeholder' => __( 'Click "View Debug Logs" to fetch the log content.', self::$text_domain ),
|
||||
'class' => '',
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
?>
|
||||
<div id="eventlog-sys" class="vc-logs <?php echo esc_attr( $args['class'] ); ?>" style="margin-top: 1em; margin-bottom: 1em; display: none;">
|
||||
<div class="mce-custom-fields">
|
||||
<div class="vc-logs-header">
|
||||
<span class="vc-logs-title"><?php echo esc_html( $args['title'] ); ?></span>
|
||||
<span class="vc-logs-actions">
|
||||
<a href="#" class="vc-toggle-filter" data-filtered="1"><?php echo esc_html__( 'Show All', 'chimpmatic-lite' ); ?></a>
|
||||
<span class="vc-logs-separator">|</span>
|
||||
<a href="#" class="vc-clear-logs"><?php echo esc_html( $args['clear_text'] ); ?></a>
|
||||
</span>
|
||||
</div>
|
||||
<pre><code id="log_panel"><?php echo esc_html( $args['placeholder'] ); ?></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API controller for per-form field and setting operations.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
final class Cmatic_Rest_Form {
|
||||
|
||||
/** @var string Primary REST namespace. */
|
||||
protected static $namespace = 'chimpmatic-lite/v1';
|
||||
|
||||
/** @var string Secondary REST namespace for form settings. */
|
||||
protected static $cmatic_namespace = 'cmatic';
|
||||
|
||||
/** @var bool Whether initialized. */
|
||||
protected static $initialized = false;
|
||||
|
||||
/** @var array Field pattern configuration. */
|
||||
protected static $field_patterns = array(
|
||||
'labeltags\\.(.+)' => array(
|
||||
'type' => 'boolean',
|
||||
'pro_only' => false,
|
||||
'nested' => 'labeltags',
|
||||
),
|
||||
'field(\\d+)' => array(
|
||||
'type' => 'string',
|
||||
'pro_only' => false,
|
||||
'direct' => true,
|
||||
),
|
||||
'customKey(\\d+)' => array(
|
||||
'type' => 'string',
|
||||
'pro_only' => false,
|
||||
'direct' => true,
|
||||
),
|
||||
'customValue(\\d+)' => array(
|
||||
'type' => 'string',
|
||||
'pro_only' => false,
|
||||
'direct' => true,
|
||||
),
|
||||
'GDPRCheck(\\d+)' => array(
|
||||
'type' => 'boolean',
|
||||
'pro_only' => true,
|
||||
'direct' => true,
|
||||
),
|
||||
'GDPRCustomValue(\\d+)' => array(
|
||||
'type' => 'string',
|
||||
'pro_only' => true,
|
||||
'direct' => true,
|
||||
),
|
||||
'ggCheck(\\d+)' => array(
|
||||
'type' => 'boolean',
|
||||
'pro_only' => true,
|
||||
'direct' => true,
|
||||
),
|
||||
'ggCustomValue(\\d+)' => array(
|
||||
'type' => 'string',
|
||||
'pro_only' => true,
|
||||
'direct' => true,
|
||||
),
|
||||
);
|
||||
|
||||
public static function init() {
|
||||
if ( self::$initialized ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'rest_api_init', array( static::class, 'register_routes' ) );
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
public static function register_routes() {
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/tags/toggle',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'toggle_tag' ),
|
||||
'permission_callback' => array( static::class, 'check_admin_permission' ),
|
||||
'args' => array(
|
||||
'form_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
),
|
||||
'tag' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
'enabled' => array(
|
||||
'required' => true,
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/form/field',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'save_field' ),
|
||||
'permission_callback' => array( static::class, 'check_form_permission' ),
|
||||
'args' => array(
|
||||
'form_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
),
|
||||
'field' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
'value' => array(
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::$cmatic_namespace,
|
||||
'/form/setting',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'save_setting' ),
|
||||
'permission_callback' => array( static::class, 'check_admin_permission' ),
|
||||
'args' => array(
|
||||
'form_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_numeric( $param ) && $param > 0;
|
||||
},
|
||||
),
|
||||
'field' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
),
|
||||
'value' => array(
|
||||
'required' => true,
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return Cmatic_Utils::validate_bool( $value );
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function check_admin_permission( $request ) {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
esc_html__( 'You do not have permission to access this endpoint.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$nonce = $request->get_header( 'X-WP-Nonce' );
|
||||
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cookie_invalid_nonce',
|
||||
esc_html__( 'Cookie nonce is invalid.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function check_form_permission( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
|
||||
if ( ! current_user_can( 'wpcf7_edit_contact_form', $form_id ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
esc_html__( 'You do not have permission to access the API key.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$nonce = $request->get_header( 'X-WP-Nonce' );
|
||||
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cookie_invalid_nonce',
|
||||
esc_html__( 'Cookie nonce is invalid.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function toggle_tag( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
$tag = $request->get_param( 'tag' );
|
||||
$enabled = $request->get_param( 'enabled' );
|
||||
|
||||
if ( ! $form_id || ! $tag ) {
|
||||
return new WP_Error(
|
||||
'missing_params',
|
||||
__( 'Missing required parameters.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
|
||||
if ( ! isset( $cf7_mch['labeltags'] ) || ! is_array( $cf7_mch['labeltags'] ) ) {
|
||||
$cf7_mch['labeltags'] = array();
|
||||
}
|
||||
|
||||
if ( $enabled ) {
|
||||
$cf7_mch['labeltags'][ $tag ] = '1';
|
||||
} else {
|
||||
unset( $cf7_mch['labeltags'][ $tag ] );
|
||||
}
|
||||
|
||||
update_option( $option_name, $cf7_mch );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'tag' => $tag,
|
||||
'enabled' => $enabled,
|
||||
'message' => $enabled
|
||||
? sprintf( __( 'Tag [%s] enabled.', 'chimpmatic-lite' ), $tag )
|
||||
: sprintf( __( 'Tag [%s] disabled.', 'chimpmatic-lite' ), $tag ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function save_field( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
$field = $request->get_param( 'field' );
|
||||
$value = $request->get_param( 'value' );
|
||||
|
||||
$matched_config = null;
|
||||
$matched_key = null;
|
||||
|
||||
foreach ( self::$field_patterns as $pattern => $config ) {
|
||||
if ( preg_match( '/^' . $pattern . '$/', $field, $matches ) ) {
|
||||
$matched_config = $config;
|
||||
$matched_key = isset( $matches[1] ) ? $matches[1] : null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( null === $matched_config ) {
|
||||
return new WP_Error(
|
||||
'invalid_field',
|
||||
sprintf( __( 'Field "%s" is not allowed.', 'chimpmatic-lite' ), $field ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $matched_config['pro_only'] && ! defined( 'CMATIC_VERSION' ) ) {
|
||||
return new WP_Error(
|
||||
'pro_required',
|
||||
__( 'This field requires ChimpMatic PRO.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'boolean' === $matched_config['type'] ) {
|
||||
$value = rest_sanitize_boolean( $value );
|
||||
} elseif ( 'string' === $matched_config['type'] ) {
|
||||
$value = trim( sanitize_text_field( $value ) );
|
||||
}
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
|
||||
if ( isset( $matched_config['nested'] ) ) {
|
||||
$nested_key = $matched_config['nested'];
|
||||
if ( ! isset( $cf7_mch[ $nested_key ] ) ) {
|
||||
$cf7_mch[ $nested_key ] = array();
|
||||
}
|
||||
|
||||
if ( $value ) {
|
||||
$cf7_mch[ $nested_key ][ $matched_key ] = true;
|
||||
} else {
|
||||
unset( $cf7_mch[ $nested_key ][ $matched_key ] );
|
||||
}
|
||||
} elseif ( null === $value || '' === $value ) {
|
||||
unset( $cf7_mch[ $field ] );
|
||||
} else {
|
||||
$cf7_mch[ $field ] = $value;
|
||||
}
|
||||
|
||||
update_option( $option_name, $cf7_mch );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'field' => $field,
|
||||
'value' => $value,
|
||||
'message' => __( 'Field saved successfully.', 'chimpmatic-lite' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function save_setting( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
$field = $request->get_param( 'field' );
|
||||
$value = $request->get_param( 'value' );
|
||||
|
||||
$allowed_fields = array(
|
||||
'sync_tags' => array(
|
||||
'label' => __( 'Sync Tags', 'chimpmatic-lite' ),
|
||||
'pro_only' => false,
|
||||
),
|
||||
'double_optin' => array(
|
||||
'label' => __( 'Double Opt-in', 'chimpmatic-lite' ),
|
||||
'pro_only' => false,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the allowed per-form setting fields.
|
||||
*
|
||||
* Pro can extend this to add GDPR, Groups/Interests, etc.
|
||||
*
|
||||
* @since 0.9.69
|
||||
*
|
||||
* @param array $allowed_fields Associative array of field_name => config.
|
||||
* @param int $form_id The CF7 form ID.
|
||||
*/
|
||||
$allowed_fields = apply_filters( 'cmatic_form_setting_fields', $allowed_fields, $form_id );
|
||||
|
||||
if ( ! array_key_exists( $field, $allowed_fields ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_field',
|
||||
sprintf(
|
||||
/* translators: %s: field name */
|
||||
__( 'Field "%s" is not a valid setting.', 'chimpmatic-lite' ),
|
||||
$field
|
||||
),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$field_config = $allowed_fields[ $field ];
|
||||
|
||||
if ( ! empty( $field_config['pro_only'] ) && ! defined( 'CMATIC_VERSION' ) ) {
|
||||
return new WP_Error(
|
||||
'pro_required',
|
||||
sprintf(
|
||||
/* translators: %s: field label */
|
||||
__( '%s requires ChimpMatic Pro.', 'chimpmatic-lite' ),
|
||||
$field_config['label']
|
||||
),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
|
||||
if ( ! is_array( $cf7_mch ) ) {
|
||||
$cf7_mch = array();
|
||||
}
|
||||
|
||||
$cf7_mch[ $field ] = $value ? 1 : 0;
|
||||
|
||||
update_option( $option_name, $cf7_mch );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'form_id' => $form_id,
|
||||
'field' => $field,
|
||||
'value' => (bool) $value,
|
||||
'message' => $value
|
||||
? sprintf(
|
||||
/* translators: %s: field label */
|
||||
__( '%s enabled.', 'chimpmatic-lite' ),
|
||||
$field_config['label']
|
||||
)
|
||||
: sprintf(
|
||||
/* translators: %s: field label */
|
||||
__( '%s disabled.', 'chimpmatic-lite' ),
|
||||
$field_config['label']
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API controller for Mailchimp lists and merge fields.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
final class Cmatic_Rest_Lists {
|
||||
|
||||
/** @var string REST namespace. */
|
||||
protected static $namespace = 'chimpmatic-lite/v1';
|
||||
|
||||
/** @var bool Whether initialized. */
|
||||
protected static $initialized = false;
|
||||
|
||||
public static function init() {
|
||||
if ( self::$initialized ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'rest_api_init', array( static::class, 'register_routes' ) );
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
public static function register_routes() {
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/lists',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'get_lists' ),
|
||||
'permission_callback' => array( static::class, 'check_form_permission' ),
|
||||
'args' => array(
|
||||
'form_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_numeric( $param ) && $param > 0;
|
||||
},
|
||||
),
|
||||
'api_key' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'validate_callback' => function ( $param ) {
|
||||
return preg_match( '/^[a-f0-9]{32}-[a-z]{2,3}\d+$/', $param );
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/merge-fields',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'get_merge_fields' ),
|
||||
'permission_callback' => array( static::class, 'check_form_permission' ),
|
||||
'args' => array(
|
||||
'form_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
),
|
||||
'list_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/api-key/(?P<form_id>\d+)',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( static::class, 'get_api_key' ),
|
||||
'permission_callback' => array( static::class, 'check_form_permission' ),
|
||||
'args' => array(
|
||||
'form_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_numeric( $param ) && $param > 0;
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function check_form_permission( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
|
||||
if ( ! current_user_can( 'wpcf7_edit_contact_form', $form_id ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
esc_html__( 'You do not have permission to access the API key.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$nonce = $request->get_header( 'X-WP-Nonce' );
|
||||
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cookie_invalid_nonce',
|
||||
esc_html__( 'Cookie nonce is invalid.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function get_lists( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
$api_key = $request->get_param( 'api_key' );
|
||||
|
||||
if ( ! Cmatic_Options_Repository::get_option( 'api.sync_attempted' ) ) {
|
||||
Cmatic_Options_Repository::set_option( 'api.sync_attempted', time() );
|
||||
}
|
||||
$current_count = (int) Cmatic_Options_Repository::get_option( 'api.sync_attempts_count', 0 );
|
||||
Cmatic_Options_Repository::set_option( 'api.sync_attempts_count', $current_count + 1 );
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
|
||||
if ( ! is_array( $cf7_mch ) ) {
|
||||
$cf7_mch = array();
|
||||
}
|
||||
|
||||
$logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false );
|
||||
|
||||
try {
|
||||
$validation_result = Cmatic_Lite_Api_Service::validate_key( $api_key, $logfile_enabled );
|
||||
$api_valid = $validation_result['api-validation'] ?? 0;
|
||||
|
||||
$lists_result = ( 1 === (int) $api_valid ) ? Cmatic_Lite_Api_Service::get_lists( $api_key, $logfile_enabled ) : array( 'lisdata' => array() );
|
||||
$lists_data = $lists_result['lisdata'] ?? array();
|
||||
$merge_fields_data = $lists_result['merge_fields'] ?? array();
|
||||
|
||||
$lists = array();
|
||||
if ( $api_valid === 1 && isset( $lists_data['lists'] ) && is_array( $lists_data['lists'] ) ) {
|
||||
foreach ( $lists_data['lists'] as $list ) {
|
||||
if ( is_array( $list ) && isset( $list['id'], $list['name'] ) ) {
|
||||
$member_count = isset( $list['stats']['member_count'] ) ? intval( $list['stats']['member_count'] ) : 0;
|
||||
$field_count = isset( $list['stats']['merge_field_count'] ) ? intval( $list['stats']['merge_field_count'] ) : 0;
|
||||
|
||||
$lists[] = array(
|
||||
'id' => $list['id'],
|
||||
'name' => $list['name'],
|
||||
'member_count' => $member_count,
|
||||
'field_count' => $field_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$excluded_types = array( 'address', 'birthday', 'imageurl', 'zip' );
|
||||
$merge_fields = array();
|
||||
|
||||
$merge_fields[] = array(
|
||||
'tag' => 'EMAIL',
|
||||
'name' => 'Subscriber Email',
|
||||
'type' => 'email',
|
||||
);
|
||||
|
||||
if ( isset( $merge_fields_data['merge_fields'] ) && is_array( $merge_fields_data['merge_fields'] ) ) {
|
||||
$fields_to_process = $merge_fields_data['merge_fields'];
|
||||
|
||||
usort(
|
||||
$fields_to_process,
|
||||
function ( $a, $b ) {
|
||||
return ( $a['display_order'] ?? 0 ) - ( $b['display_order'] ?? 0 );
|
||||
}
|
||||
);
|
||||
|
||||
$count = 1;
|
||||
foreach ( $fields_to_process as $field ) {
|
||||
$field_type = strtolower( $field['type'] ?? '' );
|
||||
$field_tag = $field['tag'] ?? '';
|
||||
|
||||
if ( $field_tag === 'EMAIL' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $field_type, $excluded_types, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $count >= CMATIC_LITE_FIELDS ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$merge_fields[] = array(
|
||||
'tag' => $field_tag,
|
||||
'name' => $field['name'] ?? '',
|
||||
'type' => $field_type,
|
||||
);
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
$settings_to_save = array_merge(
|
||||
$cf7_mch,
|
||||
$validation_result,
|
||||
$lists_result,
|
||||
array(
|
||||
'api' => $api_key,
|
||||
'merge_fields' => $merge_fields,
|
||||
)
|
||||
);
|
||||
update_option( $option_name, $settings_to_save );
|
||||
|
||||
// Record first successful connection after form settings are saved.
|
||||
if ( 1 === (int) $api_valid && ! Cmatic_Options_Repository::get_option( 'api.first_connected' ) ) {
|
||||
Cmatic_Options_Repository::set_option( 'api.first_connected', time() );
|
||||
}
|
||||
|
||||
if ( ! empty( $lists_result['lisdata'] ) ) {
|
||||
Cmatic_Options_Repository::set_option( 'lisdata', $lists_result['lisdata'] );
|
||||
Cmatic_Options_Repository::set_option( 'lisdata_updated', time() );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'api_valid' => $api_valid === 1,
|
||||
'lists' => $lists,
|
||||
'total' => count( $lists ),
|
||||
'merge_fields' => $merge_fields,
|
||||
)
|
||||
);
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
$logger = new Cmatic_File_Logger( 'REST-API-Error', true );
|
||||
$logger->log( 'ERROR', 'REST API list loading failed.', $e->getMessage() );
|
||||
|
||||
return new WP_Error(
|
||||
'api_request_failed',
|
||||
esc_html__( 'Failed to load Mailchimp lists. Check debug log for details.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_merge_fields( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
$list_id = $request->get_param( 'list_id' );
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
$api_key = $cf7_mch['api'] ?? '';
|
||||
$logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false );
|
||||
|
||||
if ( empty( $api_key ) ) {
|
||||
return new WP_Error(
|
||||
'missing_api_key',
|
||||
esc_html__( 'API key not found. Please connect to Mailchimp first.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
$merge_fields_result = Cmatic_Lite_Api_Service::get_merge_fields( $api_key, $list_id, $logfile_enabled );
|
||||
$merge_fields_data = $merge_fields_result['merge_fields'] ?? array();
|
||||
|
||||
$excluded_types = array( 'address', 'birthday', 'imageurl', 'zip' );
|
||||
$merge_fields = array();
|
||||
|
||||
$merge_fields[] = array(
|
||||
'tag' => 'EMAIL',
|
||||
'name' => 'Subscriber Email',
|
||||
'type' => 'email',
|
||||
);
|
||||
|
||||
$raw_field_count = 0;
|
||||
|
||||
if ( isset( $merge_fields_data['merge_fields'] ) && is_array( $merge_fields_data['merge_fields'] ) ) {
|
||||
$fields_to_process = $merge_fields_data['merge_fields'];
|
||||
$raw_field_count = count( $fields_to_process ) + 1;
|
||||
|
||||
usort(
|
||||
$fields_to_process,
|
||||
function ( $a, $b ) {
|
||||
return ( $a['display_order'] ?? 0 ) - ( $b['display_order'] ?? 0 );
|
||||
}
|
||||
);
|
||||
|
||||
$count = 1;
|
||||
foreach ( $fields_to_process as $field ) {
|
||||
$field_type = strtolower( $field['type'] ?? '' );
|
||||
$field_tag = $field['tag'] ?? '';
|
||||
|
||||
if ( $field_tag === 'EMAIL' ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $field_type, $excluded_types, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $count >= CMATIC_LITE_FIELDS ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$merge_fields[] = array(
|
||||
'tag' => $field_tag,
|
||||
'name' => $field['name'] ?? '',
|
||||
'type' => $field_type,
|
||||
);
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
$cf7_mch['merge_fields'] = $merge_fields;
|
||||
$cf7_mch['list'] = $list_id;
|
||||
$cf7_mch['total_merge_fields'] = $raw_field_count;
|
||||
update_option( $option_name, $cf7_mch );
|
||||
|
||||
if ( ! Cmatic_Options_Repository::get_option( 'api.audience_selected' ) ) {
|
||||
Cmatic_Options_Repository::set_option( 'api.audience_selected', time() );
|
||||
}
|
||||
|
||||
if ( class_exists( 'Cmatic\\Metrics\\Core\\Sync' ) && class_exists( 'Cmatic\\Metrics\\Core\\Collector' ) ) {
|
||||
$payload = \Cmatic\Metrics\Core\Collector::collect( 'list_selected' );
|
||||
\Cmatic\Metrics\Core\Sync::send_async( $payload );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'merge_fields' => $merge_fields,
|
||||
)
|
||||
);
|
||||
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error(
|
||||
'api_request_failed',
|
||||
esc_html__( 'Failed to load merge fields. Check debug log for details.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_api_key( $request ) {
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
|
||||
if ( ! is_array( $cf7_mch ) ) {
|
||||
$cf7_mch = array();
|
||||
}
|
||||
|
||||
$api_key = isset( $cf7_mch['api'] ) ? $cf7_mch['api'] : '';
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'api_key' => $api_key,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API controller for reset operations.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
final class Cmatic_Rest_Reset {
|
||||
|
||||
/** @var string REST namespace. */
|
||||
protected static $namespace = 'chimpmatic-lite/v1';
|
||||
|
||||
/** @var bool Whether initialized. */
|
||||
protected static $initialized = false;
|
||||
|
||||
/** @var array License options to delete during nuclear reset. */
|
||||
protected static $license_options = array(
|
||||
'chimpmatic_license_activation',
|
||||
'chimpmatic_license_status',
|
||||
'chimpmatic_license_state',
|
||||
'chimpmatic_license_last_check',
|
||||
'chimpmatic_license_error_state',
|
||||
'cmatic_license_activated',
|
||||
'cmatic_license_api_key',
|
||||
'cmatic_product_id',
|
||||
'wc_am_client_chimpmatic',
|
||||
'wc_am_product_id_chimpmatic',
|
||||
'wc_am_client_chimpmatic_activated',
|
||||
'wc_am_client_chimpmatic_instance',
|
||||
'wc_am_client_chimpmatic_deactivate_checkbox',
|
||||
'chimpmatic_product_id',
|
||||
);
|
||||
|
||||
public static function init() {
|
||||
if ( self::$initialized ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'rest_api_init', array( static::class, 'register_routes' ) );
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
public static function register_routes() {
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/settings/reset',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'reset_settings' ),
|
||||
'permission_callback' => array( static::class, 'check_admin_permission' ),
|
||||
'args' => array(
|
||||
'type' => array(
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'default' => 'form',
|
||||
'enum' => array( 'form', 'nuclear' ),
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
'form_id' => array(
|
||||
'required' => false,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function check_admin_permission( $request ) {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
esc_html__( 'You do not have permission to access this endpoint.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$nonce = $request->get_header( 'X-WP-Nonce' );
|
||||
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cookie_invalid_nonce',
|
||||
esc_html__( 'Cookie nonce is invalid.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function reset_settings( $request ) {
|
||||
$type = $request->get_param( 'type' );
|
||||
|
||||
if ( 'nuclear' === $type ) {
|
||||
return self::nuclear_reset( $request );
|
||||
}
|
||||
|
||||
$form_id = $request->get_param( 'form_id' );
|
||||
if ( ! $form_id ) {
|
||||
return new WP_Error(
|
||||
'missing_form_id',
|
||||
__( 'Form ID is required for form reset.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
delete_option( $option_name );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'message' => __( 'Form settings cleared successfully.', 'chimpmatic-lite' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function nuclear_reset( $request ) {
|
||||
global $wpdb;
|
||||
|
||||
$current_user = wp_get_current_user();
|
||||
$username = $current_user->user_login ?? 'unknown';
|
||||
$deleted_counts = array();
|
||||
|
||||
$options_deleted = 0;
|
||||
foreach ( self::$license_options as $option ) {
|
||||
if ( delete_option( $option ) ) {
|
||||
++$options_deleted;
|
||||
}
|
||||
}
|
||||
$deleted_counts['options'] = $options_deleted;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$transients_deleted = $wpdb->query(
|
||||
"DELETE FROM {$wpdb->options}
|
||||
WHERE option_name LIKE '_transient_%chimpmatic%'
|
||||
OR option_name LIKE '_transient_timeout_%chimpmatic%'
|
||||
OR option_name LIKE '_site_transient_%chimpmatic%'
|
||||
OR option_name LIKE '_site_transient_timeout_%chimpmatic%'
|
||||
OR option_name LIKE 'site_transient_%chimpmatic%'
|
||||
OR option_name LIKE '_transient_%cmatic%'
|
||||
OR option_name LIKE '_transient_timeout_%cmatic%'
|
||||
OR option_name LIKE '_site_transient_%cmatic%'
|
||||
OR option_name LIKE '_site_transient_timeout_%cmatic%'
|
||||
OR option_name LIKE 'site_transient_%cmatic%'"
|
||||
);
|
||||
$deleted_counts['transients'] = (int) $transients_deleted;
|
||||
$cache_flushed = false;
|
||||
if ( function_exists( 'wp_cache_flush' ) ) {
|
||||
$cache_flushed = wp_cache_flush();
|
||||
}
|
||||
$deleted_counts['cache_flushed'] = $cache_flushed;
|
||||
delete_site_transient( 'update_plugins' );
|
||||
update_option( 'chimpmatic_license_status', 'deactivated' );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'message' => 'License data completely wiped (nuclear reset)',
|
||||
'deleted_counts' => $deleted_counts,
|
||||
'performed_by' => $username,
|
||||
'timestamp' => current_time( 'mysql' ),
|
||||
'actions_taken' => array(
|
||||
'Deleted ' . $options_deleted . ' license options',
|
||||
'Deleted ' . $transients_deleted . ' cached transients',
|
||||
'Flushed object cache: ' . ( $cache_flushed ? 'yes' : 'no' ),
|
||||
'Cleared plugin update cache',
|
||||
'Set license status to deactivated',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API controller for global settings.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
final class Cmatic_Rest_Settings {
|
||||
|
||||
/** @var string REST namespace. */
|
||||
protected static $namespace = 'chimpmatic-lite/v1';
|
||||
|
||||
/** @var bool Whether initialized. */
|
||||
protected static $initialized = false;
|
||||
|
||||
/** @var array Allowed GLOBAL settings configuration (toggles in Advanced Settings panel). */
|
||||
protected static $allowed_settings = array(
|
||||
'debug' => array(
|
||||
'type' => 'cmatic',
|
||||
'path' => 'debug',
|
||||
),
|
||||
'backlink' => array(
|
||||
'type' => 'cmatic',
|
||||
'path' => 'backlink',
|
||||
),
|
||||
'auto_update' => array(
|
||||
'type' => 'cmatic',
|
||||
'path' => 'auto_update',
|
||||
),
|
||||
'telemetry' => array(
|
||||
'type' => 'cmatic',
|
||||
'path' => 'telemetry.enabled',
|
||||
),
|
||||
);
|
||||
|
||||
/** @var array Field labels for user messages. */
|
||||
protected static $field_labels = array(
|
||||
'debug' => 'Debug Logger',
|
||||
'backlink' => 'Developer Backlink',
|
||||
'auto_update' => 'Auto Update',
|
||||
'telemetry' => 'Usage Statistics',
|
||||
);
|
||||
|
||||
public static function init() {
|
||||
if ( self::$initialized ) {
|
||||
return;
|
||||
}
|
||||
add_action( 'rest_api_init', array( static::class, 'register_routes' ) );
|
||||
self::$initialized = true;
|
||||
}
|
||||
|
||||
public static function register_routes() {
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/settings/toggle',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'toggle_setting' ),
|
||||
'permission_callback' => array( static::class, 'check_admin_permission' ),
|
||||
'args' => array(
|
||||
'field' => array(
|
||||
'required' => true,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
'enabled' => array(
|
||||
'required' => true,
|
||||
'type' => 'boolean',
|
||||
'sanitize_callback' => 'rest_sanitize_boolean',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
self::$namespace,
|
||||
'/notices/dismiss',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( static::class, 'dismiss_notice' ),
|
||||
'permission_callback' => array( static::class, 'check_admin_permission' ),
|
||||
'args' => array(
|
||||
'notice_id' => array(
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'default' => 'news',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'enum' => array( 'news', 'upgrade' ),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public static function check_admin_permission( $request ) {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden',
|
||||
esc_html__( 'You do not have permission to access this endpoint.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
$nonce = $request->get_header( 'X-WP-Nonce' );
|
||||
if ( ! wp_verify_nonce( $nonce, 'wp_rest' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cookie_invalid_nonce',
|
||||
esc_html__( 'Cookie nonce is invalid.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function toggle_setting( $request ) {
|
||||
$field = $request->get_param( 'field' );
|
||||
$enabled = $request->get_param( 'enabled' );
|
||||
|
||||
if ( ! array_key_exists( $field, self::$allowed_settings ) ) {
|
||||
return new WP_Error(
|
||||
'invalid_field',
|
||||
__( 'Invalid settings field.', 'chimpmatic-lite' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$field_config = self::$allowed_settings[ $field ];
|
||||
|
||||
if ( 'telemetry' === $field ) {
|
||||
self::handle_telemetry_toggle( $enabled );
|
||||
}
|
||||
|
||||
Cmatic_Options_Repository::set_option( $field_config['path'], $enabled ? 1 : 0 );
|
||||
|
||||
$label = self::$field_labels[ $field ] ?? ucfirst( str_replace( '_', ' ', $field ) );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'field' => $field,
|
||||
'enabled' => $enabled,
|
||||
'message' => $enabled
|
||||
? sprintf( __( '%s enabled.', 'chimpmatic-lite' ), $label )
|
||||
: sprintf( __( '%s disabled.', 'chimpmatic-lite' ), $label ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected static function handle_telemetry_toggle( $enabled ) {
|
||||
if ( class_exists( 'Cmatic\\Metrics\\Core\\Storage' ) && class_exists( 'Cmatic\\Metrics\\Core\\Tracker' ) ) {
|
||||
$storage_enabled = \Cmatic\Metrics\Core\Storage::is_enabled();
|
||||
if ( ! $enabled && $storage_enabled ) {
|
||||
\Cmatic\Metrics\Core\Tracker::on_opt_out();
|
||||
}
|
||||
if ( $enabled && ! $storage_enabled ) {
|
||||
\Cmatic\Metrics\Core\Tracker::on_re_enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function toggle_telemetry( $request ) {
|
||||
$enabled = $request->get_param( 'enabled' );
|
||||
|
||||
self::handle_telemetry_toggle( $enabled );
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.enabled', $enabled );
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'enabled' => $enabled,
|
||||
'message' => $enabled
|
||||
? esc_html__( 'Telemetry enabled. Thank you for helping improve the plugin!', 'chimpmatic-lite' )
|
||||
: esc_html__( 'Telemetry disabled.', 'chimpmatic-lite' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function dismiss_notice( $request ) {
|
||||
$notice_id = $request->get_param( 'notice_id' );
|
||||
|
||||
switch ( $notice_id ) {
|
||||
case 'upgrade':
|
||||
Cmatic_Options_Repository::set_option( 'ui.upgrade_clicked', true );
|
||||
$message = __( 'Upgrade notice dismissed.', 'chimpmatic-lite' );
|
||||
break;
|
||||
|
||||
case 'news':
|
||||
default:
|
||||
Cmatic_Options_Repository::set_option( 'ui.news', false );
|
||||
$message = __( 'Notice dismissed successfully.', 'chimpmatic-lite' );
|
||||
break;
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'success' => true,
|
||||
'message' => esc_html( $message ),
|
||||
'dismissed' => $notice_id,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Form submission feedback handler.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Cmatic_Submission_Feedback {
|
||||
|
||||
private static $last_result = null;
|
||||
|
||||
public static function init() {
|
||||
add_filter( 'wpcf7_feedback_response', array( __CLASS__, 'inject_feedback' ), 10, 2 );
|
||||
}
|
||||
|
||||
public static function set_result( $result ) {
|
||||
self::$last_result = $result;
|
||||
}
|
||||
|
||||
public static function get_result() {
|
||||
return self::$last_result;
|
||||
}
|
||||
|
||||
public static function clear() {
|
||||
self::$last_result = null;
|
||||
}
|
||||
|
||||
public static function inject_feedback( $response, $result ) {
|
||||
if ( null !== self::$last_result ) {
|
||||
$response['chimpmatic'] = self::$last_result;
|
||||
self::clear();
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
public static function success( $email, $status, $merge_vars = array(), $api_response = array() ) {
|
||||
$received = array();
|
||||
|
||||
if ( ! empty( $api_response['email_address'] ) ) {
|
||||
$received['EMAIL'] = $api_response['email_address'];
|
||||
}
|
||||
|
||||
if ( ! empty( $api_response['merge_fields'] ) && is_array( $api_response['merge_fields'] ) ) {
|
||||
foreach ( $api_response['merge_fields'] as $key => $value ) {
|
||||
if ( ! empty( $value ) || '0' === $value || 0 === $value ) {
|
||||
$received[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $api_response['tags'] ) && is_array( $api_response['tags'] ) ) {
|
||||
$tag_names = array_column( $api_response['tags'], 'name' );
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
$received['TAGS'] = implode( ', ', $tag_names );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $merge_vars['INTERESTS'] ) ) {
|
||||
$received['INTERESTS'] = '✓ ' . $merge_vars['INTERESTS'];
|
||||
} elseif ( ! empty( $api_response['interests'] ) && is_array( $api_response['interests'] ) ) {
|
||||
$enabled_count = count( array_filter( $api_response['interests'] ) );
|
||||
if ( $enabled_count > 0 ) {
|
||||
$received['INTERESTS'] = '✓ ' . $enabled_count . ( 1 === $enabled_count ? ' group' : ' groups' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $merge_vars['GDPR'] ) ) {
|
||||
$received['GDPR'] = '✓ ' . $merge_vars['GDPR'];
|
||||
} elseif ( ! empty( $api_response['marketing_permissions'] ) && is_array( $api_response['marketing_permissions'] ) ) {
|
||||
$enabled_count = 0;
|
||||
$total_count = count( $api_response['marketing_permissions'] );
|
||||
foreach ( $api_response['marketing_permissions'] as $permission ) {
|
||||
if ( ! empty( $permission['enabled'] ) ) {
|
||||
++$enabled_count;
|
||||
}
|
||||
}
|
||||
$received['GDPR'] = '✓ ' . $enabled_count . ' of ' . $total_count . ( 1 === $total_count ? ' permission' : ' permissions' );
|
||||
}
|
||||
|
||||
if ( ! empty( $api_response['location'] ) && is_array( $api_response['location'] ) ) {
|
||||
$lat = $api_response['location']['latitude'] ?? 0;
|
||||
$lng = $api_response['location']['longitude'] ?? 0;
|
||||
if ( 0 !== $lat || 0 !== $lng ) {
|
||||
$received['LOCATION'] = round( $lat, 4 ) . ', ' . round( $lng, 4 );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $api_response['language'] ) ) {
|
||||
$received['LANGUAGE'] = strtoupper( $api_response['language'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $api_response['email_type'] ) ) {
|
||||
$received['EMAIL_TYPE'] = strtoupper( $api_response['email_type'] );
|
||||
}
|
||||
|
||||
if ( ! empty( $api_response['status'] ) ) {
|
||||
$received['STATUS'] = ucfirst( $api_response['status'] );
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'email' => $email,
|
||||
'status' => $status,
|
||||
'status_text' => self::get_status_text( $status ),
|
||||
'merge_vars' => $merge_vars,
|
||||
'received' => $received,
|
||||
'message' => self::get_success_message( $email, $status ),
|
||||
);
|
||||
}
|
||||
|
||||
public static function failure( $reason, $detail = '', $email = '' ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'reason' => $reason,
|
||||
'detail' => $detail,
|
||||
'email' => $email,
|
||||
'message' => self::get_failure_message( $reason, $detail ),
|
||||
);
|
||||
}
|
||||
|
||||
public static function skipped( $reason ) {
|
||||
return array(
|
||||
'success' => null,
|
||||
'skipped' => true,
|
||||
'reason' => $reason,
|
||||
'message' => self::get_skip_message( $reason ),
|
||||
);
|
||||
}
|
||||
|
||||
private static function get_status_text( $status ) {
|
||||
$statuses = array(
|
||||
'subscribed' => __( 'Subscribed', 'chimpmatic-lite' ),
|
||||
'pending' => __( 'Pending Confirmation', 'chimpmatic-lite' ),
|
||||
'unsubscribed' => __( 'Unsubscribed', 'chimpmatic-lite' ),
|
||||
);
|
||||
return isset( $statuses[ $status ] ) ? $statuses[ $status ] : $status;
|
||||
}
|
||||
|
||||
private static function get_success_message( $email, $status ) {
|
||||
if ( 'pending' === $status ) {
|
||||
/* translators: %s: email address */
|
||||
return sprintf( __( '%s added - awaiting confirmation email.', 'chimpmatic-lite' ), $email );
|
||||
}
|
||||
/* translators: %s: email address */
|
||||
return sprintf( __( '%s subscribed successfully!', 'chimpmatic-lite' ), $email );
|
||||
}
|
||||
|
||||
private static function get_failure_message( $reason, $detail = '' ) {
|
||||
$messages = array(
|
||||
'invalid_email' => __( 'Invalid email address provided.', 'chimpmatic-lite' ),
|
||||
'already_subscribed' => __( 'This email is already subscribed.', 'chimpmatic-lite' ),
|
||||
'permanently_deleted' => __( 'This email was permanently deleted and cannot be re-imported.', 'chimpmatic-lite' ),
|
||||
'previously_unsubscribed' => __( 'This email previously unsubscribed and cannot be re-added.', 'chimpmatic-lite' ),
|
||||
'compliance_state' => __( 'This email is in a compliance state and cannot be subscribed.', 'chimpmatic-lite' ),
|
||||
'api_error' => __( 'Mailchimp API error occurred.', 'chimpmatic-lite' ),
|
||||
'network_error' => __( 'Network error connecting to Mailchimp.', 'chimpmatic-lite' ),
|
||||
);
|
||||
|
||||
$message = isset( $messages[ $reason ] ) ? $messages[ $reason ] : __( 'Subscription failed.', 'chimpmatic-lite' );
|
||||
|
||||
if ( ! empty( $detail ) ) {
|
||||
$message .= ' ' . $detail;
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private static function get_skip_message( $reason ) {
|
||||
$messages = array(
|
||||
'acceptance_not_checked' => __( 'Opt-in checkbox was not checked.', 'chimpmatic-lite' ),
|
||||
'no_api_configured' => __( 'Mailchimp API is not configured for this form.', 'chimpmatic-lite' ),
|
||||
);
|
||||
return isset( $messages[ $reason ] ) ? $messages[ $reason ] : __( 'Subscription skipped.', 'chimpmatic-lite' );
|
||||
}
|
||||
|
||||
public static function parse_api_error( $api_response, $email = '' ) {
|
||||
$title = isset( $api_response['title'] ) ? $api_response['title'] : '';
|
||||
$detail = isset( $api_response['detail'] ) ? $api_response['detail'] : '';
|
||||
|
||||
if ( strpos( strtolower( $title ), 'member exists' ) !== false ) {
|
||||
return self::failure( 'already_subscribed', '', $email );
|
||||
}
|
||||
|
||||
if ( strpos( strtolower( $detail ), 'permanently deleted' ) !== false ) {
|
||||
return self::failure( 'permanently_deleted', '', $email );
|
||||
}
|
||||
|
||||
if ( strpos( strtolower( $detail ), 'compliance state' ) !== false ) {
|
||||
return self::failure( 'compliance_state', '', $email );
|
||||
}
|
||||
|
||||
if ( strpos( strtolower( $title ), 'forgotten email' ) !== false ) {
|
||||
return self::failure( 'permanently_deleted', '', $email );
|
||||
}
|
||||
|
||||
return self::failure( 'api_error', $detail, $email );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user