Changed source root directory

This commit is contained in:
2026-03-05 16:30:11 +01:00
parent dc85447ee1
commit 538f85d7a2
5868 changed files with 749734 additions and 99 deletions

View File

@@ -0,0 +1,67 @@
<?php
/**
* Mailchimp API key importer.
*
* @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_Api_Key_Importer {
public static function detect() {
$mc4wp = get_option( 'mc4wp' );
if ( ! empty( $mc4wp['api_key'] ) ) {
return $mc4wp['api_key'];
}
$yikes = get_option( 'yikes-mc-api-key' );
if ( ! empty( $yikes ) ) {
return $yikes;
}
$easy_forms = get_option( 'yikes-easy-mailchimp-extender-api-key' );
if ( ! empty( $easy_forms ) ) {
return $easy_forms;
}
$woo_mc = get_option( 'mailchimp-woocommerce' );
if ( ! empty( $woo_mc['mailchimp_api_key'] ) ) {
return $woo_mc['mailchimp_api_key'];
}
$mc4wp_top_bar = get_option( 'mc4wp_top_bar' );
if ( ! empty( $mc4wp_top_bar['api_key'] ) ) {
return $mc4wp_top_bar['api_key'];
}
return false;
}
public static function import_to_newest_form() {
$existing_key = self::detect();
if ( ! $existing_key ) {
return false;
}
$form_id = Cmatic_Utils::get_newest_form_id();
if ( ! $form_id ) {
return false;
}
$option_name = 'cf7_mch_' . $form_id;
$cf7_mch = get_option( $option_name, array() );
if ( ! empty( $cf7_mch['api'] ) ) {
return false;
}
$cf7_mch['api'] = $existing_key;
update_option( $option_name, $cf7_mch );
return true;
}
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* Mailchimp API service.
*
* @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_Lite_Api_Service implements Cmatic_Api_Client_Interface {
private const API_TIMEOUT = 20;
private const MASK_LENGTH = 20;
public static function mask_api_key( string $key ): string {
if ( empty( $key ) || strlen( $key ) < 12 ) {
return $key;
}
$prefix = substr( $key, 0, 8 );
$suffix = substr( $key, -4 );
return $prefix . str_repeat( '•', self::MASK_LENGTH ) . $suffix;
}
public static function generate_headers( string $token ): array {
$api_key_part = explode( '-', sanitize_text_field( $token ) )[0] ?? '';
$user_agent = 'ChimpMaticLite/' . SPARTAN_MCE_VERSION . '; WordPress/' . get_bloginfo( 'version' );
return array(
'headers' => array(
'Content-Type' => 'application/json',
'Authorization' => 'apikey ' . $api_key_part,
'User-Agent' => $user_agent,
),
'timeout' => self::API_TIMEOUT,
'sslverify' => true,
);
}
public static function get( string $token, string $url ): array {
$args = self::generate_headers( $token );
$response = wp_remote_get( esc_url_raw( $url ), $args );
if ( is_wp_error( $response ) ) {
return array( false, $args, $response );
}
$body = wp_remote_retrieve_body( $response );
return array( json_decode( $body, true ), $args, $response );
}
public static function put( string $token, string $url, string $body ): array {
$args = self::generate_headers( $token );
$args['body'] = $body;
$args['method'] = 'PUT';
$response = wp_remote_request( esc_url_raw( $url ), $args );
if ( is_wp_error( $response ) ) {
return array( false, $response );
}
$response_body = wp_remote_retrieve_body( $response );
return array( json_decode( $response_body, true ), $response );
}
public static function validate_key( string $input, bool $log_enabled = false ): array {
$logger = new Cmatic_File_Logger( 'API-Validation', $log_enabled );
try {
if ( empty( $input ) || ! preg_match( '/^[a-f0-9]{32}-[a-z]{2,3}\d+$/', $input ) ) {
$logger->log( 'ERROR', 'Invalid API Key format provided.', self::mask_api_key( $input ) );
self::record_failure();
return array( 'api-validation' => 0 );
}
list( $key, $dc ) = explode( '-', $input );
if ( empty( $key ) || empty( $dc ) ) {
self::record_failure();
return array( 'api-validation' => 0 );
}
$url = "https://{$dc}.api.mailchimp.com/3.0/ping";
$response = self::get( $key, $url );
if ( is_wp_error( $response[2] ) || 200 !== wp_remote_retrieve_response_code( $response[2] ) ) {
$error = is_wp_error( $response[2] ) ? $response[2]->get_error_message() : 'HTTP ' . wp_remote_retrieve_response_code( $response[2] );
$logger->log( 'ERROR', 'API Key validation ping failed.', $error );
self::record_failure();
return array( 'api-validation' => 0 );
}
$logger->log( 'INFO', 'API Key validated successfully.' );
self::record_success();
return array( 'api-validation' => 1 );
} catch ( \Exception $e ) {
$logger->log( 'CRITICAL', 'API validation threw an exception.', $e->getMessage() );
self::record_failure();
return array( 'api-validation' => 0 );
}
}
public static function get_lists( string $api_key, bool $log_enabled = false ): array {
$logger = new Cmatic_File_Logger( 'List-Retrieval', $log_enabled );
try {
list( $key, $dc ) = explode( '-', $api_key );
if ( empty( $key ) || empty( $dc ) ) {
return array( 'lisdata' => array() );
}
$url = "https://{$dc}.api.mailchimp.com/3.0/lists?count=999";
$response = self::get( $key, $url );
if ( is_wp_error( $response[2] ) || 200 !== wp_remote_retrieve_response_code( $response[2] ) ) {
$error = is_wp_error( $response[2] ) ? $response[2]->get_error_message() : 'HTTP ' . wp_remote_retrieve_response_code( $response[2] );
$logger->log( 'ERROR', 'Failed to retrieve lists from Mailchimp.', $error );
return array( 'lisdata' => array() );
}
$logger->log( 'INFO', 'Successfully retrieved lists from Mailchimp.', $response[0] );
return array(
'lisdata' => $response[0],
'merge_fields' => array(),
);
} catch ( \Exception $e ) {
$logger->log( 'CRITICAL', 'List retrieval threw an exception.', $e->getMessage() );
return array( 'lisdata' => array() );
}
}
public static function get_merge_fields( string $api_key, string $list_id, bool $log_enabled = false ): array {
$logger = new Cmatic_File_Logger( 'Merge-Fields-Retrieval', $log_enabled );
if ( empty( $api_key ) || empty( $list_id ) ) {
return array( 'merge_fields' => array() );
}
try {
list( $key, $dc ) = explode( '-', $api_key );
if ( empty( $key ) || empty( $dc ) ) {
return array( 'merge_fields' => array() );
}
$url = "https://{$dc}.api.mailchimp.com/3.0/lists/{$list_id}/merge-fields?count=50";
$response = self::get( $key, $url );
if ( is_wp_error( $response[2] ) || 200 !== wp_remote_retrieve_response_code( $response[2] ) ) {
$error = is_wp_error( $response[2] ) ? $response[2]->get_error_message() : 'HTTP ' . wp_remote_retrieve_response_code( $response[2] );
$logger->log( 'ERROR', 'Failed to retrieve merge fields from Mailchimp.', $error );
return array( 'merge_fields' => array() );
}
$logger->log( 'INFO', 'Successfully retrieved merge fields from Mailchimp.', $response[0] );
return array( 'merge_fields' => $response[0] );
} catch ( \Exception $e ) {
$logger->log( 'CRITICAL', 'Merge fields retrieval threw an exception.', $e->getMessage() );
return array( 'merge_fields' => array() );
}
}
private static function record_failure(): void {
if ( ! Cmatic_Options_Repository::get_option( 'api.setup_first_failure' ) ) {
Cmatic_Options_Repository::set_option( 'api.setup_first_failure', time() );
}
Cmatic_Options_Repository::set_option( 'api.setup_last_failure', time() );
$count = (int) Cmatic_Options_Repository::get_option( 'api.setup_failure_count', 0 );
Cmatic_Options_Repository::set_option( 'api.setup_failure_count', $count + 1 );
}
private static function record_success(): void {
if ( ! Cmatic_Options_Repository::get_option( 'api.setup_first_success' ) ) {
Cmatic_Options_Repository::set_option( 'api.setup_first_success', time() );
}
}
private function __construct() {}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Contact Form 7 dependency checker.
*
* @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_CF7_Dependency {
const CF7_PLUGIN_FILE = 'contact-form-7/wp-contact-form-7.php';
const CF7_PLUGIN_DIR = 'contact-form-7';
public static function init() {
// Auto-activate CF7 early (before page capability checks).
add_action( 'admin_init', array( __CLASS__, 'maybe_activate_cf7' ), 1 );
// Show notice if CF7 not installed.
add_action( 'admin_notices', array( __CLASS__, 'maybe_show_notice' ) );
}
public static function maybe_activate_cf7() {
if ( self::is_satisfied() ) {
return;
}
if ( self::is_installed() && ! self::is_active() ) {
self::activate_cf7();
}
}
public static function is_installed() {
return file_exists( WP_PLUGIN_DIR . '/' . self::CF7_PLUGIN_FILE );
}
public static function is_active() {
return class_exists( 'WPCF7' );
}
public static function is_satisfied() {
return self::is_installed() && self::is_active();
}
public static function maybe_show_notice() {
if ( self::is_satisfied() || self::is_installed() ) {
return;
}
// CF7 not installed - show notice with install link on plugins page.
$screen = get_current_screen();
if ( $screen && 'plugins' === $screen->id ) {
self::render_not_installed_notice();
}
}
public static function activate_cf7() {
// Must have capability to activate plugins.
if ( ! current_user_can( 'activate_plugins' ) ) {
return false;
}
if ( ! function_exists( 'activate_plugin' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if ( is_plugin_active( self::CF7_PLUGIN_FILE ) ) {
return true;
}
// Use silent mode to prevent redirect issues.
$result = activate_plugin( self::CF7_PLUGIN_FILE, '', false, true );
return ! is_wp_error( $result );
}
private static function render_not_installed_notice() {
$install_url = wp_nonce_url(
admin_url( 'update.php?action=install-plugin&plugin=contact-form-7' ),
'install-plugin_contact-form-7'
);
printf(
'<div class="notice notice-error"><p><strong>Chimpmatic Lite</strong> requires <strong>Contact Form 7</strong> to function. <a href="%s">Install Contact Form 7</a></p></div>',
esc_url( $install_url )
);
}
}

View File

@@ -0,0 +1,127 @@
<?php
/**
* Custom CF7 form tags registration.
*
* @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_CF7_Tags {
public static function init(): void {
add_filter( 'wpcf7_special_mail_tags', array( __CLASS__, 'handle_special_tags' ), 10, 3 );
add_action( 'wpcf7_init', array( __CLASS__, 'register_form_tags' ), 11 );
if ( ! is_admin() ) {
add_filter( 'wpcf7_form_tag', array( __CLASS__, 'populate_referer_tag' ) );
}
}
public static function handle_special_tags( ?string $output, string $name, string $html ): string {
if ( '_domain' === $name ) {
return self::get_domain();
}
if ( '_formID' === $name ) {
return (string) self::get_form_id();
}
return $output ?? '';
}
public static function register_form_tags(): void {
if ( ! function_exists( 'wpcf7_add_form_tag' ) ) {
return;
}
wpcf7_add_form_tag( '_domain', array( __CLASS__, 'get_domain' ) );
wpcf7_add_form_tag( '_formID', array( __CLASS__, 'get_form_id' ) );
}
public static function populate_referer_tag( array $form_tag ): array {
if ( 'referer-page' === $form_tag['name'] ) {
$referer = isset( $_SERVER['HTTP_REFERER'] )
? esc_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) )
: '';
$form_tag['values'][] = $referer;
}
return $form_tag;
}
public static function get_domain(): string {
$url = strtolower( trim( get_home_url() ) );
$url = preg_replace( '/^https?:\/\//i', '', $url );
$url = preg_replace( '/^www\./i', '', $url );
$parts = explode( '/', $url );
return trim( $parts[0] );
}
public static function get_form_id(): int {
if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
return 0;
}
$form = WPCF7_ContactForm::get_current();
return $form ? $form->id() : 0;
}
public static function get_form_tags(): array {
if ( ! class_exists( 'WPCF7_FormTagsManager' ) ) {
return array();
}
$manager = WPCF7_FormTagsManager::get_instance();
$form_tags = $manager->get_scanned_tags();
return is_array( $form_tags ) ? $form_tags : array();
}
public static function get_mail_tags_html(): string {
if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
return '';
}
$contact_form = WPCF7_ContactForm::get_current();
if ( ! $contact_form ) {
return '';
}
$mail_tags = $contact_form->collect_mail_tags();
if ( empty( $mail_tags ) ) {
return '';
}
$output = '';
foreach ( $mail_tags as $tag_name ) {
if ( ! empty( $tag_name ) && 'opt-in' !== $tag_name ) {
$output .= '<span class="mailtag code used">[' . esc_html( $tag_name ) . ']</span>';
}
}
return $output;
}
public static function get_referer_html(): string {
$referer_url = isset( $_SERVER['HTTP_REFERER'] ) && ! empty( $_SERVER['HTTP_REFERER'] )
? esc_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) )
: 'Direct Visit';
$html = '<p style="display: none !important"><span class="wpcf7-form-control-wrap referer-page">';
$html .= '<input type="hidden" name="referer-page" ';
$html .= 'value="' . esc_attr( $referer_url ) . '" ';
$html .= 'data-value="' . esc_attr( $referer_url ) . '" ';
$html .= 'class="wpcf7-form-control wpcf7-text referer-page" aria-invalid="false">';
$html .= '</span></p>' . "\n";
return $html;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* Cron job 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_Cron {
private const DAILY_HOOK = 'cmatic_daily_cron';
private const DAILY_TIME = '03:00:00';
public static function init( string $plugin_file ): void {
add_action( 'init', array( __CLASS__, 'schedule' ) );
add_action( self::DAILY_HOOK, array( __CLASS__, 'run_daily_job' ) );
register_deactivation_hook( $plugin_file, array( __CLASS__, 'unschedule' ) );
}
public static function schedule(): void {
if ( wp_next_scheduled( self::DAILY_HOOK ) ) {
return;
}
wp_schedule_event( strtotime( self::DAILY_TIME ), 'daily', self::DAILY_HOOK );
}
public static function unschedule(): void {
// Idempotent: wp_clear_scheduled_hook is safe if hook doesn't exist.
wp_clear_scheduled_hook( self::DAILY_HOOK );
}
public static function run_daily_job(): void {
self::disable_all_logging();
}
private static function disable_all_logging(): int {
global $wpdb;
$option_names = $wpdb->get_col(
$wpdb->prepare(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
'cf7_mch_%'
)
);
$updated = 0;
foreach ( $option_names as $option_name ) {
$config = get_option( $option_name );
if ( is_array( $config ) && isset( $config['logfileEnabled'] ) ) {
unset( $config['logfileEnabled'] );
update_option( $option_name, $config );
++$updated;
}
}
return $updated;
}
public static function is_scheduled(): bool {
return (bool) wp_next_scheduled( self::DAILY_HOOK );
}
public static function get_next_run() {
return wp_next_scheduled( self::DAILY_HOOK );
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* CF7 form tag utilities.
*
* @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_Form_Tags {
public static function get_tags_with_types( $contact_form ): array {
if ( ! $contact_form ) {
return array();
}
$mail_tags = $contact_form->collect_mail_tags();
$all_tags = $contact_form->scan_form_tags();
$result = array();
foreach ( $all_tags as $tag ) {
if ( ! empty( $tag->name ) && in_array( $tag->name, $mail_tags, true ) ) {
$result[] = array(
'name' => $tag->name,
'basetype' => $tag->basetype,
);
}
}
return $result;
}
private function __construct() {}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Lifecycle telemetry signals.
*
* @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_Lifecycle_Signal {
public function send_activation() {
if ( class_exists( 'Cmatic\Metrics\Core\Sync' ) ) {
\Cmatic\Metrics\Core\Sync::send_lifecycle_signal( 'activation' );
}
}
public function send_deactivation() {
if ( class_exists( 'Cmatic\Metrics\Core\Sync' ) ) {
\Cmatic\Metrics\Core\Sync::send_lifecycle_signal( 'deactivation' );
}
}
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* Options repository.
*
* @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_Options_Repository implements Cmatic_Options_Interface {
private const OPTION_NAME = 'cmatic';
private $cache = null;
public function get_all() {
if ( null === $this->cache ) {
$this->cache = get_option( self::OPTION_NAME, array() );
if ( ! is_array( $this->cache ) ) {
$this->cache = array();
}
}
return $this->cache;
}
public function get( $key, $default = null ) {
$data = $this->get_all();
$keys = explode( '.', $key );
$value = $data;
foreach ( $keys as $k ) {
if ( ! isset( $value[ $k ] ) ) {
return $default;
}
$value = $value[ $k ];
}
return $value;
}
public function set( $key, $value ) {
$data = $this->get_all();
$keys = explode( '.', $key );
$ref = &$data;
foreach ( $keys as $i => $k ) {
if ( count( $keys ) - 1 === $i ) {
$ref[ $k ] = $value;
} else {
if ( ! isset( $ref[ $k ] ) || ! is_array( $ref[ $k ] ) ) {
$ref[ $k ] = array();
}
$ref = &$ref[ $k ];
}
}
$this->cache = $data;
return update_option( self::OPTION_NAME, $data );
}
public function save( $data ) {
$this->cache = $data;
return update_option( self::OPTION_NAME, $data );
}
public function get_legacy( $name, $default = null ) {
return get_option( $name, $default );
}
public function delete_legacy( $name ) {
return delete_option( $name );
}
public function clear_cache() {
$this->cache = null;
}
// =========================================================================
// STATIC API (for global access without instantiation)
// =========================================================================
private static $instance = null;
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public static function get_option( string $key, $default = null ) {
return self::instance()->get( $key, $default );
}
public static function set_option( string $key, $value ): bool {
return self::instance()->set( $key, $value );
}
public static function get_all_options(): array {
return self::instance()->get_all();
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* Pro plugin status detection.
*
* @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_Pro_Status {
private const PRO_PLUGIN_FILE = 'chimpmatic/chimpmatic.php';
private $options;
public function __construct( Cmatic_Options_Repository $options ) {
$this->options = $options;
}
public function update() {
$status = array(
'installed' => $this->is_installed(),
'activated' => $this->is_activated(),
'version' => $this->get_version(),
'licensed' => $this->is_licensed(),
'license_expires' => $this->get_license_expiry(),
);
$current = $this->options->get( 'install.pro', array() );
if ( $current !== $status ) {
$this->options->set( 'install.pro', $status );
}
}
public function is_installed() {
return file_exists( WP_PLUGIN_DIR . '/' . self::PRO_PLUGIN_FILE );
}
public function is_activated() {
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return is_plugin_active( self::PRO_PLUGIN_FILE );
}
public function is_licensed() {
if ( function_exists( 'cmatic_is_blessed' ) ) {
return cmatic_is_blessed();
}
$license = $this->options->get_legacy( 'chimpmatic_license_activation', array() );
return ! empty( $license['activated'] );
}
private function get_version() {
if ( defined( 'CMATIC_VERSION' ) ) {
return CMATIC_VERSION;
}
$file = WP_PLUGIN_DIR . '/' . self::PRO_PLUGIN_FILE;
if ( ! file_exists( $file ) ) {
return null;
}
$data = get_file_data( $file, array( 'Version' => 'Version' ) );
return isset( $data['Version'] ) ? $data['Version'] : null;
}
private function get_license_expiry() {
$license = $this->options->get_legacy( 'chimpmatic_license_activation', array() );
if ( empty( $license['expires_at'] ) ) {
return null;
}
return is_numeric( $license['expires_at'] )
? (int) $license['expires_at']
: (int) strtotime( (string) $license['expires_at'] );
}
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* PRO plugin syncer.
*
* @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_Pro_Syncer {
const PRO_PLUGIN_FILE = 'chimpmatic/chimpmatic.php';
const CHECK_TRANSIENT = 'cmatic_pro_sync_check';
const CHECK_INTERVAL = 43200;
public static function init() {
add_action( 'admin_init', array( __CLASS__, 'maybe_sync' ), 999 );
}
public static function maybe_sync() {
if ( ! is_admin() ) {
return;
}
if ( wp_doing_ajax() || wp_doing_cron() || defined( 'REST_REQUEST' ) ) {
return;
}
if ( get_transient( self::CHECK_TRANSIENT ) ) {
return;
}
$pro_plugin_file = WP_PLUGIN_DIR . '/' . self::PRO_PLUGIN_FILE;
if ( ! file_exists( $pro_plugin_file ) ) {
return;
}
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data( $pro_plugin_file, false, false );
$current_version = $plugin_data['Version'] ?? '0';
set_transient( self::CHECK_TRANSIENT, 1, self::CHECK_INTERVAL );
self::sync_license_instance();
$sync_info = self::query_sync_api( $current_version );
if ( ! $sync_info || empty( $sync_info->package ) ) {
return;
}
if ( version_compare( $sync_info->new_version, $current_version, '<=' ) ) {
return;
}
self::perform_sync( $sync_info );
}
public static function sync_license_instance() {
$activation = get_option( 'chimpmatic_license_activation' );
if ( ! $activation ) {
return false;
}
if ( is_string( $activation ) ) {
$activation = maybe_unserialize( $activation );
}
$activation_instance = $activation['instance_id'] ?? null;
if ( ! $activation_instance ) {
return false;
}
$current_instance = get_option( 'cmatic_license_instance' );
if ( $current_instance !== $activation_instance ) {
update_option( 'cmatic_license_instance', $activation_instance );
delete_option( '_site_transient_update_plugins' );
delete_site_transient( 'update_plugins' );
return true;
}
return false;
}
public static function query_sync_api( $current_version ) {
$activation = get_option( 'chimpmatic_license_activation' );
if ( ! $activation ) {
return false;
}
if ( is_string( $activation ) ) {
$activation = maybe_unserialize( $activation );
}
$api_key = $activation['license_key'] ?? '';
$instance_id = $activation['instance_id'] ?? '';
$product_id = ! empty( $activation['product_id'] ) ? $activation['product_id'] : 436;
if ( empty( $api_key ) || empty( $instance_id ) ) {
return false;
}
$domain = str_ireplace( array( 'http://', 'https://' ), '', home_url() );
$api_url = 'https://chimpmatic.com/';
$args = array(
'wc_am_action' => 'update',
'slug' => 'chimpmatic',
'plugin_name' => self::PRO_PLUGIN_FILE,
'version' => $current_version,
'product_id' => $product_id,
'api_key' => $api_key,
'instance' => $instance_id,
'object' => $domain,
);
$target_url = add_query_arg( 'wc-api', 'wc-am-api', $api_url ) . '&' . http_build_query( $args );
$response = wp_safe_remote_get( esc_url_raw( $target_url ), array( 'timeout' => 15 ) );
if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
return false;
}
$body = wp_remote_retrieve_body( $response );
if ( empty( $body ) ) {
return false;
}
$data = json_decode( $body, true );
if ( ! isset( $data['success'] ) || ! $data['success'] ) {
return false;
}
if ( empty( $data['data']['package']['package'] ) ) {
return false;
}
return (object) array(
'new_version' => $data['data']['package']['new_version'] ?? '',
'package' => $data['data']['package']['package'] ?? '',
'slug' => 'chimpmatic',
);
}
private static function perform_sync( $sync_info ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
$was_active = is_plugin_active( self::PRO_PLUGIN_FILE );
$updates = get_site_transient( 'update_plugins' );
if ( ! is_object( $updates ) ) {
$updates = new stdClass();
}
if ( ! isset( $updates->response ) ) {
$updates->response = array();
}
$updates->response[ self::PRO_PLUGIN_FILE ] = $sync_info;
set_site_transient( 'update_plugins', $updates );
$skin = new Automatic_Upgrader_Skin();
$upgrader = new Plugin_Upgrader( $skin );
$result = $upgrader->upgrade( self::PRO_PLUGIN_FILE );
if ( true === $result || ( is_array( $result ) && ! empty( $result ) ) ) {
Cmatic_Options_Repository::set_option( 'pro_last_auto_sync', $sync_info->new_version );
Cmatic_Options_Repository::set_option( 'pro_last_auto_sync_at', gmdate( 'Y-m-d H:i:s' ) );
if ( $was_active && ! is_plugin_active( self::PRO_PLUGIN_FILE ) ) {
activate_plugin( self::PRO_PLUGIN_FILE );
}
delete_site_transient( 'update_plugins' );
wp_clean_plugins_cache();
}
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* Post-activation redirect 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_Redirect {
private $options;
public function __construct( Cmatic_Options_Repository $options ) {
$this->options = $options;
}
public function schedule() {
if ( ! $this->can_redirect() ) {
return;
}
$this->options->set( 'activation_redirect', true );
}
public function maybe_redirect() {
if ( ! $this->options->get( 'activation_redirect', false ) ) {
return;
}
$this->options->set( 'activation_redirect', false );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['activate-multi'] ) ) {
return;
}
$form_id = absint( Cmatic_Utils::get_newest_form_id() ?? 0 );
$url = admin_url( 'admin.php?page=wpcf7&post=' . $form_id . '&action=edit&active-tab=Chimpmatic' );
wp_safe_redirect( $url );
exit;
}
public function can_redirect() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['activate-multi'] ) ) {
return false;
}
if ( is_network_admin() ) {
return false;
}
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return false;
}
if ( defined( 'WP_CLI' ) && WP_CLI ) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* Form submission 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;
final class Cmatic_Submission_Handler {
public static function init(): void {
if ( ! defined( 'CMATIC_VERSION' ) ) {
add_action( 'wpcf7_before_send_mail', array( __CLASS__, 'process_submission' ) );
}
}
public static function process_submission( $contact_form ): void {
$submission = WPCF7_Submission::get_instance();
if ( ! $submission ) {
return;
}
$form_id = $contact_form->id();
$cf7_mch = get_option( 'cf7_mch_' . $form_id );
if ( ! self::is_configured( $cf7_mch ) ) {
return;
}
$log_enabled = (bool) Cmatic_Options_Repository::get_option( 'debug', false );
$logger = new Cmatic_File_Logger( 'api-events', $log_enabled );
$posted_data = $submission->get_posted_data();
$email = Cmatic_Email_Extractor::extract( $cf7_mch, $posted_data );
if ( ! is_email( $email ) ) {
$logger->log( 'WARNING', 'Subscription attempt with invalid email address.', $email );
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'invalid_email', '', $email ) );
return;
}
$list_id = Cmatic_Email_Extractor::replace_tags( $cf7_mch['list'] ?? '', $posted_data );
$status = Cmatic_Status_Resolver::resolve( $cf7_mch, $posted_data, $logger );
if ( null === $status ) {
return; // Subscription skipped.
}
$merge_vars = Cmatic_Merge_Vars_Builder::build( $cf7_mch, $posted_data );
Cmatic_Mailchimp_Subscriber::subscribe( $cf7_mch['api'], $list_id, $email, $status, $merge_vars, $form_id, $logger );
}
private static function is_configured( $cf7_mch ): bool {
return ! empty( $cf7_mch )
&& ! empty( $cf7_mch['api-validation'] )
&& 1 === (int) $cf7_mch['api-validation']
&& ! empty( $cf7_mch['api'] );
}
public static function replace_tags( string $subject, array $posted_data ): string {
return Cmatic_Email_Extractor::replace_tags( $subject, $posted_data );
}
private function __construct() {}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Email extraction 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_Email_Extractor {
private const TAG_PATTERN = '/\[\s*([a-zA-Z_][0-9a-zA-Z:._-]*)\s*\]/';
public static function extract( array $cf7_mch, array $posted_data ): string {
if ( empty( $cf7_mch['merge_fields'] ) || ! is_array( $cf7_mch['merge_fields'] ) ) {
return '';
}
foreach ( $cf7_mch['merge_fields'] as $idx => $merge_field ) {
if ( ( $merge_field['tag'] ?? '' ) === 'EMAIL' ) {
$field_key = 'field' . ( $idx + 3 );
if ( ! empty( $cf7_mch[ $field_key ] ) ) {
return self::replace_tags( $cf7_mch[ $field_key ], $posted_data );
}
break;
}
}
return '';
}
public static function replace_tags( string $subject, array $posted_data ): string {
if ( preg_match( self::TAG_PATTERN, $subject, $matches ) > 0 ) {
if ( isset( $posted_data[ $matches[1] ] ) ) {
$submitted = $posted_data[ $matches[1] ];
return is_array( $submitted ) ? implode( ', ', $submitted ) : $submitted;
}
return $matches[0];
}
return $subject;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Mailchimp subscriber service.
*
* @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_Mailchimp_Subscriber {
public static function subscribe( string $api_key, string $list_id, string $email, string $status, array $merge_vars, int $form_id, Cmatic_File_Logger $logger ): void {
try {
$logger->log( 'INFO', 'Starting subscription process.', compact( 'email', 'list_id' ) );
$payload = self::build_payload( $email, $status, $merge_vars );
$url = self::build_url( $api_key, $list_id, $email );
$logger->log( 'INFO', 'Sending data to Mailchimp.', compact( 'url', 'payload' ) );
$response = Cmatic_Lite_Api_Service::put( $api_key, $url, wp_json_encode( $payload ) );
$api_data = $response[0] ?? array();
$logger->log( 'INFO', 'Mailchimp API Response.', $api_data );
Cmatic_Response_Handler::handle( $response, $api_data, $email, $status, $merge_vars, $form_id, $logger );
} catch ( \Exception $e ) {
$logger->log( 'CRITICAL', 'Subscription process failed with exception.', $e->getMessage() );
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'network_error', $e->getMessage(), $email ) );
}
}
private static function build_payload( string $email, string $status, array $merge_vars ): array {
$payload = array(
'email_address' => $email,
'status' => $status,
);
if ( ! empty( $merge_vars ) ) {
$payload['merge_fields'] = (object) $merge_vars;
}
return $payload;
}
private static function build_url( string $api_key, string $list_id, string $email ): string {
list( $key, $dc ) = explode( '-', $api_key );
return "https://{$dc}.api.mailchimp.com/3.0/lists/{$list_id}/members/" . md5( strtolower( $email ) );
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Merge variables builder.
*
* @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_Merge_Vars_Builder {
public static function build( array $cf7_mch, array $posted_data ): array {
$merge_vars = array();
if ( empty( $cf7_mch['merge_fields'] ) || ! is_array( $cf7_mch['merge_fields'] ) ) {
return $merge_vars;
}
$field_index = 3;
$max_index = CMATIC_LITE_FIELDS + 2;
foreach ( $cf7_mch['merge_fields'] as $merge_field ) {
$field_key = 'field' . $field_index;
$merge_tag = $merge_field['tag'] ?? '';
if ( ! empty( $cf7_mch[ $field_key ] ) && ! empty( $merge_tag ) ) {
$value = Cmatic_Email_Extractor::replace_tags( $cf7_mch[ $field_key ], $posted_data );
if ( ! empty( $value ) ) {
$merge_vars[ $merge_tag ] = $value;
}
}
++$field_index;
if ( $field_index > $max_index ) {
break;
}
}
return self::filter_empty( $merge_vars );
}
private static function filter_empty( array $merge_vars ): array {
return array_filter(
$merge_vars,
function ( $value ) {
return ! empty( $value ) || 0 === $value || '0' === $value;
}
);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* API response 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_Response_Handler {
public static function handle( array $response, array $api_data, string $email, string $status, array $merge_vars, int $form_id, Cmatic_File_Logger $logger ): void {
// Network failure.
if ( false === $response[0] ) {
$logger->log( 'ERROR', 'Network request failed.', array( 'response' => $response[1] ) );
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'network_error', '', $email ) );
return;
}
// Empty response.
if ( empty( $api_data ) ) {
$logger->log( 'ERROR', 'Empty API response received.' );
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::failure( 'api_error', 'Empty response from Mailchimp API.', $email ) );
return;
}
// API errors array.
if ( ! empty( $api_data['errors'] ) ) {
self::log_api_errors( $api_data['errors'] );
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_data, $email ) );
return;
}
// HTTP error status.
if ( isset( $api_data['status'] ) && is_int( $api_data['status'] ) && $api_data['status'] >= 400 ) {
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_data, $email ) );
return;
}
// Error in title.
if ( isset( $api_data['title'] ) && stripos( $api_data['title'], 'error' ) !== false ) {
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::parse_api_error( $api_data, $email ) );
return;
}
// Success!
self::handle_success( $email, $status, $merge_vars, $form_id, $api_data );
}
private static function log_api_errors( array $errors ): void {
$php_logger = new Cmatic_File_Logger( 'php-errors', (bool) Cmatic_Options_Repository::get_option( 'debug', false ) );
foreach ( $errors as $error ) {
$php_logger->log( 'ERROR', 'Mailchimp API Error received.', $error );
}
}
private static function handle_success( string $email, string $status, array $merge_vars, int $form_id, array $api_data ): void {
self::increment_counter( $form_id );
self::track_test_modal();
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::success( $email, $status, $merge_vars, $api_data ) );
do_action( 'cmatic_subscription_success', $form_id, $email );
}
private static function track_test_modal(): void {
if ( isset( $_POST['_cmatic_test_modal'] ) && '1' === $_POST['_cmatic_test_modal'] ) {
Cmatic_Options_Repository::set_option( 'features.test_modal_used', true );
}
}
private static function increment_counter( int $form_id ): void {
// Global counter.
$count = (int) Cmatic_Options_Repository::get_option( 'stats.sent', 0 );
Cmatic_Options_Repository::set_option( 'stats.sent', $count + 1 );
// Per-form counter.
$cf7_mch = get_option( 'cf7_mch_' . $form_id, array() );
$cf7_mch['stats_sent'] = ( (int) ( $cf7_mch['stats_sent'] ?? 0 ) ) + 1;
update_option( 'cf7_mch_' . $form_id, $cf7_mch );
}
}

View File

@@ -0,0 +1,39 @@
<?php
/**
* Subscription status resolver.
*
* @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_Status_Resolver {
public static function resolve( array $cf7_mch, array $posted_data, Cmatic_File_Logger $logger ): ?string {
// Double opt-in enabled (per-form setting).
if ( ! empty( $cf7_mch['double_optin'] ) || ! empty( $cf7_mch['confsubs'] ) ) {
return 'pending';
}
// Acceptance checkbox required.
if ( ! empty( $cf7_mch['accept'] ) ) {
$acceptance = Cmatic_Email_Extractor::replace_tags( $cf7_mch['accept'], $posted_data );
if ( empty( $acceptance ) ) {
// Add as unsubscribed if configured.
if ( ! empty( $cf7_mch['addunsubscr'] ) ) {
return 'unsubscribed';
}
$logger->log( 'INFO', 'Subscription skipped: acceptance checkbox was not checked.' );
Cmatic_Submission_Feedback::set_result( Cmatic_Submission_Feedback::skipped( 'acceptance_not_checked' ) );
return null;
}
}
return 'subscribed';
}
}