forked from LiveCarta/LiveCartaWP
Changed source root directory
This commit is contained in:
2
html/wp-content/index.php
Normal file
2
html/wp-content/index.php
Normal file
@@ -0,0 +1,2 @@
|
||||
<?php
|
||||
// Silence is golden.
|
||||
840
html/wp-content/mu-plugins/domain_mapping.php
Normal file
840
html/wp-content/mu-plugins/domain_mapping.php
Normal file
@@ -0,0 +1,840 @@
|
||||
<?php
|
||||
/*
|
||||
Plugin Name: WordPress MU Domain Mapping
|
||||
Plugin URI: http://ocaoimh.ie/wordpress-mu-domain-mapping/
|
||||
Description: Map any blog on a WordPress website to another domain.
|
||||
Version: 0.5.5.1
|
||||
Author: Donncha O Caoimh
|
||||
Author URI: http://ocaoimh.ie/
|
||||
*/
|
||||
/* Copyright Donncha O Caoimh (http://ocaoimh.ie/)
|
||||
With contributions by Ron Rennick(http://wpmututorials.com/), Greg Sidberry and others.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
function dm_text_domain() {
|
||||
load_plugin_textdomain( 'wordpress-mu-domain-mapping', basename( dirname( __FILE__ ) ) . 'languages', 'wordpress-mu-domain-mapping/languages' );
|
||||
}
|
||||
add_action( 'init', 'dm_text_domain' );
|
||||
|
||||
function domain_mapping_warning() {
|
||||
echo "<div id='domain-mapping-warning' class='updated fade'><p><strong>".__( 'Domain Mapping Disabled.', 'wordpress-mu-domain-mapping' )."</strong> ".sprintf(__('You must <a href="%1$s">create a network</a> for it to work.', 'wordpress-mu-domain-mapping' ), "http://codex.wordpress.org/Create_A_Network")."</p></div>";
|
||||
}
|
||||
|
||||
function dm_add_pages() {
|
||||
global $current_site, $wpdb, $wp_db_version, $wp_version;
|
||||
|
||||
if ( !isset( $current_site ) && $wp_db_version >= 15260 ) { // WP 3.0 network hasn't been configured
|
||||
add_action('admin_notices', 'domain_mapping_warning');
|
||||
return false;
|
||||
}
|
||||
if ( $current_site->path != "/" ) {
|
||||
wp_die( __( "The domain mapping plugin only works if the site is installed in /. This is a limitation of how virtual servers work and is very difficult to work around.", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
|
||||
if ( get_site_option( 'dm_user_settings' ) && $current_site->blog_id != $wpdb->blogid && !dm_sunrise_warning( false ) ) {
|
||||
add_management_page(__( 'Domain Mapping', 'wordpress-mu-domain-mapping'), __( 'Domain Mapping', 'wordpress-mu-domain-mapping'), 'manage_options', 'domainmapping', 'dm_manage_page' );
|
||||
}
|
||||
|
||||
}
|
||||
add_action( 'admin_menu', 'dm_add_pages' );
|
||||
|
||||
function dm_network_pages() {
|
||||
add_submenu_page('settings.php', 'Domain Mapping', 'Domain Mapping', 'manage_options', 'dm_admin_page', 'dm_admin_page');
|
||||
add_submenu_page('settings.php', 'Domains', 'Domains', 'manage_options', 'dm_domains_admin', 'dm_domains_admin');
|
||||
}
|
||||
add_action( 'network_admin_menu', 'dm_network_pages' );
|
||||
|
||||
// Default Messages for the users Domain Mapping management page
|
||||
// This can now be replaced by using:
|
||||
// remove_action('dm_echo_updated_msg','dm_echo_default_updated_msg');
|
||||
// add_action('dm_echo_updated_msg','my_custom_updated_msg_function');
|
||||
function dm_echo_default_updated_msg() {
|
||||
switch( $_GET[ 'updated' ] ) {
|
||||
case "add":
|
||||
$msg = __( 'New domain added.', 'wordpress-mu-domain-mapping' );
|
||||
break;
|
||||
case "exists":
|
||||
$msg = __( 'New domain already exists.', 'wordpress-mu-domain-mapping' );
|
||||
break;
|
||||
case "primary":
|
||||
$msg = __( 'New primary domain.', 'wordpress-mu-domain-mapping' );
|
||||
break;
|
||||
case "del":
|
||||
$msg = __( 'Domain deleted.', 'wordpress-mu-domain-mapping' );
|
||||
break;
|
||||
}
|
||||
echo "<div class='updated fade'><p>$msg</p></div>";
|
||||
}
|
||||
add_action('dm_echo_updated_msg','dm_echo_default_updated_msg');
|
||||
|
||||
function maybe_create_db() {
|
||||
global $wpdb;
|
||||
|
||||
get_dm_hash(); // initialise the remote login hash
|
||||
|
||||
$wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
|
||||
$wpdb->dmtablelogins = $wpdb->base_prefix . 'domain_mapping_logins';
|
||||
if ( dm_site_admin() ) {
|
||||
$created = 0;
|
||||
if ( $wpdb->get_var("SHOW TABLES LIKE '{$wpdb->dmtable}'") != $wpdb->dmtable ) {
|
||||
$wpdb->query( "CREATE TABLE IF NOT EXISTS `{$wpdb->dmtable}` (
|
||||
`id` bigint(20) NOT NULL auto_increment,
|
||||
`blog_id` bigint(20) NOT NULL,
|
||||
`domain` varchar(255) NOT NULL,
|
||||
`active` tinyint(4) default '1',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `blog_id` (`blog_id`,`domain`,`active`)
|
||||
);" );
|
||||
$created = 1;
|
||||
}
|
||||
if ( $wpdb->get_var("SHOW TABLES LIKE '{$wpdb->dmtablelogins}'") != $wpdb->dmtablelogins ) {
|
||||
$wpdb->query( "CREATE TABLE IF NOT EXISTS `{$wpdb->dmtablelogins}` (
|
||||
`id` varchar(32) NOT NULL,
|
||||
`user_id` bigint(20) NOT NULL,
|
||||
`blog_id` bigint(20) NOT NULL,
|
||||
`t` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`)
|
||||
);" );
|
||||
$created = 1;
|
||||
}
|
||||
if ( $created ) {
|
||||
?> <div id="message" class="updated fade"><p><strong><?php _e( 'Domain mapping database table created.', 'wordpress-mu-domain-mapping' ) ?></strong></p></div> <?php
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function dm_domains_admin() {
|
||||
global $wpdb, $current_site;
|
||||
if ( false == dm_site_admin() ) { // paranoid? moi?
|
||||
return false;
|
||||
}
|
||||
|
||||
dm_sunrise_warning();
|
||||
|
||||
if ( $current_site->path != "/" ) {
|
||||
wp_die( sprintf( __( "<strong>Warning!</strong> This plugin will only work if WordPress is installed in the root directory of your webserver. It is currently installed in ’%s’.", "wordpress-mu-domain-mapping" ), $current_site->path ) );
|
||||
}
|
||||
|
||||
echo '<h2>' . __( 'Domain Mapping: Domains', 'wordpress-mu-domain-mapping' ) . '</h2>';
|
||||
if ( !empty( $_POST[ 'action' ] ) ) {
|
||||
check_admin_referer( 'domain_mapping' );
|
||||
$domain = strtolower( $_POST[ 'domain' ] );
|
||||
switch( $_POST[ 'action' ] ) {
|
||||
case "edit":
|
||||
$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->dmtable} WHERE domain = %s", $domain ) );
|
||||
if ( $row ) {
|
||||
dm_edit_domain( $row );
|
||||
} else {
|
||||
echo "<h3>" . __( 'Domain not found', 'wordpress-mu-domain-mapping' ) . "</h3>";
|
||||
}
|
||||
break;
|
||||
case "save":
|
||||
if ( $_POST[ 'blog_id' ] != 0 AND
|
||||
$_POST[ 'blog_id' ] != 1 AND
|
||||
null == $wpdb->get_var( $wpdb->prepare( "SELECT domain FROM {$wpdb->dmtable} WHERE blog_id != %d AND domain = %s", $_POST[ 'blog_id' ], $domain ) )
|
||||
) {
|
||||
if ( $_POST[ 'orig_domain' ] == '' ) {
|
||||
$wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->dmtable} ( `blog_id`, `domain`, `active` ) VALUES ( %d, %s, %d )", $_POST[ 'blog_id' ], $domain, $_POST[ 'active' ] ) );
|
||||
echo "<p><strong>" . __( 'Domain Add', 'wordpress-mu-domain-mapping' ) . "</strong></p>";
|
||||
} else {
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->dmtable} SET blog_id = %d, domain = %s, active = %d WHERE domain = %s", $_POST[ 'blog_id' ], $domain, $_POST[ 'active' ], $_POST[ 'orig_domain' ] ) );
|
||||
echo "<p><strong>" . __( 'Domain Updated', 'wordpress-mu-domain-mapping' ) . "</strong></p>";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "del":
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->dmtable} WHERE domain = %s", $domain ) );
|
||||
echo "<p><strong>" . __( 'Domain Deleted', 'wordpress-mu-domain-mapping' ) . "</strong></p>";
|
||||
break;
|
||||
case "search":
|
||||
$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->dmtable} WHERE domain LIKE %s", $domain ) );
|
||||
dm_domain_listing( $rows, sprintf( __( "Searching for %s", 'wordpress-mu-domain-mapping' ), esc_html( $domain ) ) );
|
||||
break;
|
||||
}
|
||||
if ( $_POST[ 'action' ] == 'update' ) {
|
||||
if ( preg_match( '|^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$|', $_POST[ 'ipaddress' ] ) )
|
||||
update_site_option( 'dm_ipaddress', $_POST[ 'ipaddress' ] );
|
||||
|
||||
if ( ! preg_match( '/(--|\.\.)/', $_POST[ 'cname' ] ) && preg_match( '|^([a-zA-Z0-9-\.])+$|', $_POST[ 'cname' ] ) )
|
||||
update_site_option( 'dm_cname', stripslashes( $_POST[ 'cname' ] ) );
|
||||
else
|
||||
update_site_option( 'dm_cname', '' );
|
||||
|
||||
update_site_option( 'dm_301_redirect', intval( $_POST[ 'permanent_redirect' ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
echo "<h3>" . __( 'Search Domains', 'wordpress-mu-domain-mapping' ) . "</h3>";
|
||||
echo '<form method="POST">';
|
||||
wp_nonce_field( 'domain_mapping' );
|
||||
echo '<input type="hidden" name="action" value="search" />';
|
||||
echo '<p>';
|
||||
echo _e( "Domain:", 'wordpress-mu-domain-mapping' );
|
||||
echo " <input type='text' name='domain' value='' /></p>";
|
||||
echo "<p><input type='submit' class='button-secondary' value='" . __( 'Search', 'wordpress-mu-domain-mapping' ) . "' /></p>";
|
||||
echo "</form><br />";
|
||||
dm_edit_domain();
|
||||
$rows = $wpdb->get_results( "SELECT * FROM {$wpdb->dmtable} ORDER BY id DESC LIMIT 0,20" );
|
||||
dm_domain_listing( $rows );
|
||||
echo '<p>' . sprintf( __( '<strong>Note:</strong> %s', 'wordpress-mu-domain-mapping' ), dm_idn_warning() ) . "</p>";
|
||||
}
|
||||
|
||||
function dm_edit_domain( $row = false ) {
|
||||
if ( is_object( $row ) ) {
|
||||
echo "<h3>" . __( 'Edit Domain', 'wordpress-mu-domain-mapping' ) . "</h3>";
|
||||
} else {
|
||||
echo "<h3>" . __( 'New Domain', 'wordpress-mu-domain-mapping' ) . "</h3>";
|
||||
$row = new stdClass();
|
||||
$row->blog_id = '';
|
||||
$row->domain = '';
|
||||
$_POST[ 'domain' ] = '';
|
||||
$row->active = 1;
|
||||
}
|
||||
|
||||
echo "<form method='POST'><input type='hidden' name='action' value='save' /><input type='hidden' name='orig_domain' value='" . esc_attr( $_POST[ 'domain' ] ) . "' />";
|
||||
wp_nonce_field( 'domain_mapping' );
|
||||
echo "<table class='form-table'>\n";
|
||||
echo "<tr><th>" . __( 'Site ID', 'wordpress-mu-domain-mapping' ) . "</th><td><input type='text' name='blog_id' value='{$row->blog_id}' /></td></tr>\n";
|
||||
echo "<tr><th>" . __( 'Domain', 'wordpress-mu-domain-mapping' ) . "</th><td><input type='text' name='domain' value='{$row->domain}' /></td></tr>\n";
|
||||
echo "<tr><th>" . __( 'Primary', 'wordpress-mu-domain-mapping' ) . "</th><td><input type='checkbox' name='active' value='1' ";
|
||||
echo $row->active == 1 ? 'checked=1 ' : ' ';
|
||||
echo "/></td></tr>\n";
|
||||
if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) {
|
||||
echo "<tr><td colspan='2'>" . __( '<strong>Warning!</strong> Primary domains are currently disabled.', 'wordpress-mu-domain-mapping' ) . "</td></tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
echo "<p><input type='submit' class='button-primary' value='" .__( 'Save', 'wordpress-mu-domain-mapping' ). "' /></p></form><br /><br />";
|
||||
}
|
||||
|
||||
function dm_domain_listing( $rows, $heading = '' ) {
|
||||
if ( $rows ) {
|
||||
if ( file_exists( ABSPATH . 'wp-admin/network/site-info.php' ) ) {
|
||||
$edit_url = network_admin_url( 'site-info.php' );
|
||||
} elseif ( file_exists( ABSPATH . 'wp-admin/ms-sites.php' ) ) {
|
||||
$edit_url = admin_url( 'ms-sites.php' );
|
||||
} else {
|
||||
$edit_url = admin_url( 'wpmu-blogs.php' );
|
||||
}
|
||||
if ( $heading != '' )
|
||||
echo "<h3>$heading</h3>";
|
||||
echo '<table class="widefat" cellspacing="0"><thead><tr><th>'.__( 'Site ID', 'wordpress-mu-domain-mapping' ).'</th><th>'.__( 'Domain', 'wordpress-mu-domain-mapping' ).'</th><th>'.__( 'Primary', 'wordpress-mu-domain-mapping' ).'</th><th>'.__( 'Edit', 'wordpress-mu-domain-mapping' ).'</th><th>'.__( 'Delete', 'wordpress-mu-domain-mapping' ).'</th></tr></thead><tbody>';
|
||||
foreach( $rows as $row ) {
|
||||
echo "<tr><td><a href='" . add_query_arg( array( 'action' => 'editblog', 'id' => $row->blog_id ), $edit_url ) . "'>{$row->blog_id}</a></td><td><a href='http://{$row->domain}/'>{$row->domain}</a></td><td>";
|
||||
echo $row->active == 1 ? __( 'Yes', 'wordpress-mu-domain-mapping' ) : __( 'No', 'wordpress-mu-domain-mapping' );
|
||||
echo "</td><td><form method='POST'><input type='hidden' name='action' value='edit' /><input type='hidden' name='domain' value='{$row->domain}' />";
|
||||
wp_nonce_field( 'domain_mapping' );
|
||||
echo "<input type='submit' class='button-secondary' value='" .__( 'Edit', 'wordpress-mu-domain-mapping' ). "' /></form></td><td><form method='POST'><input type='hidden' name='action' value='del' /><input type='hidden' name='domain' value='{$row->domain}' />";
|
||||
wp_nonce_field( 'domain_mapping' );
|
||||
echo "<input type='submit' class='button-secondary' value='" .__( 'Del', 'wordpress-mu-domain-mapping' ). "' /></form>";
|
||||
echo "</td></tr>";
|
||||
}
|
||||
echo '</table>';
|
||||
if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) {
|
||||
echo "<p>" . __( '<strong>Warning!</strong> Primary domains are currently disabled.', 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dm_admin_page() {
|
||||
global $wpdb, $current_site;
|
||||
if ( false == dm_site_admin() ) { // paranoid? moi?
|
||||
return false;
|
||||
}
|
||||
|
||||
dm_sunrise_warning();
|
||||
maybe_create_db();
|
||||
|
||||
if ( $current_site->path != "/" ) {
|
||||
wp_die( sprintf( __( "<strong>Warning!</strong> This plugin will only work if WordPress is installed in the root directory of your webserver. It is currently installed in ’%s’.", "wordpress-mu-domain-mapping" ), $current_site->path ) );
|
||||
}
|
||||
|
||||
// set up some defaults
|
||||
if ( get_site_option( 'dm_remote_login', 'NA' ) == 'NA' )
|
||||
add_site_option( 'dm_remote_login', 1 );
|
||||
if ( get_site_option( 'dm_redirect_admin', 'NA' ) == 'NA' )
|
||||
add_site_option( 'dm_redirect_admin', 1 );
|
||||
if ( get_site_option( 'dm_user_settings', 'NA' ) == 'NA' )
|
||||
add_site_option( 'dm_user_settings', 1 );
|
||||
|
||||
if ( !empty( $_POST[ 'action' ] ) ) {
|
||||
check_admin_referer( 'domain_mapping' );
|
||||
if ( $_POST[ 'action' ] == 'update' ) {
|
||||
$ipok = true;
|
||||
$ipaddresses = explode( ',', $_POST[ 'ipaddress' ] );
|
||||
foreach( $ipaddresses as $address ) {
|
||||
if ( ( $ip = trim( $address ) ) && !preg_match( '|^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$|', $ip ) ) {
|
||||
$ipok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( $ipok )
|
||||
update_site_option( 'dm_ipaddress', $_POST[ 'ipaddress' ] );
|
||||
if ( intval( $_POST[ 'always_redirect_admin' ] ) == 0 )
|
||||
$_POST[ 'dm_remote_login' ] = 0; // disable remote login if redirecting to mapped domain
|
||||
update_site_option( 'dm_remote_login', intval( $_POST[ 'dm_remote_login' ] ) );
|
||||
if ( ! preg_match( '/(--|\.\.)/', $_POST[ 'cname' ] ) && preg_match( '|^([a-zA-Z0-9-\.])+$|', $_POST[ 'cname' ] ) )
|
||||
update_site_option( 'dm_cname', stripslashes( $_POST[ 'cname' ] ) );
|
||||
else
|
||||
update_site_option( 'dm_cname', '' );
|
||||
update_site_option( 'dm_301_redirect', isset( $_POST[ 'permanent_redirect' ] ) ? intval( $_POST[ 'permanent_redirect' ] ) : 0 );
|
||||
update_site_option( 'dm_redirect_admin', isset( $_POST[ 'always_redirect_admin' ] ) ? intval( $_POST[ 'always_redirect_admin' ] ) : 0 );
|
||||
update_site_option( 'dm_user_settings', isset( $_POST[ 'dm_user_settings' ] ) ? intval( $_POST[ 'dm_user_settings' ] ) : 0 );
|
||||
update_site_option( 'dm_no_primary_domain', isset( $_POST[ 'dm_no_primary_domain' ] ) ? intval( $_POST[ 'dm_no_primary_domain' ] ) : 0 );
|
||||
}
|
||||
}
|
||||
|
||||
echo '<h3>' . __( 'Domain Mapping Configuration', 'wordpress-mu-domain-mapping' ) . '</h3>';
|
||||
echo '<form method="POST">';
|
||||
echo '<input type="hidden" name="action" value="update" />';
|
||||
echo "<p>" . __( "As a super admin on this network you can set the IP address users need to point their DNS A records at <em>or</em> the domain to point CNAME record at. If you don't know what the IP address is, ping this blog to get it.", 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
echo "<p>" . __( "If you use round robin DNS or another load balancing technique with more than one IP, enter each address, separating them by commas.", 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
_e( "Server IP Address: ", 'wordpress-mu-domain-mapping' );
|
||||
echo "<input type='text' name='ipaddress' value='" . get_site_option( 'dm_ipaddress' ) . "' /><br />";
|
||||
|
||||
// Using a CNAME is a safer method than using IP adresses for some people (IMHO)
|
||||
echo "<p>" . __( "If you prefer the use of a CNAME record, you can set the domain here. This domain must be configured with an A record or ANAME pointing at an IP address. Visitors may experience problems if it is a CNAME of another domain.", 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
echo "<p>" . __( "NOTE, this voids the use of any IP address set above", 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
_e( "Server CNAME domain: ", 'wordpress-mu-domain-mapping' );
|
||||
echo "<input type='text' name='cname' value='" . get_site_option( 'dm_cname' ) . "' /> (" . dm_idn_warning() . ")<br />";
|
||||
echo '<p>' . __( 'The information you enter here will be shown to your users so they can configure their DNS correctly. It is for informational purposes only', 'wordpress-mu-domain-mapping' ) . '</p>';
|
||||
|
||||
echo "<h3>" . __( 'Domain Options', 'wordpress-mu-domain-mapping' ) . "</h3>";
|
||||
echo "<ol><li><input type='checkbox' name='dm_remote_login' value='1' ";
|
||||
echo get_site_option( 'dm_remote_login' ) == 1 ? "checked='checked'" : "";
|
||||
echo " /> " . __( 'Remote Login', 'wordpress-mu-domain-mapping' ) . "</li>";
|
||||
echo "<li><input type='checkbox' name='permanent_redirect' value='1' ";
|
||||
echo get_site_option( 'dm_301_redirect' ) == 1 ? "checked='checked'" : "";
|
||||
echo " /> " . __( "Permanent redirect (better for your blogger's pagerank)", 'wordpress-mu-domain-mapping' ) . "</li>";
|
||||
echo "<li><input type='checkbox' name='dm_user_settings' value='1' ";
|
||||
echo get_site_option( 'dm_user_settings' ) == 1 ? "checked='checked'" : "";
|
||||
echo " /> " . __( 'User domain mapping page', 'wordpress-mu-domain-mapping' ) . "</li> ";
|
||||
echo "<li><input type='checkbox' name='always_redirect_admin' value='1' ";
|
||||
echo get_site_option( 'dm_redirect_admin' ) == 1 ? "checked='checked'" : "";
|
||||
echo " /> " . __( "Redirect administration pages to site's original domain (remote login disabled if this redirect is disabled)", 'wordpress-mu-domain-mapping' ) . "</li>";
|
||||
echo "<li><input type='checkbox' name='dm_no_primary_domain' value='1' ";
|
||||
echo get_site_option( 'dm_no_primary_domain' ) == 1 ? "checked='checked'" : "";
|
||||
echo " /> " . __( "Disable primary domain check. Sites will not redirect to one domain name. May cause duplicate content issues.", 'wordpress-mu-domain-mapping' ) . "</li></ol>";
|
||||
wp_nonce_field( 'domain_mapping' );
|
||||
echo "<p><input class='button-primary' type='submit' value='" . __( "Save", 'wordpress-mu-domain-mapping' ) . "' /></p>";
|
||||
echo "</form><br />";
|
||||
}
|
||||
|
||||
function dm_handle_actions() {
|
||||
global $wpdb, $parent_file;
|
||||
$url = add_query_arg( array( 'page' => 'domainmapping' ), admin_url( $parent_file ) );
|
||||
if ( !empty( $_POST[ 'action' ] ) ) {
|
||||
$domain = $wpdb->escape( $_POST[ 'domain' ] );
|
||||
if ( $domain == '' ) {
|
||||
wp_die( "You must enter a domain" );
|
||||
}
|
||||
check_admin_referer( 'domain_mapping' );
|
||||
do_action('dm_handle_actions_init', $domain);
|
||||
switch( $_POST[ 'action' ] ) {
|
||||
case "add":
|
||||
do_action('dm_handle_actions_add', $domain);
|
||||
if( null == $wpdb->get_row( "SELECT blog_id FROM {$wpdb->blogs} WHERE domain = '$domain'" ) && null == $wpdb->get_row( "SELECT blog_id FROM {$wpdb->dmtable} WHERE domain = '$domain'" ) ) {
|
||||
if ( $_POST[ 'primary' ] ) {
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->dmtable} SET active = 0 WHERE blog_id = %d", $wpdb->blogid ) );
|
||||
}
|
||||
$wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->dmtable} ( `id` , `blog_id` , `domain` , `active` ) VALUES ( NULL, %d, %s, %d )", $wpdb->blogid, $domain, $_POST[ 'primary' ] ) );
|
||||
wp_redirect( add_query_arg( array( 'updated' => 'add' ), $url ) );
|
||||
exit;
|
||||
} else {
|
||||
wp_redirect( add_query_arg( array( 'updated' => 'exists' ), $url ) );
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
case "primary":
|
||||
do_action('dm_handle_actions_primary', $domain);
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->dmtable} SET active = 0 WHERE blog_id = %d", $wpdb->blogid ) );
|
||||
$orig_url = parse_url( get_original_url( 'siteurl' ) );
|
||||
if( $domain != $orig_url[ 'host' ] ) {
|
||||
$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->dmtable} SET active = 1 WHERE domain = %s", $domain ) );
|
||||
}
|
||||
wp_redirect( add_query_arg( array( 'updated' => 'primary' ), $url ) );
|
||||
exit;
|
||||
break;
|
||||
}
|
||||
} elseif( $_GET[ 'action' ] == 'delete' ) {
|
||||
$domain = $wpdb->escape( $_GET[ 'domain' ] );
|
||||
if ( $domain == '' ) {
|
||||
wp_die( __( "You must enter a domain", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
check_admin_referer( "delete" . $_GET['domain'] );
|
||||
do_action('dm_handle_actions_del', $domain);
|
||||
$wpdb->query( "DELETE FROM {$wpdb->dmtable} WHERE domain = '$domain'" );
|
||||
wp_redirect( add_query_arg( array( 'updated' => 'del' ), $url ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
if ( isset( $_GET[ 'page' ] ) && $_GET[ 'page' ] == 'domainmapping' )
|
||||
add_action( 'admin_init', 'dm_handle_actions' );
|
||||
|
||||
function dm_sunrise_warning( $die = true ) {
|
||||
if ( !file_exists( WP_CONTENT_DIR . '/sunrise.php' ) ) {
|
||||
if ( !$die )
|
||||
return true;
|
||||
|
||||
if ( dm_site_admin() ) {
|
||||
wp_die( sprintf( __( "Please copy sunrise.php to %s/sunrise.php and ensure the SUNRISE definition is in %swp-config.php", 'wordpress-mu-domain-mapping' ), WP_CONTENT_DIR, ABSPATH ) );
|
||||
} else {
|
||||
wp_die( __( "This plugin has not been configured correctly yet.", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
} elseif ( !defined( 'SUNRISE' ) ) {
|
||||
if ( !$die )
|
||||
return true;
|
||||
|
||||
if ( dm_site_admin() ) {
|
||||
wp_die( sprintf( __( "Please uncomment the line <em>define( 'SUNRISE', 'on' );</em> or add it to your %swp-config.php", 'wordpress-mu-domain-mapping' ), ABSPATH ) );
|
||||
} else {
|
||||
wp_die( __( "This plugin has not been configured correctly yet.", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
} elseif ( !defined( 'SUNRISE_LOADED' ) ) {
|
||||
if ( !$die )
|
||||
return true;
|
||||
|
||||
if ( dm_site_admin() ) {
|
||||
wp_die( sprintf( __( "Please edit your %swp-config.php and move the line <em>define( 'SUNRISE', 'on' );</em> above the last require_once() in that file or make sure you updated sunrise.php.", 'wordpress-mu-domain-mapping' ), ABSPATH ) );
|
||||
} else {
|
||||
wp_die( __( "This plugin has not been configured correctly yet.", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function dm_manage_page() {
|
||||
global $wpdb, $parent_file;
|
||||
|
||||
if ( isset( $_GET[ 'updated' ] ) ) {
|
||||
do_action('dm_echo_updated_msg');
|
||||
}
|
||||
|
||||
dm_sunrise_warning();
|
||||
|
||||
echo "<div class='wrap'><h2>" . __( 'Domain Mapping', 'wordpress-mu-domain-mapping' ) . "</h2>";
|
||||
|
||||
if ( false == get_site_option( 'dm_ipaddress' ) && false == get_site_option( 'dm_cname' ) ) {
|
||||
if ( dm_site_admin() ) {
|
||||
_e( "Please set the IP address or CNAME of your server in the <a href='wpmu-admin.php?page=dm_admin_page'>site admin page</a>.", 'wordpress-mu-domain-mapping' );
|
||||
} else {
|
||||
_e( "This plugin has not been configured correctly yet.", 'wordpress-mu-domain-mapping' );
|
||||
}
|
||||
echo "</div>";
|
||||
return false;
|
||||
}
|
||||
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
$domains = $wpdb->get_results( "SELECT * FROM {$wpdb->dmtable} WHERE blog_id = '{$wpdb->blogid}'", ARRAY_A );
|
||||
if ( is_array( $domains ) && !empty( $domains ) ) {
|
||||
$orig_url = parse_url( get_original_url( 'siteurl' ) );
|
||||
$domains[] = array( 'domain' => $orig_url[ 'host' ], 'path' => $orig_url[ 'path' ], 'active' => 0 );
|
||||
echo "<h3>" . __( 'Active domains on this blog', 'wordpress-mu-domain-mapping' ) . "</h3>";
|
||||
echo '<form method="POST">';
|
||||
echo "<table><tr><th>" . __( 'Primary', 'wordpress-mu-domain-mapping' ) . "</th><th>" . __( 'Domain', 'wordpress-mu-domain-mapping' ) . "</th><th>" . __( 'Delete', 'wordpress-mu-domain-mapping' ) . "</th></tr>\n";
|
||||
$primary_found = 0;
|
||||
$del_url = add_query_arg( array( 'page' => 'domainmapping', 'action' => 'delete' ), admin_url( $parent_file ) );
|
||||
foreach( $domains as $details ) {
|
||||
if ( 0 == $primary_found && $details[ 'domain' ] == $orig_url[ 'host' ] ) {
|
||||
$details[ 'active' ] = 1;
|
||||
}
|
||||
echo "<tr><td>";
|
||||
echo "<input type='radio' name='domain' value='{$details[ 'domain' ]}' ";
|
||||
if ( $details[ 'active' ] == 1 )
|
||||
echo "checked='1' ";
|
||||
echo "/>";
|
||||
$url = "{$protocol}{$details[ 'domain' ]}{$details[ 'path' ]}";
|
||||
echo "</td><td><a href='$url'>$url</a></td><td style='text-align: center'>";
|
||||
if ( $details[ 'domain' ] != $orig_url[ 'host' ] && $details[ 'active' ] != 1 ) {
|
||||
echo "<a href='" . wp_nonce_url( add_query_arg( array( 'domain' => $details[ 'domain' ] ), $del_url ), "delete" . $details[ 'domain' ] ) . "'>Del</a>";
|
||||
}
|
||||
echo "</td></tr>";
|
||||
if ( 0 == $primary_found )
|
||||
$primary_found = $details[ 'active' ];
|
||||
}
|
||||
?></table><?php
|
||||
echo '<input type="hidden" name="action" value="primary" />';
|
||||
echo "<p><input type='submit' class='button-primary' value='" . __( 'Set Primary Domain', 'wordpress-mu-domain-mapping' ) . "' /></p>";
|
||||
wp_nonce_field( 'domain_mapping' );
|
||||
echo "</form>";
|
||||
echo "<p>" . __( "* The primary domain cannot be deleted.", 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) {
|
||||
echo __( '<strong>Warning!</strong> Primary domains are currently disabled.', 'wordpress-mu-domain-mapping' );
|
||||
}
|
||||
}
|
||||
echo "<h3>" . __( 'Add new domain', 'wordpress-mu-domain-mapping' ) . "</h3>";
|
||||
echo '<form method="POST">';
|
||||
echo '<input type="hidden" name="action" value="add" />';
|
||||
echo "<p>http://<input type='text' name='domain' value='' />/<br />";
|
||||
wp_nonce_field( 'domain_mapping' );
|
||||
echo "<input type='checkbox' name='primary' value='1' /> " . __( 'Primary domain for this blog', 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
echo "<p><input type='submit' class='button-secondary' value='" . __( "Add", 'wordpress-mu-domain-mapping' ) . "' /></p>";
|
||||
echo "</form><br />";
|
||||
|
||||
if ( get_site_option( 'dm_cname' ) ) {
|
||||
$dm_cname = get_site_option( 'dm_cname');
|
||||
echo "<p>" . sprintf( __( 'If you want to redirect a domain you will need to add a DNS "CNAME" record pointing to the following domain name for this server: <strong>%s</strong>', 'wordpress-mu-domain-mapping' ), $dm_cname ) . "</p>";
|
||||
echo "<p>" . __( 'Google have published <a href="http://www.google.com/support/blogger/bin/answer.py?hl=en&answer=58317" target="_blank">instructions</a> for creating CNAME records on various hosting platforms such as GoDaddy and others.', 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
} else {
|
||||
echo "<p>" . __( 'If your domain name includes a hostname like "www", "blog" or some other prefix before the actual domain name you will need to add a CNAME for that hostname in your DNS pointing at this blog URL.', 'wordpress-mu-domain-mapping' ) . "</p>";
|
||||
$dm_ipaddress = get_site_option( 'dm_ipaddress', 'IP not set by admin yet.' );
|
||||
if ( strpos( $dm_ipaddress, ',' ) ) {
|
||||
echo "<p>" . sprintf( __( 'If you want to redirect a domain you will need to add DNS "A" records pointing at the IP addresses of this server: <strong>%s</strong>', 'wordpress-mu-domain-mapping' ), $dm_ipaddress ) . "</p>";
|
||||
} else {
|
||||
echo "<p>" . sprintf( __( 'If you want to redirect a domain you will need to add a DNS "A" record pointing at the IP address of this server: <strong>%s</strong>', 'wordpress-mu-domain-mapping' ), $dm_ipaddress ) . "</p>";
|
||||
}
|
||||
}
|
||||
echo '<p>' . sprintf( __( '<strong>Note:</strong> %s', 'wordpress-mu-domain-mapping' ), dm_idn_warning() ) . "</p>";
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
function domain_mapping_siteurl( $setting ) {
|
||||
global $wpdb, $current_blog;
|
||||
|
||||
// To reduce the number of database queries, save the results the first time we encounter each blog ID.
|
||||
static $return_url = array();
|
||||
|
||||
$wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
|
||||
|
||||
if ( !isset( $return_url[ $wpdb->blogid ] ) ) {
|
||||
$s = $wpdb->suppress_errors();
|
||||
|
||||
if ( get_site_option( 'dm_no_primary_domain' ) == 1 ) {
|
||||
$domain = $wpdb->get_var( "SELECT domain FROM {$wpdb->dmtable} WHERE blog_id = '{$wpdb->blogid}' AND domain = '" . $wpdb->escape( $_SERVER[ 'HTTP_HOST' ] ) . "' LIMIT 1" );
|
||||
if ( null == $domain ) {
|
||||
$return_url[ $wpdb->blogid ] = untrailingslashit( get_original_url( "siteurl" ) );
|
||||
return $return_url[ $wpdb->blogid ];
|
||||
}
|
||||
} else {
|
||||
// get primary domain, if we don't have one then return original url.
|
||||
$domain = $wpdb->get_var( "SELECT domain FROM {$wpdb->dmtable} WHERE blog_id = '{$wpdb->blogid}' AND active = 1 LIMIT 1" );
|
||||
if ( null == $domain ) {
|
||||
$return_url[ $wpdb->blogid ] = untrailingslashit( get_original_url( "siteurl" ) );
|
||||
return $return_url[ $wpdb->blogid ];
|
||||
}
|
||||
}
|
||||
|
||||
$wpdb->suppress_errors( $s );
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
if ( $domain ) {
|
||||
$return_url[ $wpdb->blogid ] = untrailingslashit( $protocol . $domain );
|
||||
$setting = $return_url[ $wpdb->blogid ];
|
||||
} else {
|
||||
$return_url[ $wpdb->blogid ] = false;
|
||||
}
|
||||
} elseif ( $return_url[ $wpdb->blogid ] !== FALSE) {
|
||||
$setting = $return_url[ $wpdb->blogid ];
|
||||
}
|
||||
|
||||
return $setting;
|
||||
}
|
||||
|
||||
// url is siteurl or home
|
||||
function get_original_url( $url, $blog_id = 0 ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( $blog_id != 0 ) {
|
||||
$id = $blog_id;
|
||||
} else {
|
||||
$id = $wpdb->blogid;
|
||||
}
|
||||
|
||||
static $orig_urls = array();
|
||||
if ( ! isset( $orig_urls[ $id ] ) ) {
|
||||
if ( defined( 'DOMAIN_MAPPING' ) )
|
||||
remove_filter( 'pre_option_' . $url, 'domain_mapping_' . $url );
|
||||
if ( $blog_id == 0 ) {
|
||||
$orig_url = get_option( $url );
|
||||
} else {
|
||||
$orig_url = get_blog_option( $blog_id, $url );
|
||||
}
|
||||
if ( is_ssl() ) {
|
||||
$orig_url = str_replace( "http://", "https://", $orig_url );
|
||||
} else {
|
||||
$orig_url = str_replace( "https://", "http://", $orig_url );
|
||||
}
|
||||
if ( $blog_id == 0 ) {
|
||||
$orig_urls[ $wpdb->blogid ] = $orig_url;
|
||||
} else {
|
||||
$orig_urls[ $blog_id ] = $orig_url;
|
||||
}
|
||||
if ( defined( 'DOMAIN_MAPPING' ) )
|
||||
add_filter( 'pre_option_' . $url, 'domain_mapping_' . $url );
|
||||
}
|
||||
return $orig_urls[ $id ];
|
||||
}
|
||||
|
||||
function domain_mapping_adminurl( $url, $path, $blog_id = 0 ) {
|
||||
$index = strpos( $url, '/wp-admin' );
|
||||
if( $index !== false ) {
|
||||
$url = get_original_url( 'siteurl', $blog_id ) . substr( $url, $index );
|
||||
|
||||
// make sure admin_url is ssl if current page is ssl, or admin ssl is forced
|
||||
if( ( is_ssl() || force_ssl_admin() ) && 0 === strpos( $url, 'http://' ) ) {
|
||||
$url = 'https://' . substr( $url, 7 );
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
function domain_mapping_post_content( $post_content ) {
|
||||
global $wpdb;
|
||||
|
||||
$orig_url = get_original_url( 'siteurl' );
|
||||
|
||||
$url = domain_mapping_siteurl( 'NA' );
|
||||
if ( $url == 'NA' )
|
||||
return $post_content;
|
||||
return str_replace( $orig_url, $url, $post_content );
|
||||
}
|
||||
|
||||
function dm_redirect_admin() {
|
||||
// don't redirect admin ajax calls
|
||||
if ( strpos( $_SERVER['REQUEST_URI'], 'wp-admin/admin-ajax.php' ) !== false )
|
||||
return;
|
||||
|
||||
if ( get_site_option( 'dm_redirect_admin' ) ) {
|
||||
// redirect mapped domain admin page to original url
|
||||
$url = get_original_url( 'siteurl' );
|
||||
if ( false === strpos( $url, $_SERVER[ 'HTTP_HOST' ] ) ) {
|
||||
wp_redirect( untrailingslashit( $url ) . $_SERVER[ 'REQUEST_URI' ] );
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
global $current_blog;
|
||||
// redirect original url to primary domain wp-admin/ - remote login is disabled!
|
||||
$url = domain_mapping_siteurl( false );
|
||||
$request_uri = str_replace( $current_blog->path, '/', $_SERVER[ 'REQUEST_URI' ] );
|
||||
if ( false === strpos( $url, $_SERVER[ 'HTTP_HOST' ] ) ) {
|
||||
wp_redirect( str_replace( '//wp-admin', '/wp-admin', trailingslashit( $url ) . $request_uri ) );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function redirect_login_to_orig() {
|
||||
if ( !get_site_option( 'dm_remote_login' ) || $_GET[ 'action' ] == 'logout' || isset( $_GET[ 'loggedout' ] ) ) {
|
||||
return false;
|
||||
}
|
||||
$url = get_original_url( 'siteurl' );
|
||||
if ( $url != site_url() ) {
|
||||
$url .= "/wp-login.php";
|
||||
echo "<script type='text/javascript'>\nwindow.location = '$url'</script>";
|
||||
}
|
||||
}
|
||||
|
||||
// fixes the plugins_url
|
||||
function domain_mapping_plugins_uri( $full_url, $path=NULL, $plugin=NULL ) {
|
||||
return get_option( 'siteurl' ) . substr( $full_url, stripos( $full_url, PLUGINDIR ) - 1 );
|
||||
}
|
||||
|
||||
function domain_mapping_themes_uri( $full_url ) {
|
||||
return str_replace( get_original_url ( 'siteurl' ), get_option( 'siteurl' ), $full_url );
|
||||
}
|
||||
|
||||
if ( defined( 'DOMAIN_MAPPING' ) ) {
|
||||
add_filter( 'plugins_url', 'domain_mapping_plugins_uri', 1 );
|
||||
add_filter( 'theme_root_uri', 'domain_mapping_themes_uri', 1 );
|
||||
add_filter( 'pre_option_siteurl', 'domain_mapping_siteurl' );
|
||||
add_filter( 'pre_option_home', 'domain_mapping_siteurl' );
|
||||
add_filter( 'the_content', 'domain_mapping_post_content' );
|
||||
add_action( 'wp_head', 'remote_login_js_loader' );
|
||||
add_action( 'login_head', 'redirect_login_to_orig' );
|
||||
add_action( 'wp_logout', 'remote_logout_loader', 9999 );
|
||||
|
||||
add_filter( 'stylesheet_uri', 'domain_mapping_post_content' );
|
||||
add_filter( 'stylesheet_directory', 'domain_mapping_post_content' );
|
||||
add_filter( 'stylesheet_directory_uri', 'domain_mapping_post_content' );
|
||||
add_filter( 'template_directory', 'domain_mapping_post_content' );
|
||||
add_filter( 'template_directory_uri', 'domain_mapping_post_content' );
|
||||
add_filter( 'plugins_url', 'domain_mapping_post_content' );
|
||||
} else {
|
||||
add_filter( 'admin_url', 'domain_mapping_adminurl', 10, 3 );
|
||||
}
|
||||
add_action( 'admin_init', 'dm_redirect_admin' );
|
||||
if ( isset( $_GET[ 'dm' ] ) )
|
||||
add_action( 'template_redirect', 'remote_login_js' );
|
||||
|
||||
function remote_logout_loader() {
|
||||
global $current_site, $current_blog, $wpdb;
|
||||
$wpdb->dmtablelogins = $wpdb->base_prefix . 'domain_mapping_logins';
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
$hash = get_dm_hash();
|
||||
$key = md5( time() );
|
||||
$wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->dmtablelogins} ( `id`, `user_id`, `blog_id`, `t` ) VALUES( %s, 0, %d, NOW() )", $key, $current_blog->blog_id ) );
|
||||
if ( get_site_option( 'dm_redirect_admin' ) ) {
|
||||
wp_redirect( $protocol . $current_site->domain . $current_site->path . "?dm={$hash}&action=logout&blogid={$current_blog->blog_id}&k={$key}&t=" . mt_rand() );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
function redirect_to_mapped_domain() {
|
||||
global $current_blog, $wpdb;
|
||||
|
||||
// don't redirect the main site
|
||||
if ( is_main_site() )
|
||||
return;
|
||||
// don't redirect post previews
|
||||
if ( isset( $_GET['preview'] ) && $_GET['preview'] == 'true' )
|
||||
return;
|
||||
|
||||
// don't redirect theme customizer (WP 3.4)
|
||||
if ( isset( $_POST['customize'] ) && isset( $_POST['theme'] ) && $_POST['customize'] == 'on' )
|
||||
return;
|
||||
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
$url = domain_mapping_siteurl( false );
|
||||
if ( $url && $url != untrailingslashit( $protocol . $current_blog->domain . $current_blog->path ) ) {
|
||||
$redirect = get_site_option( 'dm_301_redirect' ) ? '301' : '302';
|
||||
if ( ( defined( 'VHOST' ) && constant( "VHOST" ) != 'yes' ) || ( defined( 'SUBDOMAIN_INSTALL' ) && constant( 'SUBDOMAIN_INSTALL' ) == false ) ) {
|
||||
$_SERVER[ 'REQUEST_URI' ] = str_replace( $current_blog->path, '/', $_SERVER[ 'REQUEST_URI' ] );
|
||||
}
|
||||
header( "Location: {$url}{$_SERVER[ 'REQUEST_URI' ]}", true, $redirect );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
add_action( 'template_redirect', 'redirect_to_mapped_domain' );
|
||||
|
||||
function get_dm_hash() {
|
||||
$remote_login_hash = get_site_option( 'dm_hash' );
|
||||
if ( null == $remote_login_hash ) {
|
||||
$remote_login_hash = md5( time() );
|
||||
update_site_option( 'dm_hash', $remote_login_hash );
|
||||
}
|
||||
return $remote_login_hash;
|
||||
}
|
||||
|
||||
function remote_login_js() {
|
||||
global $current_blog, $current_user, $wpdb;
|
||||
|
||||
if ( 0 == get_site_option( 'dm_remote_login' ) )
|
||||
return false;
|
||||
|
||||
$wpdb->dmtablelogins = $wpdb->base_prefix . 'domain_mapping_logins';
|
||||
$hash = get_dm_hash();
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
if ( $_GET[ 'dm' ] == $hash ) {
|
||||
if ( $_GET[ 'action' ] == 'load' ) {
|
||||
if ( !is_user_logged_in() )
|
||||
exit;
|
||||
$key = md5( time() . mt_rand() );
|
||||
$wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->dmtablelogins} ( `id`, `user_id`, `blog_id`, `t` ) VALUES( %s, %d, %d, NOW() )", $key, $current_user->ID, $_GET[ 'blogid' ] ) );
|
||||
$url = add_query_arg( array( 'action' => 'login', 'dm' => $hash, 'k' => $key, 't' => mt_rand() ), $_GET[ 'back' ] );
|
||||
echo "window.location = '$url'";
|
||||
exit;
|
||||
} elseif ( $_GET[ 'action' ] == 'login' ) {
|
||||
if ( $details = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->dmtablelogins} WHERE id = %s AND blog_id = %d", $_GET[ 'k' ], $wpdb->blogid ) ) ) {
|
||||
if ( $details->blog_id == $wpdb->blogid ) {
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->dmtablelogins} WHERE id = %s", $_GET[ 'k' ] ) );
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->dmtablelogins} WHERE t < %d", ( time() - 120 ) ) ); // remote logins survive for only 2 minutes if not used.
|
||||
wp_set_auth_cookie( $details->user_id );
|
||||
wp_redirect( remove_query_arg( array( 'dm', 'action', 'k', 't', $protocol . $current_blog->domain . $_SERVER[ 'REQUEST_URI' ] ) ) );
|
||||
exit;
|
||||
} else {
|
||||
wp_die( __( "Incorrect or out of date login key", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
} else {
|
||||
wp_die( __( "Unknown login key", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
} elseif ( $_GET[ 'action' ] == 'logout' ) {
|
||||
if ( $details = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->dmtablelogins} WHERE id = %d AND blog_id = %d", $_GET[ 'k' ], $_GET[ 'blogid' ] ) ) ) {
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->dmtablelogins} WHERE id = %s", $_GET[ 'k' ] ) );
|
||||
$blog = get_blog_details( $_GET[ 'blogid' ] );
|
||||
wp_clear_auth_cookie();
|
||||
wp_redirect( trailingslashit( $blog->siteurl ) . "wp-login.php?loggedout=true" );
|
||||
exit;
|
||||
} else {
|
||||
wp_die( __( "Unknown logout key", 'wordpress-mu-domain-mapping' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function remote_login_js_loader() {
|
||||
global $current_site, $current_blog;
|
||||
|
||||
if ( 0 == get_site_option( 'dm_remote_login' ) || is_user_logged_in() )
|
||||
return false;
|
||||
|
||||
$protocol = is_ssl() ? 'https://' : 'http://';
|
||||
$hash = get_dm_hash();
|
||||
echo "<script src='{$protocol}{$current_site->domain}{$current_site->path}?dm={$hash}&action=load&blogid={$current_blog->blog_id}&siteid={$current_blog->site_id}&t=" . mt_rand() . "&back=" . urlencode( $protocol . $current_blog->domain . $_SERVER[ 'REQUEST_URI' ] ) . "' type='text/javascript'></script>";
|
||||
}
|
||||
|
||||
// delete mapping if blog is deleted
|
||||
function delete_blog_domain_mapping( $blog_id, $drop ) {
|
||||
global $wpdb;
|
||||
$wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
|
||||
if ( $blog_id && $drop ) {
|
||||
// Get an array of domain names to pass onto any delete_blog_domain_mapping actions
|
||||
$domains = $wpdb->get_col( $wpdb->prepare( "SELECT domain FROM {$wpdb->dmtable} WHERE blog_id = %d", $blog_id ) );
|
||||
do_action('dm_delete_blog_domain_mappings', $domains);
|
||||
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->dmtable} WHERE blog_id = %d", $blog_id ) );
|
||||
}
|
||||
}
|
||||
add_action( 'delete_blog', 'delete_blog_domain_mapping', 1, 2 );
|
||||
|
||||
// show mapping on site admin blogs screen
|
||||
function ra_domain_mapping_columns( $columns ) {
|
||||
$columns[ 'map' ] = __( 'Mapping' );
|
||||
return $columns;
|
||||
}
|
||||
add_filter( 'wpmu_blogs_columns', 'ra_domain_mapping_columns' );
|
||||
|
||||
function ra_domain_mapping_field( $column, $blog_id ) {
|
||||
global $wpdb;
|
||||
static $maps = false;
|
||||
|
||||
if ( $column == 'map' ) {
|
||||
if ( $maps === false ) {
|
||||
$wpdb->dmtable = $wpdb->base_prefix . 'domain_mapping';
|
||||
$work = $wpdb->get_results( "SELECT blog_id, domain FROM {$wpdb->dmtable} ORDER BY blog_id" );
|
||||
$maps = array();
|
||||
if($work) {
|
||||
foreach( $work as $blog ) {
|
||||
$maps[ $blog->blog_id ][] = $blog->domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( !empty( $maps[ $blog_id ] ) && is_array( $maps[ $blog_id ] ) ) {
|
||||
foreach( $maps[ $blog_id ] as $blog ) {
|
||||
echo $blog . '<br />';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
add_action( 'manage_blogs_custom_column', 'ra_domain_mapping_field', 1, 3 );
|
||||
add_action( 'manage_sites_custom_column', 'ra_domain_mapping_field', 1, 3 );
|
||||
|
||||
function dm_site_admin() {
|
||||
if ( function_exists( 'is_super_admin' ) ) {
|
||||
return is_super_admin();
|
||||
} elseif ( function_exists( 'is_site_admin' ) ) {
|
||||
return is_site_admin();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function dm_idn_warning() {
|
||||
return sprintf( __( 'International Domain Names should be in <a href="%s">punycode</a> format.', 'wordpress-mu-domain-mapping' ), "http://api.webnic.cc/idnconversion.html" );
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,469 @@
|
||||
/**
|
||||
* Deactivation survey modal styles.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
:root {
|
||||
--cmatic-modal-overlay-bg: rgba(0, 0, 0, 0.6);
|
||||
--cmatic-modal-bg: #fff;
|
||||
--cmatic-modal-max-width: 600px;
|
||||
--cmatic-modal-border-radius: 8px;
|
||||
--cmatic-modal-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
--cmatic-primary-color: #0073aa;
|
||||
--cmatic-primary-hover: #005a87;
|
||||
--cmatic-secondary-color: #f0f0f1;
|
||||
--cmatic-secondary-hover: #dcdcde;
|
||||
--cmatic-text-color: #1e1e1e;
|
||||
--cmatic-text-light: #646970;
|
||||
--cmatic-border-color: #dcdcde;
|
||||
--cmatic-error-color: #d63638;
|
||||
--cmatic-success-color: #00a32a;
|
||||
--cmatic-transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cmatic-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100000;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.cmatic-modal--active {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cmatic-modal--active .cmatic-modal__overlay {
|
||||
animation: modalOverlayFadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.cmatic-modal--active .cmatic-modal__dialog {
|
||||
animation: modalSlideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
@keyframes modalOverlayFadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes modalSlideUp {
|
||||
from { opacity: 0; transform: translateY(100px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.cmatic-modal--closing .cmatic-modal__overlay {
|
||||
animation: modalOverlayFadeOut 0.3s ease;
|
||||
}
|
||||
|
||||
.cmatic-modal--closing .cmatic-modal__dialog {
|
||||
animation: modalSlideDown 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes modalOverlayFadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes modalSlideDown {
|
||||
from { opacity: 1; transform: translateY(0); }
|
||||
to { opacity: 0; transform: translateY(50px); }
|
||||
}
|
||||
|
||||
.cmatic-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cmatic-modal__overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--cmatic-modal-overlay-bg);
|
||||
z-index: -1;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.cmatic-modal__dialog {
|
||||
position: relative;
|
||||
max-width: var(--cmatic-modal-max-width);
|
||||
width: 90%;
|
||||
background: var(--cmatic-modal-bg);
|
||||
border-radius: var(--cmatic-modal-border-radius);
|
||||
box-shadow: var(--cmatic-modal-shadow);
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.cmatic-modal__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 24px;
|
||||
border-bottom: 1px solid var(--cmatic-border-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__header h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--cmatic-text-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__header .button {
|
||||
margin-left: auto;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.cmatic-modal__close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 28px;
|
||||
line-height: 1;
|
||||
color: var(--cmatic-text-light);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: var(--cmatic-transition);
|
||||
}
|
||||
|
||||
.cmatic-modal__close:hover,
|
||||
.cmatic-modal__close:focus {
|
||||
background: var(--cmatic-secondary-color);
|
||||
color: var(--cmatic-text-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__body {
|
||||
padding: 24px;
|
||||
max-height: calc(100vh - 300px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.cmatic-modal__body > h3 {
|
||||
margin: 0 0 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--cmatic-text-color);
|
||||
}
|
||||
|
||||
.cmatic-reasons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cmatic-reason-btn {
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #f8f9fa 100%);
|
||||
border: 2px solid transparent;
|
||||
padding: 16px 20px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
font-size: 14px;
|
||||
color: var(--cmatic-text-color);
|
||||
text-align: left;
|
||||
font-family: inherit;
|
||||
width: 100%;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cmatic-reason-btn:hover {
|
||||
background: linear-gradient(135deg, #e8f0fe 0%, #f1f8ff 100%);
|
||||
border-color: #f0f6fc;
|
||||
}
|
||||
|
||||
.cmatic-reason-btn.selected {
|
||||
background: linear-gradient(135deg, #f0f6fc 0%, #f0f6fc 100%);
|
||||
border-color: #d8dde3;
|
||||
color: var(--cmatic-text-color);
|
||||
font-weight: 500;
|
||||
box-shadow: 0 6px 16px rgba(26, 115, 232, 0.3);
|
||||
}
|
||||
|
||||
.cmatic-reason-btn:focus-visible {
|
||||
outline: 2px solid var(--cmatic-primary-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.cmatic-input-wrapper {
|
||||
margin-top: 12px;
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from { opacity: 0; transform: translateY(-10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.cmatic-input-field {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.cmatic-input-field:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.cmatic-input-field[type="text"] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
select.cmatic-input-field {
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-color: #fff;
|
||||
background-image: url(data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23646970' d='M6 9L1 4h10z'/%3E%3C/svg%3E);
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 14px center;
|
||||
background-size: 12px;
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.cmatic-char-counter {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--cmatic-text-light);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cmatic-error-message {
|
||||
display: none;
|
||||
margin-top: 16px;
|
||||
padding: 12px;
|
||||
background: #fcf0f1;
|
||||
border: 1px solid var(--cmatic-error-color);
|
||||
border-radius: 4px;
|
||||
color: var(--cmatic-error-color);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cmatic-error-message--visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cmatic-modal__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-direction: row;
|
||||
padding: 20px 24px;
|
||||
border-top: 1px solid var(--cmatic-border-color);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.cmatic-modal__footer .button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.cmatic-skip-link {
|
||||
color: var(--cmatic-text-light);
|
||||
font-size: 13px;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.cmatic-skip-link:hover {
|
||||
color: var(--cmatic-text-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.cmatic-modal__dialog {
|
||||
width: 95%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.cmatic-modal__header,
|
||||
.cmatic-modal__body,
|
||||
.cmatic-modal__footer {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.cmatic-modal__footer {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cmatic-modal__footer .button {
|
||||
width: 100%;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.cmatic-skip-link {
|
||||
order: 2;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.cmatic-reason-btn {
|
||||
padding: 14px 16px;
|
||||
}
|
||||
|
||||
.cmatic-reasons {
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.cmatic-modal__footer .button:focus-visible {
|
||||
outline: 2px solid var(--cmatic-primary-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (prefers-contrast: high) {
|
||||
.cmatic-modal__dialog {
|
||||
border: 2px solid currentcolor;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Submission Feedback
|
||||
========================================================================== */
|
||||
|
||||
.cmatic-modal__feedback {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 8px;
|
||||
background: #f0f6fc;
|
||||
border: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-icon {
|
||||
flex-shrink: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-icon .dashicons {
|
||||
font-size: 24px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--cmatic-text-color);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-details {
|
||||
font-size: 13px;
|
||||
color: var(--cmatic-text-light);
|
||||
}
|
||||
|
||||
/* Sent vs Received comparison table */
|
||||
.cmatic-modal__feedback-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-table th,
|
||||
.cmatic-modal__feedback-table td {
|
||||
padding: 6px 10px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-table th {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: var(--cmatic-text-light);
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-table .field-key {
|
||||
font-weight: 600;
|
||||
color: var(--cmatic-text-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-table .field-empty {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-table .field-mismatch {
|
||||
background: #fff8e5;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback-table .field-mismatch td {
|
||||
border-bottom-color: #f0c36d;
|
||||
}
|
||||
|
||||
/* Success state */
|
||||
.cmatic-modal__feedback--success {
|
||||
background: #edfaef;
|
||||
border-color: var(--cmatic-success-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback--success .cmatic-modal__feedback-icon .dashicons {
|
||||
color: var(--cmatic-success-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback--success .cmatic-modal__feedback-title {
|
||||
color: #006d1b;
|
||||
}
|
||||
|
||||
/* Error state */
|
||||
.cmatic-modal__feedback--error {
|
||||
background: #fcf0f1;
|
||||
border-color: var(--cmatic-error-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback--error .cmatic-modal__feedback-icon .dashicons {
|
||||
color: var(--cmatic-error-color);
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback--error .cmatic-modal__feedback-title {
|
||||
color: #8a1f1f;
|
||||
}
|
||||
|
||||
/* Skipped state */
|
||||
.cmatic-modal__feedback--skipped {
|
||||
background: #fff8e5;
|
||||
border-color: #dba617;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback--skipped .cmatic-modal__feedback-icon .dashicons {
|
||||
color: #996800;
|
||||
}
|
||||
|
||||
.cmatic-modal__feedback--skipped .cmatic-modal__feedback-title {
|
||||
color: #614200;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="7 9.5 8 1" enable-background="new 7 9.5 8 1" xml:space="preserve" preserveAspectRatio="none slice"><path fill="#CE1D00" d="M10.9 9.5l-1 1H12l1-1z"/><path fill="#2284C0" d="M14.9 9.5l-1 1H15v-1zM7 9.5v1h1l1-1z"/></svg>
|
||||
|
After Width: | Height: | Size: 267 B |
Binary file not shown.
|
After Width: | Height: | Size: 339 B |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -0,0 +1,383 @@
|
||||
/**
|
||||
* Deactivation survey modal.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const config = window.cmaticDeactivate || {};
|
||||
let deactivateUrl = '';
|
||||
let modalElement = null;
|
||||
let pluginsCache = null;
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
init();
|
||||
});
|
||||
|
||||
function init() {
|
||||
const deactivateLink = findDeactivateLink();
|
||||
if (!deactivateLink) return;
|
||||
|
||||
deactivateUrl = deactivateLink.href;
|
||||
buildModal();
|
||||
attachEventListeners(deactivateLink);
|
||||
}
|
||||
|
||||
function findDeactivateLink() {
|
||||
const row = document.querySelector(`tr[data-slug="${config.pluginSlug}"]`);
|
||||
return row ? row.querySelector('.deactivate a') : null;
|
||||
}
|
||||
|
||||
function buildModal() {
|
||||
modalElement = document.getElementById('cmatic-deactivate-modal');
|
||||
if (!modalElement) return;
|
||||
|
||||
modalElement.innerHTML = `
|
||||
<div class="cmatic-modal__overlay"></div>
|
||||
<div class="cmatic-modal__dialog">
|
||||
<div class="cmatic-modal__header">
|
||||
<h2 id="cmatic-modal-title">${config.strings.title}</h2>
|
||||
<button type="button" class="cmatic-modal__close" aria-label="${config.strings.closeLabel}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cmatic-modal__body">
|
||||
<h3 id="cmatic-modal-description">${config.strings.description}</h3>
|
||||
<form id="cmatic-deactivate-form">
|
||||
<div class="cmatic-reasons" role="radiogroup" aria-labelledby="cmatic-modal-description">
|
||||
${buildReasonsList()}
|
||||
</div>
|
||||
<div class="cmatic-error-message" role="alert" aria-live="assertive"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="cmatic-modal__footer">
|
||||
<a href="#" class="cmatic-skip-link" style="display: none;">${config.strings.skipButton}</a>
|
||||
<button type="submit" form="cmatic-deactivate-form" class="button button-primary cmatic-submit-button">
|
||||
${config.strings.submitButton}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function buildReasonsList() {
|
||||
return config.reasons.map(reason => {
|
||||
return `
|
||||
<button type="button" class="cmatic-reason-btn" data-reason-id="${reason.id}" data-input-type="${reason.input_type}" aria-pressed="false">
|
||||
${reason.text}
|
||||
</button>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function buildInputField(reasonId, inputType) {
|
||||
const reason = config.reasons.find(r => r.id === reasonId);
|
||||
if (!reason) return '';
|
||||
|
||||
let inputHtml = '';
|
||||
if (inputType === 'plugin-dropdown') {
|
||||
inputHtml = `<select class="cmatic-input-field" aria-label="${reason.placeholder || 'Select a plugin'}" disabled><option value="">Loading plugins...</option></select>`;
|
||||
} else if (inputType === 'textfield') {
|
||||
inputHtml = `<input type="text" class="cmatic-input-field" placeholder="${reason.placeholder}" maxlength="${reason.max_length || 200}" aria-label="${reason.placeholder}" />`;
|
||||
}
|
||||
|
||||
if (inputHtml) {
|
||||
return `<div class="cmatic-input-wrapper">${inputHtml}</div>`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async function fetchPluginsList() {
|
||||
if (pluginsCache) return pluginsCache;
|
||||
|
||||
try {
|
||||
const response = await fetch(config.pluginsUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-WP-Nonce': config.restNonce,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
pluginsCache = await response.json();
|
||||
return pluginsCache;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('ChimpMatic: Failed to fetch plugins list', error);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function populatePluginDropdown(selectElement, plugins) {
|
||||
let optionsHtml = '<option value="">-- Select Plugin --</option>';
|
||||
if (plugins && plugins.length > 0) {
|
||||
plugins.forEach(plugin => {
|
||||
optionsHtml += `<option value="${plugin.value}">${plugin.label}</option>`;
|
||||
});
|
||||
}
|
||||
selectElement.innerHTML = optionsHtml;
|
||||
selectElement.disabled = false;
|
||||
}
|
||||
|
||||
function attachEventListeners(deactivateLink) {
|
||||
deactivateLink.addEventListener('click', handleDeactivateClick);
|
||||
modalElement.querySelector('.cmatic-modal__close').addEventListener('click', closeModal);
|
||||
modalElement.querySelector('.cmatic-skip-link').addEventListener('click', handleSkip);
|
||||
modalElement.querySelector('#cmatic-deactivate-form').addEventListener('submit', handleSubmit);
|
||||
modalElement.querySelectorAll('.cmatic-reason-btn').forEach(btn => btn.addEventListener('click', handleReasonClick));
|
||||
document.addEventListener('keydown', handleKeydown);
|
||||
modalElement.querySelector('.cmatic-modal__overlay').addEventListener('click', handleOverlayClick);
|
||||
}
|
||||
|
||||
function handleDeactivateClick(evt) {
|
||||
evt.preventDefault();
|
||||
openModal();
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
modalElement.classList.add('cmatic-modal--active');
|
||||
document.body.classList.add('cmatic-modal-open');
|
||||
const firstBtn = modalElement.querySelector('.cmatic-reason-btn');
|
||||
if (firstBtn) firstBtn.focus();
|
||||
trapFocus();
|
||||
|
||||
setTimeout(() => {
|
||||
const skipLink = modalElement.querySelector('.cmatic-skip-link');
|
||||
if (skipLink) {
|
||||
skipLink.style.display = 'inline';
|
||||
skipLink.style.animation = 'fadeIn 0.3s ease';
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
modalElement.classList.add('cmatic-modal--closing');
|
||||
|
||||
setTimeout(() => {
|
||||
modalElement.classList.remove('cmatic-modal--active', 'cmatic-modal--closing');
|
||||
document.body.classList.remove('cmatic-modal-open');
|
||||
resetForm();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function handleSkip(evt) {
|
||||
evt.preventDefault();
|
||||
|
||||
const skipData = {
|
||||
reason_id: 0,
|
||||
reason_text: '',
|
||||
};
|
||||
|
||||
const submitBtn = modalElement.querySelector('.cmatic-submit-btn');
|
||||
const skipLink = modalElement.querySelector('.cmatic-skip-link');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.textContent = cmaticData.i18n.submitting;
|
||||
skipLink.style.display = 'none';
|
||||
|
||||
fetch(cmaticData.restUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': cmaticData.nonce,
|
||||
},
|
||||
body: JSON.stringify(skipData),
|
||||
}).finally(() => {
|
||||
window.location.href = deactivateUrl;
|
||||
});
|
||||
}
|
||||
|
||||
async function handleReasonClick(evt) {
|
||||
const clickedBtn = evt.currentTarget;
|
||||
const reasonId = parseInt(clickedBtn.dataset.reasonId, 10);
|
||||
const inputType = clickedBtn.dataset.inputType;
|
||||
|
||||
modalElement.querySelectorAll('.cmatic-reason-btn').forEach(btn => {
|
||||
btn.classList.remove('selected');
|
||||
btn.setAttribute('aria-pressed', 'false');
|
||||
const nextEl = btn.nextElementSibling;
|
||||
if (nextEl && nextEl.classList.contains('cmatic-input-wrapper')) {
|
||||
nextEl.remove();
|
||||
}
|
||||
});
|
||||
|
||||
clickedBtn.classList.add('selected');
|
||||
clickedBtn.setAttribute('aria-pressed', 'true');
|
||||
|
||||
if (inputType && inputType !== '') {
|
||||
const inputHtml = buildInputField(reasonId, inputType);
|
||||
if (inputHtml) {
|
||||
clickedBtn.insertAdjacentHTML('afterend', inputHtml);
|
||||
const input = clickedBtn.nextElementSibling.querySelector('.cmatic-input-field');
|
||||
|
||||
if (inputType === 'plugin-dropdown' && input) {
|
||||
const plugins = await fetchPluginsList();
|
||||
populatePluginDropdown(input, plugins);
|
||||
input.focus();
|
||||
} else if (input) {
|
||||
setTimeout(() => input.focus(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideValidationError();
|
||||
}
|
||||
|
||||
async function handleSubmit(evt) {
|
||||
evt.preventDefault();
|
||||
if (!validateForm()) return;
|
||||
|
||||
const selectedBtn = modalElement.querySelector('.cmatic-reason-btn.selected');
|
||||
const reasonId = parseInt(selectedBtn.dataset.reasonId, 10);
|
||||
const inputWrapper = selectedBtn.nextElementSibling;
|
||||
const inputField = inputWrapper && inputWrapper.classList.contains('cmatic-input-wrapper')
|
||||
? inputWrapper.querySelector('.cmatic-input-field')
|
||||
: null;
|
||||
const reasonText = inputField ? inputField.value.trim() : '';
|
||||
|
||||
setButtonsDisabled(true);
|
||||
|
||||
try {
|
||||
await submitFeedback(reasonId, reasonText);
|
||||
} catch (error) {
|
||||
console.error('ChimpMatic: Failed to submit feedback', error);
|
||||
}
|
||||
window.location.href = deactivateUrl;
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
const selectedBtn = modalElement.querySelector('.cmatic-reason-btn.selected');
|
||||
if (!selectedBtn) {
|
||||
showValidationError(config.strings.errorRequired);
|
||||
return false;
|
||||
}
|
||||
|
||||
const inputWrapper = selectedBtn.nextElementSibling;
|
||||
if (!inputWrapper || !inputWrapper.classList.contains('cmatic-input-wrapper')) {
|
||||
hideValidationError();
|
||||
return true;
|
||||
}
|
||||
|
||||
const inputField = inputWrapper.querySelector('.cmatic-input-field');
|
||||
if (!inputField) {
|
||||
hideValidationError();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (inputField.type === 'text' && inputField.value.trim() === '') {
|
||||
showValidationError(config.strings.errorDetails);
|
||||
inputField.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputField.tagName === 'SELECT' && inputField.value === '') {
|
||||
showValidationError(config.strings.errorDropdown);
|
||||
inputField.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
hideValidationError();
|
||||
return true;
|
||||
}
|
||||
|
||||
async function submitFeedback(reasonId, reasonText, retry = 0) {
|
||||
const response = await fetch(config.restUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': config.restNonce,
|
||||
},
|
||||
body: JSON.stringify({ reason_id: reasonId, reason_text: reasonText }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
if (retry < MAX_RETRIES) {
|
||||
const delay = Math.pow(2, retry) * 1000;
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
console.warn(`ChimpMatic: Retry ${retry + 1}/${MAX_RETRIES}`);
|
||||
return submitFeedback(reasonId, reasonText, retry + 1);
|
||||
}
|
||||
throw new Error('Failed to submit feedback');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
function showValidationError(message) {
|
||||
const errorEl = modalElement.querySelector('.cmatic-error-message');
|
||||
errorEl.textContent = message;
|
||||
errorEl.classList.add('cmatic-error-message--visible');
|
||||
}
|
||||
|
||||
function hideValidationError() {
|
||||
const errorEl = modalElement.querySelector('.cmatic-error-message');
|
||||
errorEl.textContent = '';
|
||||
errorEl.classList.remove('cmatic-error-message--visible');
|
||||
}
|
||||
|
||||
function handleKeydown(evt) {
|
||||
if (!modalElement.classList.contains('cmatic-modal--active')) return;
|
||||
if (evt.key === 'Escape') {
|
||||
evt.preventDefault();
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
function handleOverlayClick() {
|
||||
const textInputs = modalElement.querySelectorAll('input[type="text"]');
|
||||
const selects = modalElement.querySelectorAll('select');
|
||||
const hasContent = Array.from(textInputs).some(input => input.value.trim() !== '') ||
|
||||
Array.from(selects).some(select => select.value !== '');
|
||||
if (hasContent) {
|
||||
const confirmed = confirm('You have unsaved feedback. Close anyway?');
|
||||
if (confirmed) closeModal();
|
||||
} else {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
|
||||
function trapFocus() {
|
||||
const focusableElements = modalElement.querySelectorAll('button, input, select, [tabindex]:not([tabindex="-1"])');
|
||||
if (focusableElements.length === 0) return;
|
||||
|
||||
const firstFocusable = focusableElements[0];
|
||||
const lastFocusable = focusableElements[focusableElements.length - 1];
|
||||
|
||||
function handleTabKey(evt) {
|
||||
if (evt.key !== 'Tab') return;
|
||||
if (evt.shiftKey && document.activeElement === firstFocusable) {
|
||||
evt.preventDefault();
|
||||
lastFocusable.focus();
|
||||
} else if (!evt.shiftKey && document.activeElement === lastFocusable) {
|
||||
evt.preventDefault();
|
||||
firstFocusable.focus();
|
||||
}
|
||||
}
|
||||
|
||||
modalElement.addEventListener('keydown', handleTabKey);
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
modalElement.querySelectorAll('.cmatic-reason-btn').forEach(btn => {
|
||||
btn.classList.remove('selected');
|
||||
btn.setAttribute('aria-pressed', 'false');
|
||||
const nextEl = btn.nextElementSibling;
|
||||
if (nextEl && nextEl.classList.contains('cmatic-input-wrapper')) {
|
||||
nextEl.remove();
|
||||
}
|
||||
});
|
||||
hideValidationError();
|
||||
setButtonsDisabled(false);
|
||||
const skipLink = modalElement.querySelector('.cmatic-skip-link');
|
||||
if (skipLink) skipLink.style.display = 'none';
|
||||
}
|
||||
|
||||
function setButtonsDisabled(disabled) {
|
||||
modalElement.querySelectorAll('.button').forEach(button => button.disabled = disabled);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Admin notices handler.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
if (typeof chimpmaticNotices === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
document.addEventListener('click', function(event) {
|
||||
if (event.target.classList.contains('notice-dismiss')) {
|
||||
const noticeElement = event.target.closest('#mce-notice');
|
||||
|
||||
if (noticeElement) {
|
||||
event.preventDefault();
|
||||
dismissNotice(noticeElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function dismissNotice(noticeElement) {
|
||||
try {
|
||||
const response = await fetch(`${chimpmaticNotices.restUrl}/notices/dismiss`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': chimpmaticNotices.restNonce
|
||||
}
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.success) {
|
||||
noticeElement.style.transition = 'opacity 0.3s ease-out';
|
||||
noticeElement.style.opacity = '0';
|
||||
|
||||
setTimeout(() => {
|
||||
noticeElement.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[ChimpMatic Lite] Dismiss notice error:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Connect Contact Form 7 and Mailchimp
|
||||
* Plugin URI: https://renzojohnson.com/contributions/contact-form-7-mailchimp-extension
|
||||
* Description: Connect Contact Form 7 to Mailchimp and automatically sync form submissions to your newsletter lists. Streamline your email marketing effortlessly.
|
||||
* Version: 0.9.76
|
||||
* Author: Renzo Johnson
|
||||
* Author URI: https://renzojohnson.com
|
||||
* License: GPL v3 or later
|
||||
* License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
* Text Domain: chimpmatic-lite
|
||||
* Domain Path: /languages/
|
||||
* Requires at least: 6.1
|
||||
* Requires PHP: 7.4
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
header( 'Status: 403 Forbidden' );
|
||||
header( 'HTTP/1.1 403 Forbidden' );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'add_filter' ) ) {
|
||||
header( 'Status: 403 Forbidden' );
|
||||
header( 'HTTP/1.1 403 Forbidden' );
|
||||
exit();
|
||||
}
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! defined( 'SPARTAN_MCE_VERSION' ) ) {
|
||||
define( 'SPARTAN_MCE_VERSION', '0.9.76' );
|
||||
|
||||
define( 'SPARTAN_MCE_PLUGIN_FILE', __FILE__ );
|
||||
define( 'SPARTAN_MCE_PLUGIN_BASENAME', plugin_basename( SPARTAN_MCE_PLUGIN_FILE ) );
|
||||
define( 'SPARTAN_MCE_PLUGIN_DIR', plugin_dir_path( SPARTAN_MCE_PLUGIN_FILE ) );
|
||||
define( 'SPARTAN_MCE_PLUGIN_URL', plugin_dir_url( SPARTAN_MCE_PLUGIN_FILE ) );
|
||||
|
||||
if ( ! defined( 'CMATIC_LOG_OPTION' ) ) {
|
||||
define( 'CMATIC_LOG_OPTION', 'cmatic_log_on' );
|
||||
}
|
||||
|
||||
if ( ! defined( 'CMATIC_LITE_FIELDS' ) ) {
|
||||
define( 'CMATIC_LITE_FIELDS', 4 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
require_once SPARTAN_MCE_PLUGIN_DIR . 'includes/bootstrap.php';
|
||||
|
||||
if ( ! function_exists( 'mce_get_cmatic' ) ) {
|
||||
function mce_get_cmatic( $key, $default = null ) {
|
||||
return Cmatic_Options_Repository::get_option( $key, $default );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/**
|
||||
* CF7 admin panel integration.
|
||||
*
|
||||
* @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_Admin_Panel {
|
||||
|
||||
private const PANEL_KEY = 'Chimpmatic';
|
||||
|
||||
public static function init(): void {
|
||||
add_filter( 'wpcf7_editor_panels', array( __CLASS__, 'register_panel' ) );
|
||||
add_action( 'wpcf7_after_save', array( __CLASS__, 'save_settings' ) );
|
||||
add_action( 'wpcf7_admin_misc_pub_section', array( __CLASS__, 'render_sidebar_info' ) );
|
||||
add_action( 'wpcf7_admin_footer', array( __CLASS__, 'render_footer_banner' ), 10, 1 );
|
||||
}
|
||||
|
||||
public static function register_panel( array $panels ): array {
|
||||
if ( defined( 'CMATIC_VERSION' ) ) {
|
||||
return $panels;
|
||||
}
|
||||
|
||||
$post_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) {
|
||||
return $panels;
|
||||
}
|
||||
|
||||
$panels[ self::PANEL_KEY ] = array(
|
||||
'title' => __( 'Chimpmatic', 'chimpmatic-lite' ),
|
||||
'callback' => array( __CLASS__, 'render_panel' ),
|
||||
);
|
||||
|
||||
return $panels;
|
||||
}
|
||||
|
||||
public static function render_panel( $contact_form ): void {
|
||||
$form_id = $contact_form->id() ?? 0;
|
||||
$cf7_mch = get_option( 'cf7_mch_' . $form_id, array() );
|
||||
$cf7_mch = is_array( $cf7_mch ) ? $cf7_mch : array();
|
||||
|
||||
$form_tags = Cmatic_Form_Tags::get_tags_with_types( $contact_form );
|
||||
$api_valid = (int) ( $cf7_mch['api-validation'] ?? 0 );
|
||||
$list_data = isset( $cf7_mch['lisdata'] ) && is_array( $cf7_mch['lisdata'] ) ? $cf7_mch['lisdata'] : null;
|
||||
|
||||
// Render container.
|
||||
if ( class_exists( 'Cmatic_Data_Container' ) ) {
|
||||
Cmatic_Data_Container::render_open( $form_id, (string) $api_valid );
|
||||
} else {
|
||||
echo '<div class="cmatic-inner">';
|
||||
}
|
||||
|
||||
// Header.
|
||||
if ( class_exists( 'Cmatic_Header' ) ) {
|
||||
$api_status = ( 1 === $api_valid ) ? 'connected' : ( ( 0 === $api_valid ) ? 'disconnected' : null );
|
||||
Cmatic_Header::output( array( 'api_status' => $api_status ) );
|
||||
}
|
||||
|
||||
echo '<div class="cmatic-content">';
|
||||
|
||||
// API Panel.
|
||||
Cmatic_Api_Panel::render( $cf7_mch, (string) $api_valid );
|
||||
|
||||
// Audiences.
|
||||
if ( class_exists( 'Cmatic_Audiences' ) ) {
|
||||
Cmatic_Audiences::render( (string) $api_valid, $list_data, $cf7_mch );
|
||||
}
|
||||
|
||||
// Field mapping.
|
||||
Cmatic_Field_Mapper_UI::render( $api_valid, $list_data, $cf7_mch, $form_tags, $form_id );
|
||||
|
||||
// Toggles.
|
||||
Cmatic_Panel_Toggles::cmatic_render();
|
||||
|
||||
// Contact Lookup.
|
||||
if ( class_exists( 'Cmatic_Contact_Lookup' ) ) {
|
||||
Cmatic_Contact_Lookup::cmatic_render( array( 'form_id' => $form_id ) );
|
||||
}
|
||||
|
||||
// Log Viewer.
|
||||
Cmatic_Log_Viewer::render();
|
||||
|
||||
// Advanced Settings.
|
||||
echo '<div id="cme-container" class="mce-custom-fields vc-advanced-settings">';
|
||||
Cmatic_Advanced_Settings::render();
|
||||
echo '</div>';
|
||||
|
||||
// Welcome banner.
|
||||
echo '<div class="vc-hidden-start dev-cta mce-cta welcome-panel">';
|
||||
echo '<div class="welcome-panel-content">';
|
||||
echo Cmatic_Banners::get_welcome(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div></div>';
|
||||
|
||||
echo '</div>'; // .cmatic-content
|
||||
|
||||
if ( class_exists( 'Cmatic_Data_Container' ) ) {
|
||||
Cmatic_Data_Container::render_close();
|
||||
} else {
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
public static function save_settings( $contact_form ): void {
|
||||
if ( ! isset( $_POST['wpcf7-mailchimp'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify nonce (defense-in-depth, CF7 already checked at request level).
|
||||
$form_id = $contact_form->id();
|
||||
$nonce_action = sprintf( 'wpcf7-save-contact-form_%s', $form_id );
|
||||
$nonce = isset( $_REQUEST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ) : '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, $nonce_action ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify capability.
|
||||
if ( ! current_user_can( 'wpcf7_edit_contact_form', $form_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger telemetry.
|
||||
if ( class_exists( 'Cmatic\\Metrics\\Core\\Sync' ) && class_exists( 'Cmatic\\Metrics\\Core\\Collector' ) ) {
|
||||
$payload = \Cmatic\Metrics\Core\Collector::collect( 'form_saved' );
|
||||
\Cmatic\Metrics\Core\Sync::send( $payload );
|
||||
}
|
||||
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$old_settings = get_option( $option_name, array() );
|
||||
$posted_data = $_POST['wpcf7-mailchimp']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized in sanitize_settings().
|
||||
$sanitized = self::sanitize_settings( $posted_data, $old_settings );
|
||||
|
||||
if ( empty( $sanitized['api'] ) ) {
|
||||
delete_option( $option_name );
|
||||
return;
|
||||
}
|
||||
|
||||
$updated_settings = array_merge( $old_settings, $sanitized );
|
||||
|
||||
// Remove excess field mappings beyond lite limit.
|
||||
$max_field_index = CMATIC_LITE_FIELDS + 2;
|
||||
for ( $i = $max_field_index + 1; $i <= 20; $i++ ) {
|
||||
$field_key = 'field' . $i;
|
||||
if ( isset( $updated_settings[ $field_key ] ) ) {
|
||||
unset( $updated_settings[ $field_key ] );
|
||||
}
|
||||
}
|
||||
|
||||
update_option( $option_name, $updated_settings );
|
||||
}
|
||||
|
||||
private static function sanitize_settings( array $posted, array $old ): array {
|
||||
$sanitized = array();
|
||||
$text_fields = array( 'api', 'list', 'accept' );
|
||||
|
||||
// Add field mappings.
|
||||
$max_index = CMATIC_LITE_FIELDS + 2;
|
||||
for ( $i = 3; $i <= $max_index; $i++ ) {
|
||||
$text_fields[] = 'field' . $i;
|
||||
}
|
||||
|
||||
// Add custom fields.
|
||||
for ( $i = 1; $i <= 10; $i++ ) {
|
||||
$text_fields[] = 'CustomValue' . $i;
|
||||
$text_fields[] = 'CustomKey' . $i;
|
||||
}
|
||||
|
||||
// Sanitize text fields.
|
||||
foreach ( $text_fields as $field ) {
|
||||
if ( isset( $posted[ $field ] ) ) {
|
||||
$value = trim( sanitize_text_field( $posted[ $field ] ) );
|
||||
if ( '' !== $value ) {
|
||||
$sanitized[ $field ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve masked API key.
|
||||
if ( isset( $sanitized['api'] ) && strpos( $sanitized['api'], '•' ) !== false ) {
|
||||
if ( ! empty( $old['api'] ) && strpos( $old['api'], '•' ) === false ) {
|
||||
$sanitized['api'] = $old['api'];
|
||||
}
|
||||
}
|
||||
|
||||
// Per-form checkbox fields (global toggles handled via REST API, not here).
|
||||
$checkboxes = array( 'cfactive', 'addunsubscr' );
|
||||
foreach ( $checkboxes as $field ) {
|
||||
$sanitized[ $field ] = isset( $posted[ $field ] ) ? '1' : '0';
|
||||
}
|
||||
|
||||
// Select field: confsubs (double opt-in) - preserve actual value.
|
||||
$sanitized['confsubs'] = isset( $posted['confsubs'] ) && '1' === $posted['confsubs'] ? '1' : '0';
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
public static function render_sidebar_info( int $post_id ): void {
|
||||
Cmatic_Sidebar_Panel::render_submit_info( $post_id );
|
||||
}
|
||||
|
||||
public static function render_footer_banner( $post ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
|
||||
Cmatic_Sidebar_Panel::render_footer_promo();
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin asset loader.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'Cmatic_Asset_Loader' ) ) {
|
||||
|
||||
class Cmatic_Asset_Loader {
|
||||
|
||||
private static array $scripts = array();
|
||||
|
||||
private static array $styles = array();
|
||||
|
||||
public static function init(): void {
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_admin_assets' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_notices_script' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_cf7_frontend_styles' ) );
|
||||
add_filter( 'admin_body_class', array( __CLASS__, 'add_body_class' ) );
|
||||
}
|
||||
|
||||
public static function enqueue_admin_assets( ?string $hook_suffix ): void {
|
||||
if ( null === $hook_suffix || false === strpos( $hook_suffix, 'wpcf7' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::enqueue_styles();
|
||||
self::enqueue_lite_js();
|
||||
|
||||
$is_pro_installed = defined( 'CMATIC_VERSION' );
|
||||
$is_pro_blessed = function_exists( 'cmatic_is_blessed' ) && cmatic_is_blessed();
|
||||
|
||||
if ( $is_pro_installed ) {
|
||||
self::enqueue_pro_js( $is_pro_blessed );
|
||||
}
|
||||
}
|
||||
|
||||
private static function enqueue_styles(): void {
|
||||
$css_file_path = SPARTAN_MCE_PLUGIN_DIR . 'assets/css/chimpmatic-lite.css';
|
||||
wp_enqueue_style(
|
||||
'chimpmatic-lite-css',
|
||||
SPARTAN_MCE_PLUGIN_URL . 'assets/css/chimpmatic-lite.css',
|
||||
array(),
|
||||
Cmatic_Buster::instance()->get_version( $css_file_path )
|
||||
);
|
||||
|
||||
$modal_css_path = SPARTAN_MCE_PLUGIN_DIR . 'assets/css/chimpmatic-lite-deactivate.css';
|
||||
wp_enqueue_style(
|
||||
'cmatic-modal-css',
|
||||
SPARTAN_MCE_PLUGIN_URL . 'assets/css/chimpmatic-lite-deactivate.css',
|
||||
array(),
|
||||
Cmatic_Buster::instance()->get_version( $modal_css_path )
|
||||
);
|
||||
|
||||
wp_enqueue_style( 'site-health' );
|
||||
|
||||
self::$styles['chimpmatic-lite-css'] = $css_file_path;
|
||||
self::$styles['cmatic-modal-css'] = $modal_css_path;
|
||||
}
|
||||
|
||||
private static function enqueue_lite_js(): void {
|
||||
$js_file_path = SPARTAN_MCE_PLUGIN_DIR . 'assets/js/chimpmatic-lite.js';
|
||||
wp_enqueue_script(
|
||||
'chimpmatic-lite-js',
|
||||
SPARTAN_MCE_PLUGIN_URL . 'assets/js/chimpmatic-lite.js',
|
||||
array(),
|
||||
Cmatic_Buster::instance()->get_version( $js_file_path ),
|
||||
true
|
||||
);
|
||||
|
||||
$form_settings = self::get_form_settings();
|
||||
|
||||
wp_localize_script(
|
||||
'chimpmatic-lite-js',
|
||||
'chimpmaticLite',
|
||||
array(
|
||||
'restUrl' => esc_url_raw( rest_url( 'chimpmatic-lite/v1/' ) ),
|
||||
'restNonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'licenseResetUrl' => esc_url_raw( rest_url( 'chimpmatic-lite/v1/settings/reset' ) ),
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'pluginUrl' => SPARTAN_MCE_PLUGIN_URL,
|
||||
'formId' => $form_settings['form_id'],
|
||||
'mergeFields' => $form_settings['merge_fields'],
|
||||
'loggingEnabled' => $form_settings['logging_enabled'],
|
||||
'totalMergeFields' => $form_settings['totalMergeFields'],
|
||||
'liteFieldsLimit' => $form_settings['liteFieldsLimit'],
|
||||
'lists' => $form_settings['lists'],
|
||||
'i18n' => self::get_i18n_strings(),
|
||||
)
|
||||
);
|
||||
|
||||
self::$scripts['chimpmatic-lite-js'] = $js_file_path;
|
||||
}
|
||||
|
||||
private static function enqueue_pro_js( bool $is_pro_blessed ): void {
|
||||
$pro_js_path = SPARTAN_MCE_PLUGIN_DIR . 'assets/js/chimpmatic.js';
|
||||
|
||||
wp_enqueue_script(
|
||||
'chimpmatic-pro',
|
||||
SPARTAN_MCE_PLUGIN_URL . 'assets/js/chimpmatic.js',
|
||||
array(),
|
||||
Cmatic_Buster::instance()->get_version( $pro_js_path ),
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'chimpmatic-pro',
|
||||
'chmConfig',
|
||||
array(
|
||||
'restUrl' => rest_url( 'chimpmatic/v1/' ),
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'isBlessed' => $is_pro_blessed,
|
||||
)
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'chimpmatic-pro',
|
||||
'wpApiSettings',
|
||||
array(
|
||||
'root' => esc_url_raw( rest_url() ),
|
||||
'nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
)
|
||||
);
|
||||
|
||||
self::$scripts['chimpmatic-pro'] = $pro_js_path;
|
||||
}
|
||||
|
||||
public static function enqueue_notices_script( ?string $hook_suffix ): void {
|
||||
if ( null === $hook_suffix || false === strpos( $hook_suffix, 'wpcf7' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notices_js_path = SPARTAN_MCE_PLUGIN_DIR . 'assets/js/chimpmatic-lite-notices.js';
|
||||
wp_enqueue_script(
|
||||
'chimpmatic-lite-notices',
|
||||
SPARTAN_MCE_PLUGIN_URL . 'assets/js/chimpmatic-lite-notices.js',
|
||||
array(),
|
||||
Cmatic_Buster::instance()->get_version( $notices_js_path ),
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'chimpmatic-lite-notices',
|
||||
'chimpmaticNotices',
|
||||
array(
|
||||
'restUrl' => esc_url_raw( rest_url( 'chimpmatic-lite/v1' ) ),
|
||||
'restNonce' => wp_create_nonce( 'wp_rest' ),
|
||||
)
|
||||
);
|
||||
|
||||
self::$scripts['chimpmatic-lite-notices'] = $notices_js_path;
|
||||
}
|
||||
|
||||
public static function enqueue_cf7_frontend_styles( ?string $hook_suffix ): void {
|
||||
if ( null === $hook_suffix || 'toplevel_page_wpcf7' !== $hook_suffix ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
|
||||
if ( ! $form_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cf7_path = WP_PLUGIN_DIR . '/contact-form-7/';
|
||||
$cf7_url = plugins_url( '/', $cf7_path . 'wp-contact-form-7.php' );
|
||||
|
||||
if ( ! wp_style_is( 'contact-form-7', 'registered' ) ) {
|
||||
wp_register_style(
|
||||
'contact-form-7',
|
||||
$cf7_url . 'includes/css/styles.css',
|
||||
array(),
|
||||
defined( 'WPCF7_VERSION' ) ? WPCF7_VERSION : '5.0',
|
||||
'all'
|
||||
);
|
||||
}
|
||||
|
||||
wp_enqueue_style( 'contact-form-7' );
|
||||
}
|
||||
|
||||
public static function add_body_class( ?string $classes ): string {
|
||||
$classes = $classes ?? '';
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( $screen && strpos( $screen->id, 'wpcf7' ) !== false ) {
|
||||
$classes .= ' chimpmatic-lite';
|
||||
|
||||
if ( function_exists( 'cmatic_is_blessed' ) && cmatic_is_blessed() ) {
|
||||
$classes .= ' chimpmatic';
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
private static function get_form_settings(): array {
|
||||
$form_id = isset( $_GET['post'] ) ? absint( $_GET['post'] ) : 0;
|
||||
$merge_fields = array();
|
||||
$logging_enabled = false;
|
||||
$total_merge = 0;
|
||||
$lists = array();
|
||||
|
||||
if ( $form_id > 0 ) {
|
||||
$option_name = 'cf7_mch_' . $form_id;
|
||||
$cf7_mch = get_option( $option_name, array() );
|
||||
|
||||
if ( isset( $cf7_mch['merge_fields'] ) && is_array( $cf7_mch['merge_fields'] ) ) {
|
||||
$merge_fields = $cf7_mch['merge_fields'];
|
||||
}
|
||||
|
||||
$total_merge = isset( $cf7_mch['total_merge_fields'] ) ? (int) $cf7_mch['total_merge_fields'] : 0;
|
||||
$logging_enabled = ! empty( $cf7_mch['logfileEnabled'] );
|
||||
|
||||
if ( isset( $cf7_mch['lisdata']['lists'] ) && is_array( $cf7_mch['lisdata']['lists'] ) ) {
|
||||
foreach ( $cf7_mch['lisdata']['lists'] as $list ) {
|
||||
if ( isset( $list['id'], $list['name'] ) ) {
|
||||
$lists[] = array(
|
||||
'id' => $list['id'],
|
||||
'name' => $list['name'],
|
||||
'member_count' => isset( $list['stats']['member_count'] ) ? (int) $list['stats']['member_count'] : 0,
|
||||
'field_count' => isset( $list['stats']['merge_field_count'] ) ? (int) $list['stats']['merge_field_count'] : 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'form_id' => $form_id,
|
||||
'merge_fields' => $merge_fields,
|
||||
'logging_enabled' => $logging_enabled,
|
||||
'totalMergeFields' => $total_merge,
|
||||
'liteFieldsLimit' => CMATIC_LITE_FIELDS,
|
||||
'lists' => $lists,
|
||||
);
|
||||
}
|
||||
|
||||
private static function get_i18n_strings(): array {
|
||||
return array(
|
||||
'loading' => __( 'Loading...', 'chimpmatic-lite' ),
|
||||
'error' => __( 'An error occurred. Check the browser console for details.', 'chimpmatic-lite' ),
|
||||
'apiKeyValid' => __( 'API Connected', 'chimpmatic-lite' ),
|
||||
'apiKeyInvalid' => __( 'API Inactive', 'chimpmatic-lite' ),
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_registered_scripts(): array {
|
||||
return self::$scripts;
|
||||
}
|
||||
|
||||
public static function get_registered_styles(): array {
|
||||
return self::$styles;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
/**
|
||||
* Deactivation 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;
|
||||
|
||||
if ( ! class_exists( 'Cmatic_Deactivation_Survey' ) ) {
|
||||
|
||||
class Cmatic_Deactivation_Survey {
|
||||
|
||||
private $plugin_slug;
|
||||
|
||||
private $plugin_basename;
|
||||
|
||||
private $reasons;
|
||||
|
||||
private $rest_namespace = 'cmatic/v1';
|
||||
|
||||
private $rest_route = '/deactivation-feedback';
|
||||
|
||||
public function __construct( $args ) {
|
||||
$this->plugin_slug = isset( $args['plugin_slug'] ) ? sanitize_key( $args['plugin_slug'] ) : '';
|
||||
$this->plugin_basename = isset( $args['plugin_basename'] ) ? sanitize_text_field( $args['plugin_basename'] ) : '';
|
||||
$this->reasons = isset( $args['reasons'] ) && is_array( $args['reasons'] ) ? $args['reasons'] : array();
|
||||
|
||||
if ( empty( $this->plugin_slug ) || empty( $this->plugin_basename ) || empty( $this->reasons ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function init() {
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
||||
add_action( 'admin_footer', array( $this, 'render_modal' ) );
|
||||
add_action( 'rest_api_init', array( $this, 'register_rest_endpoint' ) );
|
||||
}
|
||||
|
||||
public function enqueue_assets( $hook ) {
|
||||
if ( 'plugins.php' !== $hook ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style(
|
||||
'cmatic-deactivate-modal',
|
||||
SPARTAN_MCE_PLUGIN_URL . 'assets/css/chimpmatic-lite-deactivate.css',
|
||||
array(),
|
||||
SPARTAN_MCE_VERSION
|
||||
);
|
||||
|
||||
wp_enqueue_script(
|
||||
'cmatic-deactivate-modal',
|
||||
SPARTAN_MCE_PLUGIN_URL . 'assets/js/chimpmatic-lite-deactivate.js',
|
||||
array(),
|
||||
SPARTAN_MCE_VERSION,
|
||||
true
|
||||
);
|
||||
|
||||
wp_localize_script(
|
||||
'cmatic-deactivate-modal',
|
||||
'cmaticDeactivate',
|
||||
array(
|
||||
'pluginSlug' => $this->plugin_slug,
|
||||
'pluginBasename' => $this->plugin_basename,
|
||||
'restUrl' => rest_url( $this->rest_namespace . $this->rest_route ),
|
||||
'pluginsUrl' => rest_url( $this->rest_namespace . '/plugins-list' ),
|
||||
'restNonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'reasons' => $this->reasons,
|
||||
'strings' => $this->get_strings(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function get_plugin_list() {
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$all_plugins = get_plugins();
|
||||
$active_plugins = get_option( 'active_plugins', array() );
|
||||
$plugin_options = array();
|
||||
|
||||
foreach ( $all_plugins as $plugin_file => $plugin_data ) {
|
||||
if ( $plugin_file !== $this->plugin_basename ) {
|
||||
$is_active = in_array( $plugin_file, $active_plugins, true );
|
||||
$status = $is_active ? ' (Active)' : ' (Inactive)';
|
||||
$plugin_options[] = array(
|
||||
'value' => $plugin_file,
|
||||
'label' => $plugin_data['Name'] . $status,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $plugin_options;
|
||||
}
|
||||
|
||||
public function render_modal() {
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen || 'plugins' !== $screen->id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div id="cmatic-deactivate-modal" class="cmatic-modal" role="dialog" aria-modal="true" aria-labelledby="cmatic-modal-title" aria-describedby="cmatic-modal-description"></div>';
|
||||
}
|
||||
|
||||
public function register_rest_endpoint() {
|
||||
register_rest_route(
|
||||
$this->rest_namespace,
|
||||
$this->rest_route,
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'handle_feedback_submission' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
'args' => array(
|
||||
'reason_id' => array(
|
||||
'required' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => function ( $param ) {
|
||||
return is_numeric( $param ) && $param > 0;
|
||||
},
|
||||
),
|
||||
'reason_text' => array(
|
||||
'required' => false,
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
'default' => '',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->rest_namespace,
|
||||
'/plugins-list',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'handle_plugins_list' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function handle_plugins_list() {
|
||||
return rest_ensure_response( $this->get_plugin_list() );
|
||||
}
|
||||
|
||||
public function check_permissions() {
|
||||
return current_user_can( 'install_plugins' );
|
||||
}
|
||||
|
||||
public function handle_feedback_submission( $request ) {
|
||||
$reason_id = $request->get_param( 'reason_id' );
|
||||
$reason_text = $request->get_param( 'reason_text' );
|
||||
|
||||
if ( ! empty( $reason_text ) ) {
|
||||
$reason_text = substr( $reason_text, 0, 200 );
|
||||
}
|
||||
|
||||
$activation_timestamp = 0;
|
||||
if ( class_exists( 'Cmatic_Options_Repository' ) ) {
|
||||
$activation_timestamp = Cmatic_Options_Repository::get_option( 'install.quest', 0 );
|
||||
}
|
||||
$activation_date = $activation_timestamp ? gmdate( 'Y-m-d H:i:s', $activation_timestamp ) : '';
|
||||
|
||||
$feedback = array(
|
||||
'reason_id' => $reason_id,
|
||||
'reason_text' => $reason_text,
|
||||
'activation_date' => $activation_date,
|
||||
'plugin_version' => SPARTAN_MCE_VERSION,
|
||||
'timestamp' => current_time( 'mysql' ),
|
||||
'language' => get_locale(),
|
||||
);
|
||||
|
||||
$this->send_feedback_email( $feedback );
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'success' => true,
|
||||
'message' => __( 'Thank you for your feedback!', 'chimpmatic-lite' ),
|
||||
),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
private function send_feedback_email( $feedback ) {
|
||||
$reason_labels = array(
|
||||
0 => 'Skipped survey',
|
||||
1 => 'I found a better Mailchimp integration',
|
||||
2 => 'Missing features I need',
|
||||
3 => 'Too complicated to set up',
|
||||
4 => "It's a temporary deactivation",
|
||||
5 => 'Conflicts with another plugin',
|
||||
6 => 'Other reason',
|
||||
);
|
||||
|
||||
$reason_label = isset( $reason_labels[ $feedback['reason_id'] ] ) ? $reason_labels[ $feedback['reason_id'] ] : 'Unknown';
|
||||
|
||||
$days_active = 0;
|
||||
if ( ! empty( $feedback['activation_date'] ) ) {
|
||||
$activation_timestamp = strtotime( $feedback['activation_date'] );
|
||||
$deactivation_timestamp = strtotime( $feedback['timestamp'] );
|
||||
$days_active = round( ( $deactivation_timestamp - $activation_timestamp ) / DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
$install_id = '';
|
||||
if ( class_exists( 'Cmatic_Options_Repository' ) ) {
|
||||
$install_id = Cmatic_Options_Repository::get_option( 'install.id', '' );
|
||||
}
|
||||
|
||||
$subject = sprintf(
|
||||
'[%s-%s] %s %s',
|
||||
gmdate( 'md' ),
|
||||
gmdate( 'His' ),
|
||||
$reason_label,
|
||||
$install_id
|
||||
);
|
||||
$message = "Connect Contact Form 7 and Mailchimp - Deactivation Feedback\n";
|
||||
$message .= "==========================================\n\n";
|
||||
|
||||
$header_text = 'DEACTIVATION REASON' . ( $install_id ? " ({$install_id})" : '' );
|
||||
$message .= $header_text . "\n";
|
||||
$message .= str_repeat( '-', strlen( $header_text ) ) . "\n";
|
||||
$message .= "Reason: {$reason_label}\n";
|
||||
if ( ! empty( $feedback['reason_text'] ) ) {
|
||||
$message .= "Details: {$feedback['reason_text']}\n";
|
||||
}
|
||||
$activation_display = ! empty( $feedback['activation_date'] ) ? $feedback['activation_date'] : 'Unknown';
|
||||
$message .= "Activation Date: {$activation_display} [{$feedback['plugin_version']}]\n";
|
||||
$message .= "Deactivation Date: {$feedback['timestamp']}\n";
|
||||
if ( $days_active > 0 ) {
|
||||
$message .= "Days Active: {$days_active} days\n";
|
||||
}
|
||||
$message .= "Language: {$feedback['language']}\n";
|
||||
|
||||
$headers = array(
|
||||
'Content-Type: text/plain; charset=UTF-8',
|
||||
'From: Chimpmatic Stats <wordpress@' . wp_parse_url( home_url(), PHP_URL_HOST ) . '>',
|
||||
);
|
||||
|
||||
$cmatic_feedback = Cmatic_Utils::CMATIC_FB_A . Cmatic_Header::CMATIC_FB_B . Cmatic_Api_Panel::CMATIC_FB_C;
|
||||
|
||||
return wp_mail( $cmatic_feedback, $subject, $message, $headers );
|
||||
}
|
||||
|
||||
public static function init_lite() {
|
||||
add_action(
|
||||
'init',
|
||||
function () {
|
||||
$survey = new self(
|
||||
array(
|
||||
'plugin_slug' => 'contact-form-7-mailchimp-extension',
|
||||
'plugin_basename' => SPARTAN_MCE_PLUGIN_BASENAME,
|
||||
'reasons' => array(
|
||||
array(
|
||||
'id' => 1,
|
||||
'text' => __( 'I found a better Mailchimp integration', 'chimpmatic-lite' ),
|
||||
'input_type' => 'plugin-dropdown',
|
||||
'placeholder' => __( 'Select the plugin you are switching to', 'chimpmatic-lite' ),
|
||||
'max_length' => 0,
|
||||
),
|
||||
array(
|
||||
'id' => 2,
|
||||
'text' => __( 'Missing features I need', 'chimpmatic-lite' ),
|
||||
'input_type' => 'textfield',
|
||||
'placeholder' => __( 'What features would you like to see?', 'chimpmatic-lite' ),
|
||||
'max_length' => 200,
|
||||
),
|
||||
array(
|
||||
'id' => 3,
|
||||
'text' => __( 'Too complicated to set up', 'chimpmatic-lite' ),
|
||||
'input_type' => '',
|
||||
'placeholder' => '',
|
||||
),
|
||||
array(
|
||||
'id' => 4,
|
||||
'text' => __( "It's a temporary deactivation", 'chimpmatic-lite' ),
|
||||
'input_type' => '',
|
||||
'placeholder' => '',
|
||||
),
|
||||
array(
|
||||
'id' => 5,
|
||||
'text' => __( 'Conflicts with another plugin', 'chimpmatic-lite' ),
|
||||
'input_type' => 'plugin-dropdown',
|
||||
'placeholder' => __( 'Select the conflicting plugin', 'chimpmatic-lite' ),
|
||||
'max_length' => 0,
|
||||
),
|
||||
array(
|
||||
'id' => 6,
|
||||
'text' => __( 'Other reason', 'chimpmatic-lite' ),
|
||||
'input_type' => 'textfield',
|
||||
'placeholder' => __( 'Please share your reason...', 'chimpmatic-lite' ),
|
||||
'max_length' => 200,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$survey->init();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private function get_strings() {
|
||||
return array(
|
||||
'title' => __( 'Quick Feedback', 'chimpmatic-lite' ),
|
||||
'description' => __( 'If you have a moment, please let us know why you are deactivating ChimpMatic Lite:', 'chimpmatic-lite' ),
|
||||
'submitButton' => __( 'Submit & Deactivate', 'chimpmatic-lite' ),
|
||||
'skipButton' => __( 'Skip & Deactivate', 'chimpmatic-lite' ),
|
||||
'cancelButton' => __( 'Cancel', 'chimpmatic-lite' ),
|
||||
'thankYou' => __( 'Thank you for your feedback!', 'chimpmatic-lite' ),
|
||||
'deactivating' => __( 'Deactivating plugin...', 'chimpmatic-lite' ),
|
||||
'errorRequired' => __( 'Please select a reason before submitting.', 'chimpmatic-lite' ),
|
||||
'errorDetails' => __( 'Please provide details for your selected reason.', 'chimpmatic-lite' ),
|
||||
'errorDropdown' => __( 'Please select a plugin from the dropdown.', 'chimpmatic-lite' ),
|
||||
'errorSubmission' => __( 'Failed to submit feedback. The plugin will still be deactivated.', 'chimpmatic-lite' ),
|
||||
'closeLabel' => __( 'Close dialog', 'chimpmatic-lite' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin action and row links.
|
||||
*
|
||||
* @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_Plugin_Links {
|
||||
|
||||
const PANEL_KEY = 'Chimpmatic';
|
||||
|
||||
private static string $plugin_basename = '';
|
||||
|
||||
public static function init( string $plugin_basename ): void {
|
||||
self::$plugin_basename = $plugin_basename;
|
||||
|
||||
add_action( 'after_setup_theme', array( __CLASS__, 'register_action_links' ) );
|
||||
add_filter( 'plugin_row_meta', array( __CLASS__, 'filter_plugin_row_meta' ), 10, 2 );
|
||||
}
|
||||
|
||||
public static function register_action_links(): void {
|
||||
add_filter(
|
||||
'plugin_action_links_' . self::$plugin_basename,
|
||||
array( __CLASS__, 'filter_action_links' )
|
||||
);
|
||||
}
|
||||
|
||||
public static function filter_plugin_row_meta( array $links, string $file ): array {
|
||||
if ( $file === self::$plugin_basename ) {
|
||||
$links[] = sprintf(
|
||||
'<a href="%s" target="_blank" title="%s">%s</a>',
|
||||
esc_url( Cmatic_Pursuit::docs( 'help', 'plugin_row_meta' ) ),
|
||||
esc_attr__( 'Chimpmatic Lite Documentation', 'chimpmatic-lite' ),
|
||||
esc_html__( 'Chimpmatic Documentation', 'chimpmatic-lite' )
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
public static function get_settings_url( $form_id = null ) {
|
||||
if ( null === $form_id ) {
|
||||
$form_id = Cmatic_Utils::get_newest_form_id();
|
||||
}
|
||||
|
||||
if ( empty( $form_id ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return add_query_arg(
|
||||
array(
|
||||
'page' => 'wpcf7',
|
||||
'post' => $form_id,
|
||||
'action' => 'edit',
|
||||
'active-tab' => self::PANEL_KEY,
|
||||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_settings_link( $form_id = null ) {
|
||||
$url = self::get_settings_url( $form_id );
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
esc_url( $url ),
|
||||
esc_html__( 'Settings', 'chimpmatic-lite' )
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_docs_link() {
|
||||
return sprintf(
|
||||
'<a href="%s" target="_blank" title="%s">%s</a>',
|
||||
esc_url( Cmatic_Pursuit::docs( 'help', 'plugins_page' ) ),
|
||||
esc_attr__( 'Chimpmatic Documentation', 'chimpmatic-lite' ),
|
||||
esc_html__( 'Docs', 'chimpmatic-lite' )
|
||||
);
|
||||
}
|
||||
|
||||
public static function filter_action_links( array $links ) {
|
||||
$settings_link = self::get_settings_link();
|
||||
|
||||
if ( ! empty( $settings_link ) ) {
|
||||
array_unshift( $links, $settings_link );
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
public static function filter_row_meta( array $links, string $file, string $match ) {
|
||||
if ( $file === $match ) {
|
||||
$links[] = self::get_docs_link();
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin bootstrap file.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
// Load and initialize the plugin via OOP bootstrap class.
|
||||
require_once SPARTAN_MCE_PLUGIN_DIR . 'includes/core/class-cmatic-plugin.php';
|
||||
|
||||
$cmatic_plugin = new Cmatic_Plugin( SPARTAN_MCE_PLUGIN_FILE, SPARTAN_MCE_VERSION );
|
||||
$cmatic_plugin->init();
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin activation 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_Activator {
|
||||
|
||||
private const INITIALIZED_FLAG = 'cmatic_initialized';
|
||||
|
||||
private $options;
|
||||
|
||||
private $install_data;
|
||||
|
||||
private $migration;
|
||||
|
||||
private $pro_status;
|
||||
|
||||
private $redirect;
|
||||
|
||||
private $lifecycle_signal;
|
||||
|
||||
private $version;
|
||||
|
||||
public function __construct( $version ) {
|
||||
$this->version = $version;
|
||||
$this->options = new Cmatic_Options_Repository();
|
||||
$this->install_data = new Cmatic_Install_Data( $this->options );
|
||||
$this->migration = new Cmatic_Migration( $this->options, $version );
|
||||
$this->pro_status = new Cmatic_Pro_Status( $this->options );
|
||||
$this->redirect = new Cmatic_Redirect( $this->options );
|
||||
$this->lifecycle_signal = new Cmatic_Lifecycle_Signal();
|
||||
}
|
||||
|
||||
public function activate() {
|
||||
$this->do_activation( true );
|
||||
}
|
||||
|
||||
public function ensure_initialized() {
|
||||
// Fast check - auto-loaded option, near-zero cost.
|
||||
if ( get_option( self::INITIALIZED_FLAG ) ) {
|
||||
// Flag exists, but verify data is actually complete.
|
||||
$install_id = $this->options->get( 'install.id' );
|
||||
if ( ! empty( $install_id ) ) {
|
||||
return; // Data exists, truly initialized.
|
||||
}
|
||||
// Flag exists but data is missing - delete flag and continue.
|
||||
delete_option( self::INITIALIZED_FLAG );
|
||||
}
|
||||
|
||||
// Run initialization (idempotent).
|
||||
$this->do_activation( false );
|
||||
}
|
||||
|
||||
private function do_activation( $is_normal_activation ) {
|
||||
// 1. BULLETPROOF: Ensure install_id and quest exist FIRST.
|
||||
$this->install_data->ensure();
|
||||
|
||||
// 2. Migrate legacy options (safe to run multiple times).
|
||||
$this->migration->run();
|
||||
|
||||
// 3. Update Pro plugin status.
|
||||
$this->pro_status->update();
|
||||
|
||||
// 4. Record activation in lifecycle.
|
||||
$this->record_activation();
|
||||
|
||||
// 5. Send lifecycle signal (only on normal activation, not fallback).
|
||||
if ( $is_normal_activation ) {
|
||||
$this->lifecycle_signal->send_activation();
|
||||
}
|
||||
|
||||
// 6. Schedule redirect (only on normal activation).
|
||||
if ( $is_normal_activation ) {
|
||||
$this->redirect->schedule();
|
||||
}
|
||||
|
||||
// 7. Mark plugin as active (for missed deactivation detection).
|
||||
$this->options->set( 'lifecycle.is_active', true );
|
||||
|
||||
// 8. Mark as initialized (auto-loaded option for fast checks).
|
||||
add_option( self::INITIALIZED_FLAG, true );
|
||||
|
||||
// 9. Fire action for extensibility.
|
||||
do_action( 'cmatic_activated', $is_normal_activation );
|
||||
}
|
||||
|
||||
private function record_activation() {
|
||||
$activations = $this->options->get( 'lifecycle.activations', array() );
|
||||
$activations = is_array( $activations ) ? $activations : array();
|
||||
$activations[] = time();
|
||||
|
||||
$this->options->set( 'lifecycle.activations', $activations );
|
||||
$this->options->set( 'lifecycle.is_reactivation', count( $activations ) > 1 );
|
||||
}
|
||||
|
||||
public function get_redirect() {
|
||||
return $this->redirect;
|
||||
}
|
||||
|
||||
public function get_pro_status() {
|
||||
return $this->pro_status;
|
||||
}
|
||||
|
||||
public static function is_initialized() {
|
||||
return (bool) get_option( self::INITIALIZED_FLAG );
|
||||
}
|
||||
|
||||
public static function clear_initialized_flag() {
|
||||
return delete_option( self::INITIALIZED_FLAG );
|
||||
}
|
||||
|
||||
public function verify_lifecycle_state(): void {
|
||||
$thinks_active = $this->options->get( 'lifecycle.is_active', false );
|
||||
|
||||
// If we think we're inactive (or never set), nothing to check.
|
||||
if ( ! $thinks_active ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if plugin is actually active in WordPress.
|
||||
if ( ! function_exists( 'is_plugin_active' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$actually_active = is_plugin_active( SPARTAN_MCE_PLUGIN_BASENAME );
|
||||
|
||||
// If state matches reality, all is well.
|
||||
if ( $actually_active ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// MISMATCH DETECTED: We think we're active, but WordPress says no.
|
||||
// This means deactivation hook was missed (update, FTP delete, etc).
|
||||
$this->handle_missed_deactivation();
|
||||
}
|
||||
|
||||
private function handle_missed_deactivation(): void {
|
||||
// 1. Update state flag FIRST.
|
||||
$this->options->set( 'lifecycle.is_active', false );
|
||||
|
||||
// 2. Record missed deactivation (for analytics).
|
||||
$missed = $this->options->get( 'lifecycle.missed_deactivations', array() );
|
||||
$missed = is_array( $missed ) ? $missed : array();
|
||||
$missed[] = array(
|
||||
'timestamp' => time(),
|
||||
'type' => 'self_healing',
|
||||
);
|
||||
$this->options->set( 'lifecycle.missed_deactivations', $missed );
|
||||
|
||||
// 3. Clear cron (idempotent).
|
||||
Cmatic_Cron::unschedule();
|
||||
|
||||
// 4. Send telemetry signal (if class exists).
|
||||
$this->lifecycle_signal->send_deactivation();
|
||||
|
||||
// 5. Clear initialized flag so next activation runs fully.
|
||||
delete_option( self::INITIALIZED_FLAG );
|
||||
|
||||
// 6. Fire action for extensibility.
|
||||
do_action( 'cmatic_missed_deactivation_handled' );
|
||||
}
|
||||
|
||||
public static function register_hooks( string $plugin_file, string $version ): void {
|
||||
// Activation hook.
|
||||
register_activation_hook(
|
||||
$plugin_file,
|
||||
function () use ( $version ) {
|
||||
$activator = new self( $version );
|
||||
$activator->activate();
|
||||
}
|
||||
);
|
||||
|
||||
// Deactivation hook.
|
||||
register_deactivation_hook(
|
||||
$plugin_file,
|
||||
function () {
|
||||
$deactivator = new Cmatic_Deactivator();
|
||||
$deactivator->deactivate();
|
||||
}
|
||||
);
|
||||
|
||||
// CRITICAL FALLBACK: Catch missed activations AND deactivations on admin_init.
|
||||
add_action(
|
||||
'admin_init',
|
||||
function () use ( $version ) {
|
||||
$activator = new self( $version );
|
||||
|
||||
// Check for missed deactivation FIRST (before initialization).
|
||||
$activator->verify_lifecycle_state();
|
||||
|
||||
// Then ensure initialized (existing fallback).
|
||||
$activator->ensure_initialized();
|
||||
$activator->get_redirect()->maybe_redirect();
|
||||
},
|
||||
5
|
||||
);
|
||||
|
||||
// Update Pro status on plugins_loaded (late priority).
|
||||
add_action(
|
||||
'plugins_loaded',
|
||||
function () use ( $version ) {
|
||||
$activator = new self( $version );
|
||||
$activator->get_pro_status()->update();
|
||||
},
|
||||
99
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* Dependency container.
|
||||
*
|
||||
* @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_Lite_Container {
|
||||
|
||||
private static $services = array();
|
||||
|
||||
private static $factories = array();
|
||||
|
||||
public static function set( string $id, $service ): void {
|
||||
self::$services[ $id ] = $service;
|
||||
}
|
||||
|
||||
public static function factory( string $id, callable $factory ): void {
|
||||
self::$factories[ $id ] = $factory;
|
||||
}
|
||||
|
||||
public static function get( string $id ) {
|
||||
// Return cached instance if exists.
|
||||
if ( isset( self::$services[ $id ] ) ) {
|
||||
return self::$services[ $id ];
|
||||
}
|
||||
|
||||
// Build from factory if exists.
|
||||
if ( isset( self::$factories[ $id ] ) ) {
|
||||
self::$services[ $id ] = call_user_func( self::$factories[ $id ] );
|
||||
unset( self::$factories[ $id ] );
|
||||
return self::$services[ $id ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function has( string $id ): bool {
|
||||
return isset( self::$services[ $id ] ) || isset( self::$factories[ $id ] );
|
||||
}
|
||||
|
||||
public static function clear(): void {
|
||||
self::$services = array();
|
||||
self::$factories = array();
|
||||
}
|
||||
|
||||
public static function boot(): void {
|
||||
// Register Options Repository.
|
||||
self::factory(
|
||||
Cmatic_Options_Interface::class,
|
||||
function () {
|
||||
return new Cmatic_Options_Repository();
|
||||
}
|
||||
);
|
||||
|
||||
// Alias for backward compatibility.
|
||||
self::factory(
|
||||
'options',
|
||||
function () {
|
||||
return self::get( Cmatic_Options_Interface::class );
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin deactivation 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_Deactivator {
|
||||
|
||||
private $options;
|
||||
|
||||
private $lifecycle_signal;
|
||||
|
||||
public function __construct() {
|
||||
$this->options = new Cmatic_Options_Repository();
|
||||
$this->lifecycle_signal = new Cmatic_Lifecycle_Signal();
|
||||
}
|
||||
|
||||
public function deactivate() {
|
||||
// Mark inactive FIRST (state must update before cleanup).
|
||||
$this->options->set( 'lifecycle.is_active', false );
|
||||
|
||||
$this->record_deactivation();
|
||||
$this->lifecycle_signal->send_deactivation();
|
||||
|
||||
do_action( 'cmatic_deactivated' );
|
||||
}
|
||||
|
||||
private function record_deactivation() {
|
||||
$deactivations = $this->options->get( 'lifecycle.deactivations', array() );
|
||||
$deactivations = is_array( $deactivations ) ? $deactivations : array();
|
||||
$deactivations[] = time();
|
||||
|
||||
$this->options->set( 'lifecycle.deactivations', $deactivations );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Installation data 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_Install_Data {
|
||||
|
||||
public const MIN_VALID_TIMESTAMP = 1000000000;
|
||||
|
||||
private $options;
|
||||
|
||||
public function __construct( Cmatic_Options_Repository $options ) {
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
public function ensure() {
|
||||
$data = $this->options->get_all();
|
||||
$changed = false;
|
||||
|
||||
if ( ! isset( $data['install'] ) || ! is_array( $data['install'] ) ) {
|
||||
$data['install'] = array();
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
if ( empty( $data['install']['id'] ) ) {
|
||||
$wp_cached = get_option( 'cmatic', array() );
|
||||
if ( is_array( $wp_cached ) && ! empty( $wp_cached['install']['id'] ) ) {
|
||||
$data['install']['id'] = $wp_cached['install']['id'];
|
||||
} else {
|
||||
$data['install']['id'] = $this->generate_install_id();
|
||||
}
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
$quest = isset( $data['install']['quest'] ) ? (int) $data['install']['quest'] : 0;
|
||||
if ( $quest < self::MIN_VALID_TIMESTAMP ) {
|
||||
$data['install']['quest'] = $this->determine_quest( $data );
|
||||
$changed = true;
|
||||
}
|
||||
|
||||
if ( $changed ) {
|
||||
$this->options->save( $data );
|
||||
}
|
||||
}
|
||||
|
||||
public function get_install_id() {
|
||||
$install_id = $this->options->get( 'install.id', '' );
|
||||
|
||||
if ( empty( $install_id ) ) {
|
||||
$wp_cached = get_option( 'cmatic', array() );
|
||||
if ( is_array( $wp_cached ) && ! empty( $wp_cached['install']['id'] ) ) {
|
||||
$install_id = $wp_cached['install']['id'];
|
||||
$this->options->set( 'install.id', $install_id );
|
||||
return $install_id;
|
||||
}
|
||||
|
||||
$install_id = $this->generate_install_id();
|
||||
$this->options->set( 'install.id', $install_id );
|
||||
}
|
||||
|
||||
return $install_id;
|
||||
}
|
||||
|
||||
public function get_quest() {
|
||||
$quest = (int) $this->options->get( 'install.quest', 0 );
|
||||
|
||||
if ( $quest >= self::MIN_VALID_TIMESTAMP ) {
|
||||
return $quest;
|
||||
}
|
||||
|
||||
$quest = $this->determine_quest( $this->options->get_all() );
|
||||
$this->options->set( 'install.quest', $quest );
|
||||
|
||||
return $quest;
|
||||
}
|
||||
|
||||
private function generate_install_id() {
|
||||
return bin2hex( random_bytes( 6 ) );
|
||||
}
|
||||
|
||||
private function determine_quest( $data ) {
|
||||
$candidates = array();
|
||||
|
||||
// 1. Legacy mce_loyalty (highest priority - original timestamp).
|
||||
$loyalty = $this->options->get_legacy( 'mce_loyalty' );
|
||||
if ( is_array( $loyalty ) && ! empty( $loyalty[0] ) ) {
|
||||
$candidates[] = (int) $loyalty[0];
|
||||
}
|
||||
|
||||
// 2. Lifecycle activations.
|
||||
$activations = isset( $data['lifecycle']['activations'] ) ? $data['lifecycle']['activations'] : array();
|
||||
if ( ! empty( $activations ) && is_array( $activations ) ) {
|
||||
$candidates[] = (int) min( $activations );
|
||||
}
|
||||
|
||||
// 3. Telemetry opt-in date.
|
||||
$opt_in = isset( $data['telemetry']['opt_in_date'] ) ? (int) $data['telemetry']['opt_in_date'] : 0;
|
||||
if ( $opt_in >= self::MIN_VALID_TIMESTAMP ) {
|
||||
$candidates[] = $opt_in;
|
||||
}
|
||||
|
||||
// 4. API first connected.
|
||||
$api_first = isset( $data['api']['first_connected'] ) ? (int) $data['api']['first_connected'] : 0;
|
||||
if ( $api_first >= self::MIN_VALID_TIMESTAMP ) {
|
||||
$candidates[] = $api_first;
|
||||
}
|
||||
|
||||
// 5. First submission.
|
||||
$sub_first = isset( $data['submissions']['first'] ) ? (int) $data['submissions']['first'] : 0;
|
||||
if ( $sub_first >= self::MIN_VALID_TIMESTAMP ) {
|
||||
$candidates[] = $sub_first;
|
||||
}
|
||||
|
||||
// 6. Fallback to current time.
|
||||
return ! empty( $candidates ) ? min( $candidates ) : time();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* Database migration 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_Migration {
|
||||
|
||||
private const LEGACY_MCE_OPTIONS = array(
|
||||
'mce_loyalty',
|
||||
'mce_install_id',
|
||||
'mce_sent',
|
||||
'mce_show_update_news',
|
||||
'mce_show_notice',
|
||||
'mce_conten_panel_master',
|
||||
'mce_conten_tittle_master',
|
||||
);
|
||||
|
||||
/**
|
||||
* Legacy chimpmatic/cmatic options to migrate and clean up.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private const LEGACY_CMATIC_OPTIONS = array(
|
||||
'chimpmatic-update',
|
||||
'cmatic_log_on',
|
||||
'cmatic_do_activation_redirect',
|
||||
'cmatic_news_retry_count',
|
||||
'csyncr_last_weekly_run',
|
||||
);
|
||||
|
||||
private $options;
|
||||
|
||||
private $version;
|
||||
|
||||
public function __construct( Cmatic_Options_Repository $options, $version ) {
|
||||
$this->options = $options;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
public function run() {
|
||||
$data = $this->options->get_all();
|
||||
|
||||
$data['version'] = $this->version;
|
||||
|
||||
$this->migrate_install( $data );
|
||||
$this->migrate_stats( $data );
|
||||
$this->migrate_ui( $data );
|
||||
$this->migrate_cmatic_options( $data );
|
||||
$this->migrate_api_data( $data );
|
||||
|
||||
$data['migrated'] = true;
|
||||
|
||||
$this->options->save( $data );
|
||||
$this->cleanup_legacy_options();
|
||||
}
|
||||
|
||||
public function is_migrated() {
|
||||
return (bool) $this->options->get( 'migrated', false );
|
||||
}
|
||||
|
||||
private function migrate_install( &$data ) {
|
||||
if ( ! isset( $data['install'] ) ) {
|
||||
$data['install'] = array();
|
||||
}
|
||||
|
||||
if ( ! isset( $data['install']['plugin_slug'] ) ) {
|
||||
$data['install']['plugin_slug'] = 'contact-form-7-mailchimp-extension';
|
||||
}
|
||||
|
||||
if ( ! isset( $data['install']['activated_at'] ) && ! empty( $data['install']['quest'] ) ) {
|
||||
$data['install']['activated_at'] = gmdate( 'Y-m-d H:i:s', (int) $data['install']['quest'] );
|
||||
}
|
||||
}
|
||||
|
||||
private function migrate_stats( &$data ) {
|
||||
if ( ! isset( $data['stats'] ) ) {
|
||||
$data['stats'] = array();
|
||||
}
|
||||
|
||||
if ( ! isset( $data['stats']['sent'] ) ) {
|
||||
$data['stats']['sent'] = (int) $this->options->get_legacy( 'mce_sent', 0 );
|
||||
}
|
||||
}
|
||||
|
||||
private function migrate_ui( &$data ) {
|
||||
if ( isset( $data['ui'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$panel_title = $this->options->get_legacy( 'mce_conten_tittle_master', '' );
|
||||
$panel_content = $this->options->get_legacy( 'mce_conten_panel_master', '' );
|
||||
|
||||
$data['ui'] = array(
|
||||
'news' => (bool) $this->options->get_legacy( 'mce_show_update_news', true ),
|
||||
'notice_banner' => (bool) $this->options->get_legacy( 'mce_show_notice', true ),
|
||||
'welcome_panel' => array(
|
||||
'title' => $panel_title ? $panel_title : 'Chimpmatic Lite is now ' . $this->version . '!',
|
||||
'content' => $panel_content ? $panel_content : '',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private function migrate_cmatic_options( &$data ) {
|
||||
$old_auto_update = get_option( 'chimpmatic-update' );
|
||||
if ( false !== $old_auto_update && ! isset( $data['auto_update'] ) ) {
|
||||
$data['auto_update'] = ( '1' === $old_auto_update ) ? 1 : 0;
|
||||
}
|
||||
|
||||
$old_debug = get_option( 'cmatic_log_on' );
|
||||
if ( false !== $old_debug && ! isset( $data['debug'] ) ) {
|
||||
$data['debug'] = ( 'on' === $old_debug || '1' === $old_debug ) ? 1 : 0;
|
||||
}
|
||||
|
||||
$old_redirect = get_option( 'cmatic_do_activation_redirect' );
|
||||
if ( false !== $old_redirect && ! isset( $data['activation_redirect'] ) ) {
|
||||
$data['activation_redirect'] = (bool) $old_redirect;
|
||||
}
|
||||
|
||||
$old_news_count = get_option( 'cmatic_news_retry_count' );
|
||||
if ( false !== $old_news_count ) {
|
||||
if ( ! isset( $data['news'] ) ) {
|
||||
$data['news'] = array();
|
||||
}
|
||||
if ( ! isset( $data['news']['retry_count'] ) ) {
|
||||
$data['news']['retry_count'] = (int) $old_news_count;
|
||||
}
|
||||
}
|
||||
|
||||
$old_last_run = get_option( 'csyncr_last_weekly_run' );
|
||||
if ( false !== $old_last_run ) {
|
||||
if ( ! isset( $data['telemetry'] ) ) {
|
||||
$data['telemetry'] = array();
|
||||
}
|
||||
if ( ! isset( $data['telemetry']['last_run'] ) ) {
|
||||
$data['telemetry']['last_run'] = (int) $old_last_run;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function migrate_api_data( &$data ) {
|
||||
// Skip if already set.
|
||||
if ( ! empty( $data['api']['first_connected'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First, try to use existing api.setup_first_success timestamp.
|
||||
$setup_first_success = isset( $data['api']['setup_first_success'] ) ? (int) $data['api']['setup_first_success'] : 0;
|
||||
if ( $setup_first_success > 1000000000 ) {
|
||||
if ( ! isset( $data['api'] ) ) {
|
||||
$data['api'] = array();
|
||||
}
|
||||
$data['api']['first_connected'] = $setup_first_success;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Check if any form has a successful API connection.
|
||||
global $wpdb;
|
||||
$form_options = $wpdb->get_results(
|
||||
"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE 'cf7_mch_%'",
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
if ( empty( $form_options ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $form_options as $row ) {
|
||||
$form_data = maybe_unserialize( $row['option_value'] );
|
||||
if ( is_array( $form_data ) && ! empty( $form_data['api-validation'] ) && 1 === (int) $form_data['api-validation'] ) {
|
||||
// Found a form with valid API - backfill first_connected with current time.
|
||||
if ( ! isset( $data['api'] ) ) {
|
||||
$data['api'] = array();
|
||||
}
|
||||
$data['api']['first_connected'] = time();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanup_legacy_options() {
|
||||
foreach ( self::LEGACY_MCE_OPTIONS as $option ) {
|
||||
$this->options->delete_legacy( $option );
|
||||
}
|
||||
|
||||
foreach ( self::LEGACY_CMATIC_OPTIONS as $option ) {
|
||||
delete_option( $option );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* Main plugin class.
|
||||
*
|
||||
* @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_Plugin {
|
||||
|
||||
private $file;
|
||||
|
||||
private $version;
|
||||
|
||||
private $dir;
|
||||
|
||||
private $basename;
|
||||
|
||||
public function __construct( string $file, string $version ) {
|
||||
$this->file = $file;
|
||||
$this->version = $version;
|
||||
$this->dir = plugin_dir_path( $file );
|
||||
$this->basename = plugin_basename( $file );
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
$this->load_core_dependencies();
|
||||
$this->register_lifecycle_hooks();
|
||||
$this->load_module_dependencies();
|
||||
$this->initialize_components();
|
||||
$this->load_late_dependencies();
|
||||
$this->initialize_late_components();
|
||||
}
|
||||
|
||||
private function load_core_dependencies(): void {
|
||||
// Load interfaces first.
|
||||
require_once $this->dir . 'includes/interfaces/interface-cmatic-options.php';
|
||||
require_once $this->dir . 'includes/interfaces/interface-cmatic-logger.php';
|
||||
require_once $this->dir . 'includes/interfaces/interface-cmatic-api-client.php';
|
||||
|
||||
// Load container.
|
||||
require_once $this->dir . 'includes/core/class-cmatic-container.php';
|
||||
|
||||
// Load services.
|
||||
require_once $this->dir . 'includes/services/class-cmatic-options-repository.php';
|
||||
require_once $this->dir . 'includes/services/class-cmatic-pro-status.php';
|
||||
require_once $this->dir . 'includes/services/class-cmatic-redirect.php';
|
||||
require_once $this->dir . 'includes/services/class-cmatic-lifecycle-signal.php';
|
||||
require_once $this->dir . 'includes/core/class-cmatic-install-data.php';
|
||||
require_once $this->dir . 'includes/core/class-cmatic-migration.php';
|
||||
require_once $this->dir . 'includes/core/class-cmatic-activator.php';
|
||||
require_once $this->dir . 'includes/core/class-cmatic-deactivator.php';
|
||||
require_once $this->dir . 'includes/services/class-cmatic-cf7-dependency.php';
|
||||
require_once $this->dir . 'includes/services/class-cmatic-pro-syncer.php';
|
||||
require_once $this->dir . 'includes/services/class-cmatic-api-key-importer.php';
|
||||
}
|
||||
|
||||
private function register_lifecycle_hooks(): void {
|
||||
Cmatic_Activator::register_hooks( $this->file, $this->version );
|
||||
}
|
||||
|
||||
private function load_module_dependencies(): void {
|
||||
$modules = array(
|
||||
// Utils.
|
||||
'utils/class-cmatic-utils.php',
|
||||
'utils/class-cmatic-lite-get-fields.php',
|
||||
'utils/class-cmatic-pursuit.php',
|
||||
'utils/class-cmatic-file-logger.php',
|
||||
'utils/class-cmatic-remote-fetcher.php',
|
||||
'utils/class-cmatic-buster.php',
|
||||
// Services.
|
||||
'services/class-cmatic-cf7-tags.php',
|
||||
'services/class-cmatic-cron.php',
|
||||
'services/class-cmatic-api-service.php',
|
||||
'services/class-cmatic-form-tags.php',
|
||||
// Submission handling (load before handler).
|
||||
'services/submission/class-cmatic-email-extractor.php',
|
||||
'services/submission/class-cmatic-status-resolver.php',
|
||||
'services/submission/class-cmatic-merge-vars-builder.php',
|
||||
'services/submission/class-cmatic-response-handler.php',
|
||||
'services/submission/class-cmatic-mailchimp-subscriber.php',
|
||||
'services/class-cmatic-submission-handler.php',
|
||||
// REST API Controllers.
|
||||
'api/class-cmatic-rest-lists.php',
|
||||
'api/class-cmatic-rest-settings.php',
|
||||
'api/class-cmatic-rest-form.php',
|
||||
'api/class-cmatic-rest-reset.php',
|
||||
// Admin.
|
||||
'admin/class-cmatic-plugin-links.php',
|
||||
'admin/class-cmatic-deactivation-survey.php',
|
||||
'admin/class-cmatic-asset-loader.php',
|
||||
'admin/class-cmatic-admin-panel.php',
|
||||
// API.
|
||||
'api/class-cmatic-log-viewer.php',
|
||||
'api/class-cmatic-contact-lookup.php',
|
||||
'api/class-cmatic-submission-feedback.php',
|
||||
// UI.
|
||||
'ui/class-cmatic-header.php',
|
||||
'ui/class-cmatic-api-panel.php',
|
||||
'ui/class-cmatic-audiences.php',
|
||||
'ui/class-cmatic-data-container.php',
|
||||
'ui/class-cmatic-panel-toggles.php',
|
||||
'ui/class-cmatic-tags-preview.php',
|
||||
'ui/class-cmatic-banners.php',
|
||||
'ui/class-cmatic-form-classes.php',
|
||||
'ui/class-cmatic-field-mapper.php',
|
||||
'ui/class-cmatic-sidebar-panel.php',
|
||||
'ui/class-cmatic-advanced-settings.php',
|
||||
);
|
||||
|
||||
foreach ( $modules as $module ) {
|
||||
$path = $this->dir . 'includes/' . $module;
|
||||
if ( file_exists( $path ) ) {
|
||||
require_once $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function initialize_components(): void {
|
||||
// Boot service container.
|
||||
Cmatic_Lite_Container::boot();
|
||||
|
||||
// Core services (no dependencies).
|
||||
Cmatic_CF7_Dependency::init();
|
||||
Cmatic_Pro_Syncer::init();
|
||||
|
||||
// Logging.
|
||||
Cmatic_Log_Viewer::init( 'chimpmatic-lite', '[ChimpMatic Lite]', 'chimpmatic-lite' );
|
||||
|
||||
// REST API Controllers.
|
||||
Cmatic_Rest_Lists::init();
|
||||
Cmatic_Rest_Settings::init();
|
||||
Cmatic_Rest_Form::init();
|
||||
Cmatic_Rest_Reset::init();
|
||||
|
||||
// API Services.
|
||||
Cmatic_Contact_Lookup::init();
|
||||
Cmatic_Submission_Feedback::init();
|
||||
|
||||
// Admin UI.
|
||||
Cmatic_Deactivation_Survey::init_lite();
|
||||
Cmatic_Asset_Loader::init();
|
||||
|
||||
// CF7 Integration.
|
||||
Cmatic_CF7_Tags::init();
|
||||
Cmatic_Admin_Panel::init();
|
||||
Cmatic_Submission_Handler::init();
|
||||
Cmatic_Banners::init();
|
||||
Cmatic_Form_Classes::init();
|
||||
|
||||
// Background Tasks.
|
||||
Cmatic_Cron::init( $this->file );
|
||||
Cmatic_Plugin_Links::init( $this->basename );
|
||||
}
|
||||
|
||||
private function load_late_dependencies(): void {
|
||||
// UI Components (Modals).
|
||||
require_once $this->dir . 'includes/ui/class-cmatic-modal.php';
|
||||
require_once $this->dir . 'includes/ui/class-cmatic-test-submission-modal.php';
|
||||
|
||||
// Admin Bar Notification System.
|
||||
require_once $this->dir . 'includes/ui/class-cmatic-notification.php';
|
||||
require_once $this->dir . 'includes/ui/class-cmatic-notification-center.php';
|
||||
require_once $this->dir . 'includes/ui/class-cmatic-admin-bar-menu.php';
|
||||
|
||||
// Signals (Telemetry System) - has its own PSR-4 autoloader.
|
||||
require_once $this->dir . 'includes/signals/autoload.php';
|
||||
}
|
||||
|
||||
private function initialize_late_components(): void {
|
||||
// Test Submission Modal.
|
||||
$test_submission_modal = new Cmatic_Test_Submission_Modal();
|
||||
$test_submission_modal->init();
|
||||
|
||||
// Admin Bar.
|
||||
Cmatic_Notification_Center::get();
|
||||
Cmatic_Admin_Bar_Menu::instance();
|
||||
|
||||
// Signals (Telemetry).
|
||||
Cmatic\Metrics\Bootstrap::init(
|
||||
array(
|
||||
'plugin_basename' => $this->basename,
|
||||
'endpoint_url' => 'https://signls.dev/wp-json/chimpmatic/v1/telemetry',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* API client interface.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
interface Cmatic_Api_Client_Interface {
|
||||
|
||||
/**
|
||||
* Validate an API key.
|
||||
*
|
||||
* @param string $api_key The API key to validate.
|
||||
* @param bool $log_enabled Whether logging is enabled.
|
||||
* @return array{api-validation: int} Validation result.
|
||||
*/
|
||||
public static function validate_key( string $api_key, bool $log_enabled = false ): array;
|
||||
|
||||
/**
|
||||
* Get audiences (lists) from Mailchimp.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @param bool $log_enabled Whether logging is enabled.
|
||||
* @return array{lisdata: array, merge_fields?: array}
|
||||
*/
|
||||
public static function get_lists( string $api_key, bool $log_enabled = false ): array;
|
||||
|
||||
/**
|
||||
* Get merge fields for a specific list.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @param string $list_id The list ID.
|
||||
* @param bool $log_enabled Whether logging is enabled.
|
||||
* @return array{merge_fields: array}
|
||||
*/
|
||||
public static function get_merge_fields( string $api_key, string $list_id, bool $log_enabled = false ): array;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* Logger interface.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
interface Cmatic_Logger_Interface {
|
||||
|
||||
/**
|
||||
* Log a message.
|
||||
*
|
||||
* @param string $level Log level (INFO, ERROR, WARNING, DEBUG, CRITICAL).
|
||||
* @param string $message Log message.
|
||||
* @param mixed $context Optional context data.
|
||||
* @return void
|
||||
*/
|
||||
public function log( string $level, string $message, $context = null ): void;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* Options repository interface.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
interface Cmatic_Options_Interface {
|
||||
|
||||
/**
|
||||
* Get all stored options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all();
|
||||
|
||||
/**
|
||||
* Get a value using dot notation.
|
||||
*
|
||||
* @param string $key Dot-notation key (e.g., 'install.id').
|
||||
* @param mixed $default Default value if not found.
|
||||
* @return mixed
|
||||
*/
|
||||
public function get( $key, $default = null );
|
||||
|
||||
/**
|
||||
* Set a value using dot notation.
|
||||
*
|
||||
* @param string $key Dot-notation key.
|
||||
* @param mixed $value Value to set.
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function set( $key, $value );
|
||||
|
||||
/**
|
||||
* Save the full options array.
|
||||
*
|
||||
* @param array $data Full options data.
|
||||
* @return bool Success.
|
||||
*/
|
||||
public function save( $data );
|
||||
|
||||
/**
|
||||
* Clear internal cache.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_cache();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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 )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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'] );
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 ) );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics bootstrap class.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics;
|
||||
|
||||
use Cmatic\Metrics\Core\Storage;
|
||||
use Cmatic\Metrics\Core\Tracker;
|
||||
use Cmatic\Metrics\Core\Scheduler;
|
||||
use Cmatic\Metrics\Core\Sync;
|
||||
use Cmatic\Metrics\Core\Collector;
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Bootstrap {
|
||||
|
||||
private static $instance = null;
|
||||
private $config = array();
|
||||
|
||||
public static function init( $config = array() ) {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self( $config );
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct( $config ) {
|
||||
try {
|
||||
$this->config = wp_parse_args(
|
||||
$config,
|
||||
array(
|
||||
'plugin_basename' => '',
|
||||
'endpoint_url' => '',
|
||||
)
|
||||
);
|
||||
|
||||
Sync::set_endpoint( $this->config['endpoint_url'] );
|
||||
$this->init_components();
|
||||
} catch ( \Exception $e ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function init_components() {
|
||||
try {
|
||||
Storage::init();
|
||||
Tracker::init();
|
||||
Scheduler::init();
|
||||
|
||||
add_action( 'admin_init', array( $this, 'admin_init_failsafe' ), 999 );
|
||||
add_action( 'cmatic_weekly_telemetry', array( $this, 'execute_weekly_telemetry' ) );
|
||||
$this->ensure_weekly_schedule();
|
||||
} catch ( \Exception $e ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function admin_init_failsafe() {
|
||||
try {
|
||||
$transient_key = 'cmatic_admin_checked';
|
||||
if ( get_transient( $transient_key ) ) {
|
||||
return;
|
||||
}
|
||||
set_transient( $transient_key, 1, HOUR_IN_SECONDS );
|
||||
|
||||
if ( ! class_exists( 'Cmatic_Options_Repository' ) ) {
|
||||
return;
|
||||
}
|
||||
Storage::init();
|
||||
|
||||
if ( ! Storage::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $pagenow;
|
||||
if ( isset( $pagenow ) && in_array( $pagenow, array( 'plugins.php', 'plugin-install.php', 'plugin-editor.php' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$last_heartbeat = Storage::get_last_heartbeat();
|
||||
$two_weeks = 2 * WEEK_IN_SECONDS;
|
||||
|
||||
if ( 0 === $last_heartbeat || ( time() - $last_heartbeat ) > $two_weeks ) {
|
||||
$payload = Collector::collect( 'heartbeat' );
|
||||
Sync::send_async( $payload );
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function ensure_weekly_schedule() {
|
||||
try {
|
||||
add_filter( 'cron_schedules', array( $this, 'add_weekly_schedule' ) );
|
||||
|
||||
if ( ! wp_next_scheduled( 'cmatic_weekly_telemetry' ) ) {
|
||||
wp_schedule_event( time() + WEEK_IN_SECONDS, 'cmatic_weekly', 'cmatic_weekly_telemetry' );
|
||||
}
|
||||
} catch ( \Exception $e ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function add_weekly_schedule( $schedules ) {
|
||||
if ( ! isset( $schedules['cmatic_weekly'] ) ) {
|
||||
$schedules['cmatic_weekly'] = array(
|
||||
'interval' => WEEK_IN_SECONDS,
|
||||
'display' => 'Once Weekly',
|
||||
);
|
||||
}
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
public function execute_weekly_telemetry() {
|
||||
try {
|
||||
if ( ! Storage::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$last_run = Cmatic_Options_Repository::get_option( 'telemetry.last_run' ) ?: 0;
|
||||
$current_time = time();
|
||||
|
||||
if ( $current_time - $last_run < ( 6 * DAY_IN_SECONDS ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.last_run', $current_time );
|
||||
|
||||
$payload = Collector::collect( 'heartbeat' );
|
||||
Sync::send( $payload );
|
||||
} catch ( \Exception $e ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_instance() {
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics data collector orchestrator.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core;
|
||||
|
||||
use Cmatic\Metrics\Core\Collectors\Install_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Metadata_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Lifecycle_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Environment_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Api_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Submissions_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Features_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Forms_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Performance_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Plugins_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Competitors_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\Server_Collector;
|
||||
use Cmatic\Metrics\Core\Collectors\WordPress_Collector;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Collector {
|
||||
|
||||
public static function collect( $event = 'heartbeat' ): array {
|
||||
return array(
|
||||
'install_id' => Storage::get_install_id(),
|
||||
'timestamp' => time(),
|
||||
'event' => $event,
|
||||
'install' => Install_Collector::collect(),
|
||||
'metadata' => Metadata_Collector::collect(),
|
||||
'lifecycle' => Lifecycle_Collector::collect(),
|
||||
'environment' => Environment_Collector::collect(),
|
||||
'api' => Api_Collector::collect(),
|
||||
'submissions' => Submissions_Collector::collect(),
|
||||
'features' => Features_Collector::collect(),
|
||||
'forms' => Forms_Collector::collect(),
|
||||
'performance' => Performance_Collector::collect(),
|
||||
'plugins' => Plugins_Collector::collect(),
|
||||
'competitors' => Competitors_Collector::collect(),
|
||||
'server' => Server_Collector::collect(),
|
||||
'wordpress' => WordPress_Collector::collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* API data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Api_Collector {
|
||||
public static function collect(): array {
|
||||
$has_api_key = false;
|
||||
$api_data_center = '';
|
||||
$api_key_length = 0;
|
||||
$forms_with_api = 0;
|
||||
$cf7_forms = Forms_Collector::get_cf7_forms();
|
||||
|
||||
$form_ids = array_map( fn( $form ) => $form->id(), $cf7_forms );
|
||||
$form_options = Forms_Collector::batch_load_form_options( $form_ids );
|
||||
|
||||
foreach ( $cf7_forms as $form ) {
|
||||
$form_id = $form->id();
|
||||
$cf7_mch = $form_options[ $form_id ]['settings'] ?? array();
|
||||
|
||||
if ( ! empty( $cf7_mch['api'] ) ) {
|
||||
$has_api_key = true;
|
||||
++$forms_with_api;
|
||||
|
||||
if ( empty( $api_data_center ) && preg_match( '/-([a-z0-9]+)$/i', $cf7_mch['api'], $matches ) ) {
|
||||
$api_data_center = $matches[1];
|
||||
}
|
||||
|
||||
if ( 0 === $api_key_length ) {
|
||||
$api_key_length = strlen( $cf7_mch['api'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$total_sent = (int) Cmatic_Options_Repository::get_option( 'stats.sent', 0 );
|
||||
$total_attempts = (int) Cmatic_Options_Repository::get_option( 'api.total_attempts', $total_sent );
|
||||
$total_successes = (int) Cmatic_Options_Repository::get_option( 'api.total_successes', $total_sent );
|
||||
$total_failures = (int) Cmatic_Options_Repository::get_option( 'api.total_failures', 0 );
|
||||
$success_rate = $total_attempts > 0 ? round( ( $total_successes / $total_attempts ) * 100, 2 ) : 0;
|
||||
$first_connected = (int) Cmatic_Options_Repository::get_option( 'api.first_connected', 0 );
|
||||
$last_success = (int) Cmatic_Options_Repository::get_option( 'api.last_success', 0 );
|
||||
$last_failure = (int) Cmatic_Options_Repository::get_option( 'api.last_failure', 0 );
|
||||
$avg_response_time = (int) Cmatic_Options_Repository::get_option( 'api.avg_response_time', 0 );
|
||||
|
||||
$error_summary = self::get_error_summary();
|
||||
$consecutive_failures = (int) Cmatic_Options_Repository::get_option( 'api.consecutive_failures', 0 );
|
||||
$uptime_percentage = $total_attempts > 0 ? round( ( $total_successes / $total_attempts ) * 100, 2 ) : 100;
|
||||
|
||||
$days_since_last_success = $last_success > 0 ? floor( ( time() - $last_success ) / DAY_IN_SECONDS ) : 0;
|
||||
$days_since_last_failure = $last_failure > 0 ? floor( ( time() - $last_failure ) / DAY_IN_SECONDS ) : 0;
|
||||
|
||||
$data = array(
|
||||
'is_connected' => $has_api_key,
|
||||
'forms_with_api' => $forms_with_api,
|
||||
'api_data_center' => $api_data_center,
|
||||
'api_key_length' => $api_key_length,
|
||||
'first_connected' => $first_connected,
|
||||
'total_attempts' => $total_attempts,
|
||||
'total_successes' => $total_successes,
|
||||
'total_failures' => $total_failures,
|
||||
'success_rate' => $success_rate,
|
||||
'uptime_percentage' => $uptime_percentage,
|
||||
'last_success' => $last_success,
|
||||
'last_failure' => $last_failure,
|
||||
'days_since_last_success' => $days_since_last_success,
|
||||
'days_since_last_failure' => $days_since_last_failure,
|
||||
'avg_response_time_ms' => $avg_response_time,
|
||||
'error_codes' => $error_summary,
|
||||
'api_health_score' => min( 100, max( 0, $uptime_percentage - ( $consecutive_failures * 5 ) ) ),
|
||||
'setup_sync_attempted' => Cmatic_Options_Repository::get_option( 'api.sync_attempted', false ),
|
||||
'setup_sync_attempts_count' => (int) Cmatic_Options_Repository::get_option( 'api.sync_attempts_count', 0 ),
|
||||
'setup_first_success' => Cmatic_Options_Repository::get_option( 'api.setup_first_success', false ),
|
||||
'setup_first_failure' => Cmatic_Options_Repository::get_option( 'api.setup_first_failure', false ),
|
||||
'setup_failure_count' => (int) Cmatic_Options_Repository::get_option( 'api.setup_failure_count', 0 ),
|
||||
'setup_audience_selected' => Cmatic_Options_Repository::get_option( 'api.audience_selected', false ),
|
||||
);
|
||||
|
||||
return array_filter( $data, fn( $v ) => $v !== 0 && $v !== false && $v !== '' && $v !== array() );
|
||||
}
|
||||
|
||||
private static function get_error_summary(): array {
|
||||
$error_codes = Cmatic_Options_Repository::get_option( 'api.error_codes', array() );
|
||||
$error_summary = array();
|
||||
|
||||
foreach ( $error_codes as $code => $count ) {
|
||||
if ( $count > 0 ) {
|
||||
$error_summary[ $code ] = (int) $count;
|
||||
}
|
||||
}
|
||||
|
||||
return $error_summary;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* Competitors data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Competitors_Collector {
|
||||
private const COMPETITORS = array(
|
||||
'mc4wp' => array(
|
||||
'slug' => 'mailchimp-for-wp/mailchimp-for-wp.php',
|
||||
'name' => 'MC4WP: Mailchimp for WordPress',
|
||||
),
|
||||
'mc4wp_premium' => array(
|
||||
'slug' => 'mc4wp-premium/mc4wp-premium.php',
|
||||
'name' => 'MC4WP Premium',
|
||||
),
|
||||
'mailchimp_woo' => array(
|
||||
'slug' => 'mailchimp-for-woocommerce/mailchimp-woocommerce.php',
|
||||
'name' => 'Mailchimp for WooCommerce',
|
||||
),
|
||||
'crm_perks' => array(
|
||||
'slug' => 'cf7-mailchimp/cf7-mailchimp.php',
|
||||
'name' => 'CRM Perks CF7 Mailchimp',
|
||||
),
|
||||
'easy_forms' => array(
|
||||
'slug' => 'jetwp-easy-mailchimp/jetwp-easy-mailchimp.php',
|
||||
'name' => 'Easy Forms for Mailchimp',
|
||||
),
|
||||
'jetrail' => array(
|
||||
'slug' => 'jetrail-cf7-mailchimp/jetrail-cf7-mailchimp.php',
|
||||
'name' => 'Jetrail CF7 Mailchimp',
|
||||
),
|
||||
'cf7_mailchimp_ext' => array(
|
||||
'slug' => 'contact-form-7-mailchimp-extension-jetrail/cf7-mailchimp-ext.php',
|
||||
'name' => 'CF7 Mailchimp Extension Jetrail',
|
||||
),
|
||||
'newsletter' => array(
|
||||
'slug' => 'newsletter/plugin.php',
|
||||
'name' => 'Newsletter',
|
||||
),
|
||||
'mailpoet' => array(
|
||||
'slug' => 'mailpoet/mailpoet.php',
|
||||
'name' => 'MailPoet',
|
||||
),
|
||||
'fluent_forms' => array(
|
||||
'slug' => 'fluentform/fluentform.php',
|
||||
'name' => 'Fluent Forms',
|
||||
),
|
||||
'wpforms' => array(
|
||||
'slug' => 'wpforms-lite/wpforms.php',
|
||||
'name' => 'WPForms',
|
||||
),
|
||||
'gravity_forms' => array(
|
||||
'slug' => 'gravityforms/gravityforms.php',
|
||||
'name' => 'Gravity Forms',
|
||||
),
|
||||
'ninja_forms' => array(
|
||||
'slug' => 'ninja-forms/ninja-forms.php',
|
||||
'name' => 'Ninja Forms',
|
||||
),
|
||||
'formidable' => array(
|
||||
'slug' => 'formidable/formidable.php',
|
||||
'name' => 'Formidable Forms',
|
||||
),
|
||||
'hubspot' => array(
|
||||
'slug' => 'leadin/leadin.php',
|
||||
'name' => 'HubSpot',
|
||||
),
|
||||
'elementor_pro' => array(
|
||||
'slug' => 'elementor-pro/elementor-pro.php',
|
||||
'name' => 'Elementor Pro',
|
||||
),
|
||||
);
|
||||
|
||||
public static function collect(): array {
|
||||
if ( ! function_exists( 'is_plugin_active' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$all_plugins = get_plugins();
|
||||
$competitors = self::check_competitors( $all_plugins );
|
||||
$summary = self::build_summary( $competitors );
|
||||
$individual = self::build_individual_status( $competitors );
|
||||
|
||||
return array_merge( $summary, $individual );
|
||||
}
|
||||
|
||||
private static function check_competitors( array $all_plugins ): array {
|
||||
$competitors = array();
|
||||
|
||||
foreach ( self::COMPETITORS as $key => $competitor ) {
|
||||
$competitors[ $key ] = array(
|
||||
'slug' => $competitor['slug'],
|
||||
'name' => $competitor['name'],
|
||||
'installed' => isset( $all_plugins[ $competitor['slug'] ] ),
|
||||
'active' => is_plugin_active( $competitor['slug'] ),
|
||||
);
|
||||
}
|
||||
|
||||
return $competitors;
|
||||
}
|
||||
|
||||
private static function build_summary( array $competitors ): array {
|
||||
$installed_count = 0;
|
||||
$active_count = 0;
|
||||
$installed_list = array();
|
||||
$active_list = array();
|
||||
|
||||
foreach ( $competitors as $key => $competitor ) {
|
||||
if ( $competitor['installed'] ) {
|
||||
++$installed_count;
|
||||
$installed_list[] = $key;
|
||||
}
|
||||
if ( $competitor['active'] ) {
|
||||
++$active_count;
|
||||
$active_list[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$risk_level = 'none';
|
||||
if ( $active_count > 0 ) {
|
||||
$risk_level = 'high';
|
||||
} elseif ( $installed_count > 0 ) {
|
||||
$risk_level = 'medium';
|
||||
}
|
||||
|
||||
return array(
|
||||
'has_competitors' => $installed_count > 0,
|
||||
'competitors_installed' => $installed_count,
|
||||
'competitors_active' => $active_count,
|
||||
'churn_risk' => $risk_level,
|
||||
'installed_list' => $installed_list,
|
||||
'active_list' => $active_list,
|
||||
);
|
||||
}
|
||||
|
||||
private static function build_individual_status( array $competitors ): array {
|
||||
$status = array();
|
||||
|
||||
foreach ( $competitors as $key => $competitor ) {
|
||||
$status[ $key . '_installed' ] = $competitor['installed'];
|
||||
$status[ $key . '_active' ] = $competitor['active'];
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Environment data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Environment_Collector {
|
||||
public static function collect(): array {
|
||||
global $wp_version, $wpdb;
|
||||
|
||||
$php_extensions = get_loaded_extensions();
|
||||
$critical_extensions = array( 'curl', 'json', 'mbstring', 'openssl', 'zip', 'gd', 'xml', 'dom', 'SimpleXML' );
|
||||
$loaded_critical = array_intersect( $critical_extensions, $php_extensions );
|
||||
|
||||
$theme = wp_get_theme();
|
||||
$parent_theme = $theme->parent() ? $theme->parent()->get( 'Name' ) : '';
|
||||
|
||||
$data = array(
|
||||
'php_version' => phpversion(),
|
||||
'php_sapi' => php_sapi_name(),
|
||||
'php_os' => PHP_OS,
|
||||
'php_architecture' => PHP_INT_SIZE === 8 ? '64-bit' : '32-bit',
|
||||
'php_memory_limit' => ini_get( 'memory_limit' ),
|
||||
'php_max_execution_time' => (int) ini_get( 'max_execution_time' ),
|
||||
'php_max_input_time' => (int) ini_get( 'max_input_time' ),
|
||||
'php_max_input_vars' => (int) ini_get( 'max_input_vars' ),
|
||||
'php_post_max_size' => ini_get( 'post_max_size' ),
|
||||
'php_upload_max_filesize' => ini_get( 'upload_max_filesize' ),
|
||||
'php_default_timezone' => ini_get( 'date.timezone' ),
|
||||
'php_log_errors' => ini_get( 'log_errors' ),
|
||||
'php_extensions_count' => count( $php_extensions ),
|
||||
'php_critical_extensions' => implode( ',', $loaded_critical ),
|
||||
'php_curl_version' => function_exists( 'curl_version' ) ? curl_version()['version'] : '',
|
||||
'php_openssl_version' => OPENSSL_VERSION_TEXT,
|
||||
'wp_version' => $wp_version,
|
||||
'wp_db_version' => get_option( 'db_version' ),
|
||||
'wp_memory_limit' => WP_MEMORY_LIMIT,
|
||||
'wp_max_memory_limit' => WP_MAX_MEMORY_LIMIT,
|
||||
'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG,
|
||||
'wp_debug_log' => defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG,
|
||||
'wp_debug_display' => defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY,
|
||||
'script_debug' => defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG,
|
||||
'wp_cache' => defined( 'WP_CACHE' ) && WP_CACHE,
|
||||
'wp_cron_disabled' => defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON,
|
||||
'wp_auto_update_core' => get_option( 'auto_update_core', 'enabled' ),
|
||||
'mysql_version' => $wpdb->db_version(),
|
||||
'mysql_client_version' => $wpdb->get_var( 'SELECT VERSION()' ),
|
||||
'db_charset' => $wpdb->charset,
|
||||
'db_collate' => $wpdb->collate,
|
||||
'db_prefix' => strlen( $wpdb->prefix ),
|
||||
'server_software' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : '',
|
||||
'server_protocol' => isset( $_SERVER['SERVER_PROTOCOL'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) ) : '',
|
||||
'server_port' => isset( $_SERVER['SERVER_PORT'] ) ? (int) $_SERVER['SERVER_PORT'] : 0,
|
||||
'https' => is_ssl(),
|
||||
'http_host' => hash( 'sha256', isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '' ),
|
||||
'locale' => get_locale(),
|
||||
'timezone' => wp_timezone_string(),
|
||||
'site_language' => get_bloginfo( 'language' ),
|
||||
'site_charset' => get_bloginfo( 'charset' ),
|
||||
'permalink_structure' => get_option( 'permalink_structure' ),
|
||||
'home_url' => hash( 'sha256', home_url() ),
|
||||
'site_url' => hash( 'sha256', site_url() ),
|
||||
'admin_email' => hash( 'sha256', get_option( 'admin_email' ) ),
|
||||
'theme' => $theme->get( 'Name' ),
|
||||
'theme_version' => $theme->get( 'Version' ),
|
||||
'theme_author' => $theme->get( 'Author' ),
|
||||
'parent_theme' => $parent_theme,
|
||||
'is_child_theme' => ! empty( $parent_theme ),
|
||||
'theme_supports_html5' => current_theme_supports( 'html5' ),
|
||||
'theme_supports_post_thumbnails' => current_theme_supports( 'post-thumbnails' ),
|
||||
'active_plugins_count' => count( get_option( 'active_plugins', array() ) ),
|
||||
'total_plugins_count' => count( get_plugins() ),
|
||||
'must_use_plugins_count' => count( wp_get_mu_plugins() ),
|
||||
'is_multisite' => is_multisite(),
|
||||
'is_subdomain_install' => is_multisite() ? ( defined( 'SUBDOMAIN_INSTALL' ) && SUBDOMAIN_INSTALL ) : false,
|
||||
'network_count' => is_multisite() ? get_blog_count() : 1,
|
||||
'is_main_site' => is_multisite() ? is_main_site() : true,
|
||||
'cf7_version' => defined( 'WPCF7_VERSION' ) ? WPCF7_VERSION : '',
|
||||
'cf7_installed' => class_exists( 'WPCF7_ContactForm' ),
|
||||
'plugin_version' => defined( 'SPARTAN_MCE_VERSION' ) ? SPARTAN_MCE_VERSION : '',
|
||||
'user_agent' => isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '',
|
||||
);
|
||||
|
||||
return array_filter( $data, fn( $v ) => $v !== 0 && $v !== false && $v !== '' && $v !== 'none' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
/**
|
||||
* Features data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Features_Collector {
|
||||
public static function collect(): array {
|
||||
$features = array(
|
||||
'double_optin' => 0,
|
||||
'required_consent' => 0,
|
||||
'debug_logger' => 0,
|
||||
'custom_merge_fields' => 0,
|
||||
'interest_groups' => 0,
|
||||
'groups_total_mapped' => 0,
|
||||
'tags_enabled' => 0,
|
||||
'tags_total_selected' => 0,
|
||||
'arbitrary_tags' => 0,
|
||||
'conditional_logic' => 0,
|
||||
'auto_update' => (bool) Cmatic_Options_Repository::get_option( 'auto_update', true ),
|
||||
'telemetry_enabled' => true,
|
||||
'debug' => (bool) Cmatic_Options_Repository::get_option( 'debug', false ),
|
||||
'backlink' => (bool) Cmatic_Options_Repository::get_option( 'backlink', false ),
|
||||
);
|
||||
|
||||
$cf7_forms = Forms_Collector::get_cf7_forms();
|
||||
|
||||
$form_ids = array_map( fn( $form ) => $form->id(), $cf7_forms );
|
||||
$form_options = Forms_Collector::batch_load_form_options( $form_ids );
|
||||
|
||||
foreach ( $cf7_forms as $form ) {
|
||||
$form_id = $form->id();
|
||||
$cf7_mch = $form_options[ $form_id ]['settings'] ?? array();
|
||||
|
||||
$features = self::check_double_optin( $cf7_mch, $features );
|
||||
$features = self::check_required_consent( $cf7_mch, $features );
|
||||
$features = self::check_debug_logger( $cf7_mch, $features );
|
||||
$features = self::check_custom_merge_fields( $cf7_mch, $features );
|
||||
$features = self::check_interest_groups( $cf7_mch, $features );
|
||||
$features = self::check_tags( $cf7_mch, $features );
|
||||
$features = self::check_conditional_logic( $cf7_mch, $features );
|
||||
}
|
||||
|
||||
return self::build_data( $features );
|
||||
}
|
||||
|
||||
private static function check_double_optin( array $cf7_mch, array $features ): array {
|
||||
if ( isset( $cf7_mch['confsubs'] ) && '1' === $cf7_mch['confsubs'] ) {
|
||||
++$features['double_optin'];
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
private static function check_required_consent( array $cf7_mch, array $features ): array {
|
||||
if ( ! empty( $cf7_mch['consent_required'] ) && ' ' !== $cf7_mch['consent_required'] ) {
|
||||
++$features['required_consent'];
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
private static function check_debug_logger( array $cf7_mch, array $features ): array {
|
||||
if ( isset( $cf7_mch['logfileEnabled'] ) && '1' === $cf7_mch['logfileEnabled'] ) {
|
||||
++$features['debug_logger'];
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
private static function check_custom_merge_fields( array $cf7_mch, array $features ): array {
|
||||
$merge_fields_raw = array();
|
||||
if ( ! empty( $cf7_mch['merge_fields'] ) && is_array( $cf7_mch['merge_fields'] ) ) {
|
||||
$merge_fields_raw = $cf7_mch['merge_fields'];
|
||||
} elseif ( ! empty( $cf7_mch['merge-vars'] ) && is_array( $cf7_mch['merge-vars'] ) ) {
|
||||
$merge_fields_raw = $cf7_mch['merge-vars'];
|
||||
}
|
||||
|
||||
if ( ! empty( $merge_fields_raw ) ) {
|
||||
$default_tags = array( 'EMAIL', 'FNAME', 'LNAME', 'ADDRESS', 'PHONE' );
|
||||
foreach ( $merge_fields_raw as $field ) {
|
||||
if ( isset( $field['tag'] ) && ! in_array( $field['tag'], $default_tags, true ) ) {
|
||||
++$features['custom_merge_fields'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
private static function check_interest_groups( array $cf7_mch, array $features ): array {
|
||||
$group_count = 0;
|
||||
for ( $i = 1; $i <= 20; $i++ ) {
|
||||
$key = $cf7_mch[ "ggCustomKey{$i}" ] ?? '';
|
||||
$value = $cf7_mch[ "ggCustomValue{$i}" ] ?? '';
|
||||
if ( ! empty( $key ) && ! empty( trim( $value ) ) && ' ' !== $value ) {
|
||||
++$group_count;
|
||||
}
|
||||
}
|
||||
if ( $group_count > 0 ) {
|
||||
++$features['interest_groups'];
|
||||
$features['groups_total_mapped'] += $group_count;
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
private static function check_tags( array $cf7_mch, array $features ): array {
|
||||
if ( ! empty( $cf7_mch['labeltags'] ) && is_array( $cf7_mch['labeltags'] ) ) {
|
||||
$enabled_tags = array_filter( $cf7_mch['labeltags'], fn( $v ) => '1' === $v );
|
||||
if ( count( $enabled_tags ) > 0 ) {
|
||||
++$features['tags_enabled'];
|
||||
$features['tags_total_selected'] += count( $enabled_tags );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $cf7_mch['labeltags_cm-tag'] ) && trim( $cf7_mch['labeltags_cm-tag'] ) !== '' ) {
|
||||
++$features['arbitrary_tags'];
|
||||
}
|
||||
|
||||
return $features;
|
||||
}
|
||||
|
||||
private static function check_conditional_logic( array $cf7_mch, array $features ): array {
|
||||
if ( ! empty( $cf7_mch['conditional_logic'] ) ) {
|
||||
++$features['conditional_logic'];
|
||||
}
|
||||
return $features;
|
||||
}
|
||||
|
||||
private static function build_data( array $features ): array {
|
||||
$data = array(
|
||||
'double_optin_count' => $features['double_optin'],
|
||||
'required_consent_count' => $features['required_consent'],
|
||||
'debug_logger_count' => $features['debug_logger'],
|
||||
'custom_merge_fields_count' => $features['custom_merge_fields'],
|
||||
'interest_groups_count' => $features['interest_groups'],
|
||||
'groups_total_mapped' => $features['groups_total_mapped'],
|
||||
'tags_enabled_count' => $features['tags_enabled'],
|
||||
'tags_total_selected' => $features['tags_total_selected'],
|
||||
'arbitrary_tags_count' => $features['arbitrary_tags'],
|
||||
'conditional_logic_count' => $features['conditional_logic'],
|
||||
'double_optin' => $features['double_optin'] > 0,
|
||||
'required_consent' => $features['required_consent'] > 0,
|
||||
'debug_logger' => $features['debug_logger'] > 0,
|
||||
'custom_merge_fields' => $features['custom_merge_fields'] > 0,
|
||||
'interest_groups' => $features['interest_groups'] > 0,
|
||||
'tags_enabled' => $features['tags_enabled'] > 0,
|
||||
'arbitrary_tags' => $features['arbitrary_tags'] > 0,
|
||||
'conditional_logic' => $features['conditional_logic'] > 0,
|
||||
'auto_update' => $features['auto_update'],
|
||||
'telemetry_enabled' => $features['telemetry_enabled'],
|
||||
'debug' => $features['debug'],
|
||||
'backlink' => $features['backlink'],
|
||||
'total_features_enabled' => count( array_filter( $features ) ),
|
||||
'features_usage_percentage' => round( ( count( array_filter( $features ) ) / count( $features ) ) * 100, 2 ),
|
||||
'webhook_enabled' => (bool) Cmatic_Options_Repository::get_option( 'features.webhook_enabled', false ),
|
||||
'custom_api_endpoint' => (bool) Cmatic_Options_Repository::get_option( 'features.custom_api_endpoint', false ),
|
||||
'email_notifications' => (bool) Cmatic_Options_Repository::get_option( 'features.email_notifications', false ),
|
||||
'test_modal_used' => (bool) Cmatic_Options_Repository::get_option( 'features.test_modal_used', false ),
|
||||
'contact_lookup_used' => (bool) Cmatic_Options_Repository::get_option( 'features.contact_lookup_used', false ),
|
||||
);
|
||||
|
||||
return array_filter( $data, fn( $v ) => $v !== 0 && $v !== false && $v !== '' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
<?php
|
||||
/**
|
||||
* Forms data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Forms_Collector {
|
||||
private const MAX_FORMS = 100;
|
||||
|
||||
public static function collect(): array {
|
||||
$cf7_forms = self::get_cf7_forms();
|
||||
$processed_forms = count( $cf7_forms );
|
||||
$total_forms = self::get_total_form_count();
|
||||
$forms_truncated = $total_forms > self::MAX_FORMS;
|
||||
$active_forms = 0;
|
||||
$forms_with_api = 0;
|
||||
$forms_with_lists = 0;
|
||||
$total_lists = 0;
|
||||
$total_fields = 0;
|
||||
$unique_audiences = array();
|
||||
$forms_data = array();
|
||||
$paired_lists = array();
|
||||
|
||||
$forms_detail = array();
|
||||
$field_type_counts = array();
|
||||
$total_mappings = 0;
|
||||
$total_mc_fields = 0;
|
||||
|
||||
$unique_audiences = self::build_audiences_from_global();
|
||||
|
||||
$form_ids = array_map( fn( $form ) => $form->id(), $cf7_forms );
|
||||
$form_options = self::batch_load_form_options( $form_ids );
|
||||
|
||||
foreach ( $cf7_forms as $form ) {
|
||||
$form_id = $form->id();
|
||||
$cf7_mch = $form_options[ $form_id ]['settings'] ?? array();
|
||||
|
||||
if ( ! empty( $cf7_mch['api'] ) ) {
|
||||
++$forms_with_api;
|
||||
++$active_forms;
|
||||
}
|
||||
|
||||
$selected_list = self::get_selected_list( $cf7_mch );
|
||||
|
||||
if ( ! empty( $selected_list ) && isset( $unique_audiences[ $selected_list ] ) ) {
|
||||
$unique_audiences[ $selected_list ]['is_paired'] = true;
|
||||
$paired_lists[ $selected_list ] = true;
|
||||
}
|
||||
|
||||
$audience_count = 0;
|
||||
$has_list = false;
|
||||
if ( isset( $cf7_mch['lisdata']['lists'] ) && is_array( $cf7_mch['lisdata']['lists'] ) ) {
|
||||
$audience_count = count( $cf7_mch['lisdata']['lists'] );
|
||||
$has_list = $audience_count > 0;
|
||||
$total_lists += $audience_count;
|
||||
if ( $has_list ) {
|
||||
++$forms_with_lists;
|
||||
}
|
||||
}
|
||||
|
||||
$form_fields = $form->scan_form_tags();
|
||||
$total_fields += count( $form_fields );
|
||||
|
||||
$form_field_details = self::extract_field_details( $form_fields, $field_type_counts );
|
||||
|
||||
list( $form_mappings, $unmapped_cf7, $unmapped_mc, $mapped_cf7_fields, $form_mc_fields, $form_total_mappings ) =
|
||||
self::extract_mappings( $cf7_mch, $form_field_details );
|
||||
|
||||
$total_mc_fields += $form_mc_fields;
|
||||
$total_mappings += $form_total_mappings;
|
||||
|
||||
$form_features = self::extract_form_features( $cf7_mch );
|
||||
|
||||
if ( count( $forms_detail ) < 50 ) {
|
||||
$forms_detail[] = array(
|
||||
'form_id' => hash( 'sha256', (string) $form_id ),
|
||||
'field_count' => count( $form_field_details ),
|
||||
'fields' => $form_field_details,
|
||||
'paired_audience_id' => ! empty( $selected_list ) ? hash( 'sha256', $selected_list ) : null,
|
||||
'mappings' => $form_mappings,
|
||||
'unmapped_cf7_fields' => $unmapped_cf7,
|
||||
'unmapped_mc_fields' => $unmapped_mc,
|
||||
'features' => $form_features,
|
||||
);
|
||||
}
|
||||
|
||||
$forms_data[] = array(
|
||||
'form_id' => hash( 'sha256', (string) $form_id ),
|
||||
'has_api' => ! empty( $cf7_mch['api'] ),
|
||||
'has_list' => $has_list,
|
||||
'audience_count' => $audience_count,
|
||||
'field_count' => count( $form_fields ),
|
||||
'has_double_opt' => isset( $cf7_mch['confsubs'] ) && '1' === $cf7_mch['confsubs'],
|
||||
'has_consent' => ! empty( $cf7_mch['accept'] ) && ' ' !== $cf7_mch['accept'],
|
||||
'submissions' => (int) ( $form_options[ $form_id ]['submissions'] ?? 0 ),
|
||||
'last_submission' => (int) ( $form_options[ $form_id ]['last_submission'] ?? 0 ),
|
||||
);
|
||||
}
|
||||
|
||||
$avg_fields_per_form = $processed_forms > 0 ? round( $total_fields / $processed_forms, 2 ) : 0;
|
||||
$avg_lists_per_form = $forms_with_lists > 0 ? round( $total_lists / $forms_with_lists, 2 ) : 0;
|
||||
|
||||
list( $oldest_form, $newest_form ) = self::get_form_age_range( $cf7_forms );
|
||||
|
||||
$audience_data = array_values( $unique_audiences );
|
||||
$total_unique_audiences = count( $audience_data );
|
||||
$total_contacts = array_sum( array_column( $audience_data, 'member_count' ) );
|
||||
|
||||
return array(
|
||||
'total_forms' => $total_forms,
|
||||
'processed_forms' => $processed_forms,
|
||||
'active_forms' => $active_forms,
|
||||
'forms_with_api' => $forms_with_api,
|
||||
'forms_with_lists' => $forms_with_lists,
|
||||
'inactive_forms' => $processed_forms - $active_forms,
|
||||
'total_audiences' => $total_unique_audiences,
|
||||
'audiences' => $audience_data,
|
||||
'total_contacts' => $total_contacts,
|
||||
'avg_lists_per_form' => $avg_lists_per_form,
|
||||
'max_lists_per_form' => $total_lists > 0 ? max( array_column( $forms_data, 'audience_count' ) ) : 0,
|
||||
'total_fields_all_forms' => $total_fields,
|
||||
'avg_fields_per_form' => $avg_fields_per_form,
|
||||
'min_fields_per_form' => $processed_forms > 0 ? min( array_column( $forms_data, 'field_count' ) ) : 0,
|
||||
'max_fields_per_form' => $processed_forms > 0 ? max( array_column( $forms_data, 'field_count' ) ) : 0,
|
||||
'oldest_form_created' => $oldest_form,
|
||||
'newest_form_created' => $newest_form,
|
||||
'days_since_oldest_form' => $oldest_form > 0 ? floor( ( time() - $oldest_form ) / DAY_IN_SECONDS ) : 0,
|
||||
'days_since_newest_form' => $newest_form > 0 ? floor( ( time() - $newest_form ) / DAY_IN_SECONDS ) : 0,
|
||||
'forms_with_submissions' => count( array_filter( $forms_data, fn( $f ) => $f['submissions'] > 0 ) ),
|
||||
'forms_never_submitted' => count( array_filter( $forms_data, fn( $f ) => 0 === $f['submissions'] ) ),
|
||||
'forms_with_double_opt' => count( array_filter( $forms_data, fn( $f ) => $f['has_double_opt'] ) ),
|
||||
'forms_with_consent' => count( array_filter( $forms_data, fn( $f ) => $f['has_consent'] ) ),
|
||||
'total_submissions_all_forms' => array_sum( array_column( $forms_data, 'submissions' ) ),
|
||||
'form_utilization_rate' => $processed_forms > 0 ? round( ( $active_forms / $processed_forms ) * 100, 2 ) : 0,
|
||||
'forms_detail' => $forms_detail,
|
||||
'forms_truncated' => $forms_truncated,
|
||||
'forms_detail_truncated' => $processed_forms > 50,
|
||||
'field_types_aggregate' => $field_type_counts,
|
||||
'mapping_stats' => array(
|
||||
'total_cf7_fields' => $total_fields,
|
||||
'total_mc_fields' => $total_mc_fields,
|
||||
'mapped_fields' => $total_mappings,
|
||||
'mapping_rate' => $total_fields > 0 ? round( ( $total_mappings / $total_fields ) * 100, 2 ) : 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_cf7_forms(): array {
|
||||
if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$post_ids = get_posts(
|
||||
array(
|
||||
'post_type' => 'wpcf7_contact_form',
|
||||
'posts_per_page' => self::MAX_FORMS,
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
|
||||
$forms = array();
|
||||
foreach ( $post_ids as $post_id ) {
|
||||
$form = \WPCF7_ContactForm::get_instance( $post_id );
|
||||
if ( $form ) {
|
||||
$forms[] = $form;
|
||||
}
|
||||
}
|
||||
|
||||
return $forms;
|
||||
}
|
||||
|
||||
public static function get_total_form_count(): int {
|
||||
if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
return (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'wpcf7_contact_form' AND post_status = 'publish'"
|
||||
);
|
||||
}
|
||||
|
||||
public static function batch_load_form_options( array $form_ids ): array {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $form_ids ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$option_names = array();
|
||||
foreach ( $form_ids as $form_id ) {
|
||||
$option_names[] = 'cf7_mch_' . $form_id;
|
||||
$option_names[] = 'cf7_mch_submissions_' . $form_id;
|
||||
$option_names[] = 'cf7_mch_last_submission_' . $form_id;
|
||||
}
|
||||
|
||||
$placeholders = implode( ', ', array_fill( 0, count( $option_names ), '%s' ) );
|
||||
$query = $wpdb->prepare(
|
||||
"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name IN ({$placeholders})",
|
||||
...$option_names
|
||||
);
|
||||
|
||||
$results = $wpdb->get_results( $query, ARRAY_A );
|
||||
|
||||
$options_map = array();
|
||||
foreach ( $results as $row ) {
|
||||
$options_map[ $row['option_name'] ] = maybe_unserialize( $row['option_value'] );
|
||||
}
|
||||
|
||||
$form_options = array();
|
||||
foreach ( $form_ids as $form_id ) {
|
||||
$form_options[ $form_id ] = array(
|
||||
'settings' => $options_map[ 'cf7_mch_' . $form_id ] ?? array(),
|
||||
'submissions' => $options_map[ 'cf7_mch_submissions_' . $form_id ] ?? 0,
|
||||
'last_submission' => $options_map[ 'cf7_mch_last_submission_' . $form_id ] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
return $form_options;
|
||||
}
|
||||
|
||||
private static function build_audiences_from_global(): array {
|
||||
$unique_audiences = array();
|
||||
$global_lisdata = Cmatic_Options_Repository::get_option( 'lisdata', array() );
|
||||
|
||||
if ( ! empty( $global_lisdata['lists'] ) && is_array( $global_lisdata['lists'] ) ) {
|
||||
foreach ( $global_lisdata['lists'] as $list ) {
|
||||
$list_id = $list['id'] ?? '';
|
||||
if ( empty( $list_id ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$unique_audiences[ $list_id ] = array(
|
||||
'audience_id' => hash( 'sha256', $list_id ),
|
||||
'member_count' => (int) ( $list['stats']['member_count'] ?? 0 ),
|
||||
'merge_field_count' => (int) ( $list['stats']['merge_field_count'] ?? 0 ),
|
||||
'double_optin' => ! empty( $list['double_optin'] ),
|
||||
'marketing_permissions' => ! empty( $list['marketing_permissions'] ),
|
||||
'campaign_count' => (int) ( $list['stats']['campaign_count'] ?? 0 ),
|
||||
'is_paired' => false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $unique_audiences;
|
||||
}
|
||||
|
||||
private static function get_selected_list( array $cf7_mch ): string {
|
||||
$selected_list = $cf7_mch['list'] ?? '';
|
||||
if ( is_array( $selected_list ) ) {
|
||||
$selected_list = $selected_list[0] ?? '';
|
||||
}
|
||||
return $selected_list;
|
||||
}
|
||||
|
||||
private static function extract_field_details( array $form_fields, array &$field_type_counts ): array {
|
||||
$form_field_details = array();
|
||||
$form_field_limit = 30;
|
||||
|
||||
foreach ( $form_fields as $ff_index => $tag ) {
|
||||
if ( $ff_index >= $form_field_limit ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$basetype = '';
|
||||
if ( is_object( $tag ) && isset( $tag->basetype ) ) {
|
||||
$basetype = $tag->basetype;
|
||||
} elseif ( is_array( $tag ) && isset( $tag['basetype'] ) ) {
|
||||
$basetype = $tag['basetype'];
|
||||
}
|
||||
|
||||
$field_name = '';
|
||||
if ( is_object( $tag ) && isset( $tag->name ) ) {
|
||||
$field_name = $tag->name;
|
||||
} elseif ( is_array( $tag ) && isset( $tag['name'] ) ) {
|
||||
$field_name = $tag['name'];
|
||||
}
|
||||
|
||||
if ( ! empty( $field_name ) && ! empty( $basetype ) ) {
|
||||
$form_field_details[] = array(
|
||||
'name' => $field_name,
|
||||
'type' => $basetype,
|
||||
);
|
||||
|
||||
if ( ! isset( $field_type_counts[ $basetype ] ) ) {
|
||||
$field_type_counts[ $basetype ] = 0;
|
||||
}
|
||||
++$field_type_counts[ $basetype ];
|
||||
}
|
||||
}
|
||||
|
||||
return $form_field_details;
|
||||
}
|
||||
|
||||
private static function extract_mappings( array $cf7_mch, array $form_field_details ): array {
|
||||
$form_mappings = array();
|
||||
$unmapped_cf7 = 0;
|
||||
$unmapped_mc = 0;
|
||||
$mapped_cf7_fields = array();
|
||||
$total_mc_fields = 0;
|
||||
$total_mappings = 0;
|
||||
|
||||
for ( $i = 1; $i <= 20; $i++ ) {
|
||||
$mc_tag = $cf7_mch[ 'CustomKey' . $i ] ?? '';
|
||||
$mc_type = $cf7_mch[ 'CustomKeyType' . $i ] ?? '';
|
||||
$cf7_field = trim( $cf7_mch[ 'CustomValue' . $i ] ?? '' );
|
||||
|
||||
if ( ! empty( $mc_tag ) ) {
|
||||
++$total_mc_fields;
|
||||
if ( '' !== $cf7_field ) {
|
||||
$form_mappings[] = array(
|
||||
'cf7_field' => $cf7_field,
|
||||
'mc_tag' => $mc_tag,
|
||||
'mc_type' => $mc_type,
|
||||
);
|
||||
$mapped_cf7_fields[] = $cf7_field;
|
||||
++$total_mappings;
|
||||
} else {
|
||||
++$unmapped_mc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $form_field_details as $field ) {
|
||||
if ( ! in_array( $field['name'], $mapped_cf7_fields, true ) ) {
|
||||
++$unmapped_cf7;
|
||||
}
|
||||
}
|
||||
|
||||
return array( $form_mappings, $unmapped_cf7, $unmapped_mc, $mapped_cf7_fields, $total_mc_fields, $total_mappings );
|
||||
}
|
||||
|
||||
private static function extract_form_features( array $cf7_mch ): array {
|
||||
$form_features = array();
|
||||
|
||||
if ( isset( $cf7_mch['confsubs'] ) && '1' === $cf7_mch['confsubs'] ) {
|
||||
$form_features['double_optin'] = true;
|
||||
}
|
||||
|
||||
if ( ! empty( $cf7_mch['consent_required'] ) && ' ' !== $cf7_mch['consent_required'] ) {
|
||||
$form_features['required_consent'] = true;
|
||||
}
|
||||
|
||||
if ( isset( $cf7_mch['logfileEnabled'] ) && '1' === $cf7_mch['logfileEnabled'] ) {
|
||||
$form_features['debug_logger'] = true;
|
||||
}
|
||||
|
||||
if ( ! empty( $cf7_mch['labeltags'] ) && is_array( $cf7_mch['labeltags'] ) ) {
|
||||
$enabled_tags = array_filter( $cf7_mch['labeltags'], fn( $v ) => '1' === $v );
|
||||
if ( count( $enabled_tags ) > 0 ) {
|
||||
$form_features['tags_enabled'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$form_group_count = 0;
|
||||
for ( $gi = 1; $gi <= 20; $gi++ ) {
|
||||
$gkey = $cf7_mch[ "ggCustomKey{$gi}" ] ?? '';
|
||||
$gvalue = $cf7_mch[ "ggCustomValue{$gi}" ] ?? '';
|
||||
if ( ! empty( $gkey ) && ! empty( trim( $gvalue ) ) && ' ' !== $gvalue ) {
|
||||
++$form_group_count;
|
||||
}
|
||||
}
|
||||
if ( $form_group_count > 0 ) {
|
||||
$form_features['interest_groups'] = true;
|
||||
}
|
||||
|
||||
$form_merge_fields_raw = array();
|
||||
if ( ! empty( $cf7_mch['merge_fields'] ) && is_array( $cf7_mch['merge_fields'] ) ) {
|
||||
$form_merge_fields_raw = $cf7_mch['merge_fields'];
|
||||
} elseif ( ! empty( $cf7_mch['merge-vars'] ) && is_array( $cf7_mch['merge-vars'] ) ) {
|
||||
$form_merge_fields_raw = $cf7_mch['merge-vars'];
|
||||
}
|
||||
|
||||
if ( ! empty( $form_merge_fields_raw ) ) {
|
||||
$default_tags = array( 'EMAIL', 'FNAME', 'LNAME', 'ADDRESS', 'PHONE' );
|
||||
$custom_field_count = 0;
|
||||
foreach ( $form_merge_fields_raw as $mfield ) {
|
||||
if ( isset( $mfield['tag'] ) && ! in_array( $mfield['tag'], $default_tags, true ) ) {
|
||||
++$custom_field_count;
|
||||
}
|
||||
}
|
||||
if ( $custom_field_count > 0 ) {
|
||||
$form_features['custom_merge_fields'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $cf7_mch['conditional_logic'] ) ) {
|
||||
$form_features['conditional_logic'] = true;
|
||||
}
|
||||
|
||||
return $form_features;
|
||||
}
|
||||
|
||||
private static function get_form_age_range( array $cf7_forms ): array {
|
||||
$oldest_form = 0;
|
||||
$newest_form = 0;
|
||||
|
||||
foreach ( $cf7_forms as $form ) {
|
||||
$created = get_post_field( 'post_date', $form->id(), 'raw' );
|
||||
$timestamp = strtotime( $created );
|
||||
|
||||
if ( 0 === $oldest_form || $timestamp < $oldest_form ) {
|
||||
$oldest_form = $timestamp;
|
||||
}
|
||||
if ( 0 === $newest_form || $timestamp > $newest_form ) {
|
||||
$newest_form = $timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
return array( $oldest_form, $newest_form );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Install data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic\Metrics\Core\Storage;
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Install_Collector {
|
||||
public static function collect(): array {
|
||||
return array(
|
||||
'plugin_slug' => Cmatic_Options_Repository::get_option( 'install.plugin_slug', 'contact-form-7-mailchimp-extension' ),
|
||||
'quest' => Storage::get_quest(),
|
||||
'pro' => array(
|
||||
'installed' => (bool) Cmatic_Options_Repository::get_option( 'install.pro.installed', false ),
|
||||
'activated' => (bool) Cmatic_Options_Repository::get_option( 'install.pro.activated', false ),
|
||||
'version' => Cmatic_Options_Repository::get_option( 'install.pro.version', null ),
|
||||
'licensed' => (bool) Cmatic_Options_Repository::get_option( 'install.pro.licensed', false ),
|
||||
'license_expires' => Cmatic_Options_Repository::get_option( 'install.pro.license_expires', null ),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* Lifecycle data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic\Metrics\Core\Storage;
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Lifecycle_Collector {
|
||||
public static function collect(): array {
|
||||
$activations = Cmatic_Options_Repository::get_option( 'lifecycle.activations', array() );
|
||||
$deactivations = Cmatic_Options_Repository::get_option( 'lifecycle.deactivations', array() );
|
||||
$upgrades = Cmatic_Options_Repository::get_option( 'lifecycle.upgrades', array() );
|
||||
|
||||
$first_activated = Storage::get_quest();
|
||||
$last_activated = ! empty( $activations ) ? max( $activations ) : 0;
|
||||
$last_deactivated = ! empty( $deactivations ) ? max( $deactivations ) : 0;
|
||||
$last_upgrade = ! empty( $upgrades ) ? max( $upgrades ) : 0;
|
||||
|
||||
$days_since_first = 0;
|
||||
if ( $first_activated > 0 ) {
|
||||
$days_since_first = floor( ( time() - $first_activated ) / DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
$avg_session_length = self::calculate_avg_session_length( $activations, $deactivations );
|
||||
|
||||
$version_history = Cmatic_Options_Repository::get_option( 'lifecycle.version_history', array() );
|
||||
$previous_version = Cmatic_Options_Repository::get_option( 'lifecycle.previous_version', '' );
|
||||
$days_since_last_upgrade = $last_upgrade > 0 ? floor( ( time() - $last_upgrade ) / DAY_IN_SECONDS ) : 0;
|
||||
|
||||
$active_session = empty( $deactivations ) || $last_activated > $last_deactivated;
|
||||
|
||||
$data = array(
|
||||
'activation_count' => count( $activations ),
|
||||
'deactivation_count' => count( $deactivations ),
|
||||
'upgrade_count' => count( $upgrades ),
|
||||
'first_activated' => $first_activated,
|
||||
'last_activated' => $last_activated,
|
||||
'last_deactivated' => $last_deactivated,
|
||||
'last_upgrade' => $last_upgrade,
|
||||
'days_since_first_activation' => (int) $days_since_first,
|
||||
'days_since_last_upgrade' => (int) $days_since_last_upgrade,
|
||||
'avg_session_length_seconds' => $avg_session_length,
|
||||
'total_sessions' => count( $activations ),
|
||||
'previous_version' => $previous_version,
|
||||
'version_history_count' => count( $version_history ),
|
||||
'install_method' => Cmatic_Options_Repository::get_option( 'lifecycle.install_method', 'unknown' ),
|
||||
'days_on_current_version' => $last_upgrade > 0 ? floor( ( time() - $last_upgrade ) / DAY_IN_SECONDS ) : $days_since_first,
|
||||
'activation_timestamps' => $activations,
|
||||
'deactivation_timestamps' => $deactivations,
|
||||
'upgrade_timestamps' => $upgrades,
|
||||
);
|
||||
|
||||
$data = array_filter( $data, fn( $v ) => $v !== 0 && $v !== '' && $v !== 'unknown' );
|
||||
$data['active_session'] = $active_session;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private static function calculate_avg_session_length( array $activations, array $deactivations ): int {
|
||||
if ( count( $activations ) === 0 || count( $deactivations ) === 0 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$total_session_time = 0;
|
||||
$session_count = 0;
|
||||
|
||||
foreach ( $activations as $index => $activation_time ) {
|
||||
if ( isset( $deactivations[ $index ] ) ) {
|
||||
$total_session_time += $deactivations[ $index ] - $activation_time;
|
||||
++$session_count;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $session_count > 0 ) {
|
||||
return (int) floor( $total_session_time / $session_count );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Metadata collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic\Metrics\Core\Storage;
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Metadata_Collector {
|
||||
public static function collect(): array {
|
||||
$first_installed = Storage::get_quest();
|
||||
$total_uptime = $first_installed > 0 ? time() - $first_installed : 0;
|
||||
$failed_count = (int) Cmatic_Options_Repository::get_option( 'telemetry.failed_count', 0 );
|
||||
|
||||
return array(
|
||||
'schedule' => Cmatic_Options_Repository::get_option( 'telemetry.schedule', 'frequent' ),
|
||||
'frequent_started_at' => (int) Cmatic_Options_Repository::get_option( 'telemetry.frequent_started_at', 0 ),
|
||||
'is_reactivation' => Storage::is_reactivation(),
|
||||
'disabled_count' => (int) Cmatic_Options_Repository::get_option( 'telemetry.disabled_count', 0 ),
|
||||
'opt_in_date' => (int) Cmatic_Options_Repository::get_option( 'telemetry.opt_in_date', 0 ),
|
||||
'last_heartbeat' => (int) Cmatic_Options_Repository::get_option( 'telemetry.last_heartbeat', 0 ),
|
||||
'failed_heartbeats' => $failed_count,
|
||||
'total_uptime_seconds' => $total_uptime,
|
||||
'telemetry_version' => SPARTAN_MCE_VERSION,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* Performance data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Performance_Collector {
|
||||
public static function collect(): array {
|
||||
$memory_current = memory_get_usage( true );
|
||||
$memory_peak = memory_get_peak_usage( true );
|
||||
$memory_limit = ini_get( 'memory_limit' );
|
||||
$memory_limit_bytes = self::convert_to_bytes( $memory_limit );
|
||||
$memory_usage_percent = $memory_limit_bytes > 0 ? round( ( $memory_peak / $memory_limit_bytes ) * 100, 2 ) : 0;
|
||||
|
||||
$db_queries = get_num_queries();
|
||||
$db_time = timer_stop( 0, 3 );
|
||||
|
||||
$page_load_time = isset( $_SERVER['REQUEST_TIME_FLOAT'] ) ? ( microtime( true ) - floatval( $_SERVER['REQUEST_TIME_FLOAT'] ) ) * 1000 : 0;
|
||||
|
||||
list( $object_cache_hits, $object_cache_misses ) = self::get_object_cache_stats();
|
||||
|
||||
$plugin_load_time = (float) Cmatic_Options_Repository::get_option( 'performance.plugin_load_time', 0 );
|
||||
$api_avg_response = (float) Cmatic_Options_Repository::get_option( 'performance.api_avg_response', 0 );
|
||||
|
||||
$data = array(
|
||||
'memory_current' => $memory_current,
|
||||
'memory_peak' => $memory_peak,
|
||||
'memory_limit' => $memory_limit,
|
||||
'memory_limit_bytes' => $memory_limit_bytes,
|
||||
'memory_usage_percent' => $memory_usage_percent,
|
||||
'memory_available' => max( 0, $memory_limit_bytes - $memory_peak ),
|
||||
'php_max_execution_time' => (int) ini_get( 'max_execution_time' ),
|
||||
'page_load_time_ms' => round( $page_load_time, 2 ),
|
||||
'plugin_load_time_ms' => round( $plugin_load_time, 2 ),
|
||||
'db_queries_count' => $db_queries,
|
||||
'db_query_time_seconds' => (float) $db_time,
|
||||
'db_size_mb' => self::get_database_size(),
|
||||
'api_avg_response_ms' => round( $api_avg_response, 2 ),
|
||||
'api_slowest_response_ms' => (int) Cmatic_Options_Repository::get_option( 'performance.api_slowest', 0 ),
|
||||
'api_fastest_response_ms' => (int) Cmatic_Options_Repository::get_option( 'performance.api_fastest', 0 ),
|
||||
'object_cache_enabled' => wp_using_ext_object_cache(),
|
||||
'object_cache_hits' => $object_cache_hits,
|
||||
'object_cache_misses' => $object_cache_misses,
|
||||
'object_cache_hit_rate' => ( $object_cache_hits + $object_cache_misses ) > 0 ? round( ( $object_cache_hits / ( $object_cache_hits + $object_cache_misses ) ) * 100, 2 ) : 0,
|
||||
'opcache_enabled' => self::is_opcache_enabled(),
|
||||
'opcache_hit_rate' => self::get_opcache_hit_rate(),
|
||||
);
|
||||
|
||||
return array_filter( $data, fn( $v ) => $v !== 0 && $v !== 0.0 && $v !== false && $v !== null && $v !== '' );
|
||||
}
|
||||
|
||||
private static function is_opcache_enabled(): bool {
|
||||
if ( ! function_exists( 'opcache_get_status' ) ) {
|
||||
return false;
|
||||
}
|
||||
$status = @opcache_get_status();
|
||||
return false !== $status && is_array( $status );
|
||||
}
|
||||
|
||||
public static function convert_to_bytes( $value ): int {
|
||||
$value = trim( $value );
|
||||
if ( empty( $value ) ) {
|
||||
return 0;
|
||||
}
|
||||
$last = strtolower( $value[ strlen( $value ) - 1 ] );
|
||||
$value = (int) $value;
|
||||
|
||||
switch ( $last ) {
|
||||
case 'g':
|
||||
$value *= 1024;
|
||||
// Fall through.
|
||||
case 'm':
|
||||
$value *= 1024;
|
||||
// Fall through.
|
||||
case 'k':
|
||||
$value *= 1024;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private static function get_database_size(): float {
|
||||
global $wpdb;
|
||||
|
||||
$size = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
'SELECT SUM(data_length + index_length) / 1024 / 1024
|
||||
FROM information_schema.TABLES
|
||||
WHERE table_schema = %s',
|
||||
DB_NAME
|
||||
)
|
||||
);
|
||||
|
||||
return round( (float) $size, 2 );
|
||||
}
|
||||
|
||||
private static function get_opcache_hit_rate(): float {
|
||||
if ( ! function_exists( 'opcache_get_status' ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$status = @opcache_get_status();
|
||||
if ( false === $status || ! is_array( $status ) || ! isset( $status['opcache_statistics'] ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$stats = $status['opcache_statistics'];
|
||||
$hits = isset( $stats['hits'] ) ? (int) $stats['hits'] : 0;
|
||||
$misses = isset( $stats['misses'] ) ? (int) $stats['misses'] : 0;
|
||||
|
||||
if ( ( $hits + $misses ) === 0 ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return round( ( $hits / ( $hits + $misses ) ) * 100, 2 );
|
||||
}
|
||||
|
||||
private static function get_object_cache_stats(): array {
|
||||
$object_cache_hits = 0;
|
||||
$object_cache_misses = 0;
|
||||
|
||||
if ( function_exists( 'wp_cache_get_stats' ) ) {
|
||||
$cache_stats = wp_cache_get_stats();
|
||||
$object_cache_hits = isset( $cache_stats['hits'] ) ? $cache_stats['hits'] : 0;
|
||||
$object_cache_misses = isset( $cache_stats['misses'] ) ? $cache_stats['misses'] : 0;
|
||||
}
|
||||
|
||||
return array( $object_cache_hits, $object_cache_misses );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugins data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Plugins_Collector {
|
||||
public static function collect(): array {
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$all_plugins = get_plugins();
|
||||
$active_plugins = get_option( 'active_plugins', array() );
|
||||
$mu_plugins = get_mu_plugins();
|
||||
|
||||
$plugin_list = self::build_plugin_list( $all_plugins, $active_plugins, $mu_plugins );
|
||||
$plugin_stats = self::get_plugin_stats( $all_plugins );
|
||||
$known_plugins = self::get_known_plugins();
|
||||
|
||||
$data = array(
|
||||
'total_plugins' => count( $all_plugins ),
|
||||
'active_plugins' => count( $active_plugins ),
|
||||
'inactive_plugins' => count( $all_plugins ) - count( $active_plugins ),
|
||||
'mu_plugins' => count( $mu_plugins ),
|
||||
'premium_plugins' => $plugin_stats['premium'],
|
||||
'cf7_addons' => $plugin_stats['cf7'],
|
||||
'mailchimp_plugins' => $plugin_stats['mailchimp'],
|
||||
'security_plugins' => $plugin_stats['security'],
|
||||
'cache_plugins' => $plugin_stats['cache'],
|
||||
'seo_plugins' => $plugin_stats['seo'],
|
||||
'has_woocommerce' => $known_plugins['woocommerce'],
|
||||
'has_elementor' => $known_plugins['elementor'],
|
||||
'has_jetpack' => $known_plugins['jetpack'],
|
||||
'has_wordfence' => $known_plugins['wordfence'],
|
||||
'has_yoast_seo' => $known_plugins['yoast_seo'],
|
||||
'plugin_list' => $plugin_list,
|
||||
);
|
||||
|
||||
return array_filter( $data, fn( $v ) => $v !== 0 && $v !== false && $v !== '' && $v !== array() );
|
||||
}
|
||||
|
||||
private static function build_plugin_list( array $all_plugins, array $active_plugins, array $mu_plugins ): array {
|
||||
$plugin_list = array();
|
||||
$network_active = is_multisite() ? get_site_option( 'active_sitewide_plugins', array() ) : array();
|
||||
|
||||
foreach ( $all_plugins as $plugin_path => $plugin_data ) {
|
||||
$is_active = in_array( $plugin_path, $active_plugins, true );
|
||||
$is_network = isset( $network_active[ $plugin_path ] );
|
||||
|
||||
$status = 'inactive';
|
||||
if ( $is_network ) {
|
||||
$status = 'network-active';
|
||||
} elseif ( $is_active ) {
|
||||
$status = 'active';
|
||||
}
|
||||
|
||||
$dir = dirname( $plugin_path );
|
||||
|
||||
$plugin_list[] = array(
|
||||
'slug' => '.' !== $dir ? $dir : basename( $plugin_path, '.php' ),
|
||||
'name' => $plugin_data['Name'],
|
||||
'version' => $plugin_data['Version'],
|
||||
'author' => wp_strip_all_tags( $plugin_data['Author'] ),
|
||||
'status' => $status,
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $mu_plugins as $mu_plugin_path => $mu_plugin_data ) {
|
||||
$plugin_list[] = array(
|
||||
'slug' => basename( $mu_plugin_path, '.php' ),
|
||||
'name' => $mu_plugin_data['Name'],
|
||||
'version' => $mu_plugin_data['Version'],
|
||||
'author' => wp_strip_all_tags( $mu_plugin_data['Author'] ),
|
||||
'status' => 'mu-plugin',
|
||||
);
|
||||
}
|
||||
|
||||
return $plugin_list;
|
||||
}
|
||||
|
||||
private static function get_plugin_stats( array $all_plugins ): array {
|
||||
$stats = array(
|
||||
'premium' => 0,
|
||||
'cf7' => 0,
|
||||
'mailchimp' => 0,
|
||||
'security' => 0,
|
||||
'cache' => 0,
|
||||
'seo' => 0,
|
||||
);
|
||||
|
||||
foreach ( $all_plugins as $plugin_path => $plugin_data ) {
|
||||
$name = strtolower( $plugin_data['Name'] );
|
||||
|
||||
if ( strpos( $name, 'pro' ) !== false || strpos( $name, 'premium' ) !== false ) {
|
||||
++$stats['premium'];
|
||||
}
|
||||
if ( strpos( $name, 'contact form 7' ) !== false ) {
|
||||
++$stats['cf7'];
|
||||
}
|
||||
if ( strpos( $name, 'mailchimp' ) !== false ) {
|
||||
++$stats['mailchimp'];
|
||||
}
|
||||
if ( strpos( $name, 'security' ) !== false || strpos( $name, 'wordfence' ) !== false || strpos( $name, 'sucuri' ) !== false ) {
|
||||
++$stats['security'];
|
||||
}
|
||||
if ( strpos( $name, 'cache' ) !== false || strpos( $name, 'wp rocket' ) !== false || strpos( $name, 'w3 total cache' ) !== false ) {
|
||||
++$stats['cache'];
|
||||
}
|
||||
if ( strpos( $name, 'seo' ) !== false || strpos( $name, 'yoast' ) !== false ) {
|
||||
++$stats['seo'];
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private static function get_known_plugins(): array {
|
||||
return array(
|
||||
'woocommerce' => is_plugin_active( 'woocommerce/woocommerce.php' ),
|
||||
'elementor' => is_plugin_active( 'elementor/elementor.php' ),
|
||||
'jetpack' => is_plugin_active( 'jetpack/jetpack.php' ),
|
||||
'wordfence' => is_plugin_active( 'wordfence/wordfence.php' ),
|
||||
'yoast_seo' => is_plugin_active( 'wordpress-seo/wp-seo.php' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* Server data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Server_Collector {
|
||||
public static function collect(): array {
|
||||
$server_load = self::get_load_average();
|
||||
|
||||
list( $disk_free, $disk_total ) = self::get_disk_space();
|
||||
$disk_used = $disk_total - $disk_free;
|
||||
$disk_usage_percent = $disk_total > 0 ? round( ( $disk_used / $disk_total ) * 100, 2 ) : 0;
|
||||
|
||||
$hostname = self::get_hostname();
|
||||
$architecture = self::get_architecture();
|
||||
|
||||
return array(
|
||||
'load_average_1min' => isset( $server_load[0] ) ? round( (float) $server_load[0], 2 ) : 0,
|
||||
'load_average_5min' => isset( $server_load[1] ) ? round( (float) $server_load[1], 2 ) : 0,
|
||||
'load_average_15min' => isset( $server_load[2] ) ? round( (float) $server_load[2], 2 ) : 0,
|
||||
'disk_usage_percent' => $disk_usage_percent,
|
||||
'disk_total_gb' => $disk_total ? round( $disk_total / 1024 / 1024 / 1024, 2 ) : 0,
|
||||
'server_ip' => hash( 'sha256', isset( $_SERVER['SERVER_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_ADDR'] ) ) : '' ),
|
||||
'server_hostname' => hash( 'sha256', $hostname ),
|
||||
'server_os' => PHP_OS,
|
||||
'server_architecture' => $architecture,
|
||||
);
|
||||
}
|
||||
|
||||
private static function get_load_average(): array {
|
||||
if ( ! function_exists( 'sys_getloadavg' ) ) {
|
||||
return array( 0, 0, 0 );
|
||||
}
|
||||
|
||||
$load = @sys_getloadavg();
|
||||
if ( false !== $load && is_array( $load ) ) {
|
||||
return $load;
|
||||
}
|
||||
|
||||
return array( 0, 0, 0 );
|
||||
}
|
||||
|
||||
private static function get_disk_space(): array {
|
||||
$disk_free = 0;
|
||||
$disk_total = 0;
|
||||
|
||||
if ( function_exists( 'disk_free_space' ) ) {
|
||||
$free = @disk_free_space( ABSPATH );
|
||||
if ( false !== $free ) {
|
||||
$disk_free = $free;
|
||||
}
|
||||
}
|
||||
|
||||
if ( function_exists( 'disk_total_space' ) ) {
|
||||
$total = @disk_total_space( ABSPATH );
|
||||
if ( false !== $total ) {
|
||||
$disk_total = $total;
|
||||
}
|
||||
}
|
||||
|
||||
return array( $disk_free, $disk_total );
|
||||
}
|
||||
|
||||
private static function get_hostname(): string {
|
||||
if ( ! function_exists( 'gethostname' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$name = @gethostname();
|
||||
return false !== $name ? $name : '';
|
||||
}
|
||||
|
||||
private static function get_architecture(): string {
|
||||
if ( ! function_exists( 'php_uname' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$arch = @php_uname( 'm' );
|
||||
return false !== $arch ? $arch : '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* Submissions data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
use Cmatic\Metrics\Core\Storage;
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Submissions_Collector {
|
||||
public static function collect(): array {
|
||||
$total_sent = (int) Cmatic_Options_Repository::get_option( 'stats.sent', 0 );
|
||||
$total_failed = (int) Cmatic_Options_Repository::get_option( 'submissions.failed', 0 );
|
||||
$first_submission = (int) Cmatic_Options_Repository::get_option( 'submissions.first', 0 );
|
||||
$last_submission = (int) Cmatic_Options_Repository::get_option( 'submissions.last', 0 );
|
||||
$last_success = (int) Cmatic_Options_Repository::get_option( 'submissions.last_success', 0 );
|
||||
$last_failure = (int) Cmatic_Options_Repository::get_option( 'submissions.last_failure', 0 );
|
||||
|
||||
$first_activated = Storage::get_quest();
|
||||
|
||||
$days_active = 1;
|
||||
if ( $first_activated > 0 ) {
|
||||
$days_active = max( 1, floor( ( time() - $first_activated ) / DAY_IN_SECONDS ) );
|
||||
}
|
||||
|
||||
$total_submissions = $total_sent + $total_failed;
|
||||
$avg_per_day = $days_active > 0 ? round( $total_submissions / $days_active, 2 ) : 0;
|
||||
$success_rate = $total_submissions > 0 ? round( ( $total_sent / $total_submissions ) * 100, 2 ) : 100;
|
||||
|
||||
$days_since_first = $first_submission > 0 ? floor( ( time() - $first_submission ) / DAY_IN_SECONDS ) : 0;
|
||||
$days_since_last = $last_submission > 0 ? floor( ( time() - $last_submission ) / DAY_IN_SECONDS ) : 0;
|
||||
$hours_since_last = $last_submission > 0 ? floor( ( time() - $last_submission ) / HOUR_IN_SECONDS ) : 0;
|
||||
|
||||
list( $busiest_hour, $max_submissions ) = self::get_busiest_hour();
|
||||
list( $busiest_day, $max_day_submissions ) = self::get_busiest_day();
|
||||
|
||||
$this_month = (int) Cmatic_Options_Repository::get_option( 'submissions.this_month', 0 );
|
||||
$last_month = (int) Cmatic_Options_Repository::get_option( 'submissions.last_month', 0 );
|
||||
$peak_month = (int) Cmatic_Options_Repository::get_option( 'submissions.peak_month', 0 );
|
||||
|
||||
$consecutive_successes = (int) Cmatic_Options_Repository::get_option( 'submissions.consecutive_successes', 0 );
|
||||
$consecutive_failures = (int) Cmatic_Options_Repository::get_option( 'submissions.consecutive_failures', 0 );
|
||||
|
||||
$data = array(
|
||||
'total_sent' => $total_sent,
|
||||
'total_failed' => $total_failed,
|
||||
'total_submissions' => $total_submissions,
|
||||
'successful_submissions_count' => $total_sent,
|
||||
'failed_count' => $total_failed,
|
||||
'success_rate' => $success_rate,
|
||||
'first_submission' => $first_submission,
|
||||
'last_submission' => $last_submission,
|
||||
'last_success' => $last_success,
|
||||
'last_failure' => $last_failure,
|
||||
'days_since_first' => $days_since_first,
|
||||
'days_since_last' => $days_since_last,
|
||||
'hours_since_last' => $hours_since_last,
|
||||
'avg_per_day' => $avg_per_day,
|
||||
'avg_per_week' => round( $avg_per_day * 7, 2 ),
|
||||
'avg_per_month' => round( $avg_per_day * 30, 2 ),
|
||||
'busiest_hour' => $busiest_hour,
|
||||
'busiest_day' => $busiest_day,
|
||||
'submissions_busiest_hour' => $max_submissions,
|
||||
'submissions_busiest_day' => $max_day_submissions,
|
||||
'this_month' => $this_month,
|
||||
'last_month' => $last_month,
|
||||
'peak_month' => $peak_month,
|
||||
'month_over_month_change' => $last_month > 0 ? round( ( ( $this_month - $last_month ) / $last_month ) * 100, 2 ) : 0,
|
||||
'consecutive_successes' => $consecutive_successes,
|
||||
'consecutive_failures' => $consecutive_failures,
|
||||
'longest_success_streak' => (int) Cmatic_Options_Repository::get_option( 'submissions.longest_success_streak', 0 ),
|
||||
'active_forms_count' => (int) Cmatic_Options_Repository::get_option( 'submissions.active_forms', 0 ),
|
||||
'forms_with_submissions' => count( Cmatic_Options_Repository::get_option( 'submissions.forms_used', array() ) ),
|
||||
);
|
||||
|
||||
return array_filter( $data, fn( $v ) => $v !== 0 && $v !== 0.0 );
|
||||
}
|
||||
|
||||
private static function get_busiest_hour(): array {
|
||||
$hourly_distribution = Cmatic_Options_Repository::get_option( 'submissions.hourly', array() );
|
||||
$busiest_hour = 0;
|
||||
$max_submissions = 0;
|
||||
|
||||
foreach ( $hourly_distribution as $hour => $count ) {
|
||||
if ( $count > $max_submissions ) {
|
||||
$max_submissions = $count;
|
||||
$busiest_hour = (int) $hour;
|
||||
}
|
||||
}
|
||||
|
||||
return array( $busiest_hour, $max_submissions );
|
||||
}
|
||||
|
||||
private static function get_busiest_day(): array {
|
||||
$daily_distribution = Cmatic_Options_Repository::get_option( 'submissions.daily', array() );
|
||||
$busiest_day = 0;
|
||||
$max_day_submissions = 0;
|
||||
|
||||
foreach ( $daily_distribution as $day => $count ) {
|
||||
if ( $count > $max_day_submissions ) {
|
||||
$max_day_submissions = $count;
|
||||
$busiest_day = (int) $day;
|
||||
}
|
||||
}
|
||||
|
||||
return array( $busiest_day, $max_day_submissions );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* WordPress data collector.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core\Collectors;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class WordPress_Collector {
|
||||
public static function collect(): array {
|
||||
global $wpdb;
|
||||
|
||||
$post_counts = wp_count_posts( 'post' );
|
||||
$page_counts = wp_count_posts( 'page' );
|
||||
$comment_counts = wp_count_comments();
|
||||
$user_count = count_users();
|
||||
$media_counts = wp_count_posts( 'attachment' );
|
||||
$category_count = wp_count_terms( 'category' );
|
||||
$tag_count = wp_count_terms( 'post_tag' );
|
||||
$revision_count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = 'revision'" );
|
||||
|
||||
$data = array(
|
||||
'posts_published' => isset( $post_counts->publish ) ? (int) $post_counts->publish : 0,
|
||||
'posts_draft' => isset( $post_counts->draft ) ? (int) $post_counts->draft : 0,
|
||||
'pages_published' => isset( $page_counts->publish ) ? (int) $page_counts->publish : 0,
|
||||
'media_items' => isset( $media_counts->inherit ) ? (int) $media_counts->inherit : 0,
|
||||
'comments_pending' => isset( $comment_counts->moderated ) ? (int) $comment_counts->moderated : 0,
|
||||
'comments_spam' => isset( $comment_counts->spam ) ? (int) $comment_counts->spam : 0,
|
||||
'users_total' => isset( $user_count['total_users'] ) ? (int) $user_count['total_users'] : 0,
|
||||
'users_administrators' => isset( $user_count['avail_roles']['administrator'] ) ? (int) $user_count['avail_roles']['administrator'] : 0,
|
||||
'users_editors' => isset( $user_count['avail_roles']['editor'] ) ? (int) $user_count['avail_roles']['editor'] : 0,
|
||||
'users_authors' => isset( $user_count['avail_roles']['author'] ) ? (int) $user_count['avail_roles']['author'] : 0,
|
||||
'users_subscribers' => isset( $user_count['avail_roles']['subscriber'] ) ? (int) $user_count['avail_roles']['subscriber'] : 0,
|
||||
'categories_count' => is_wp_error( $category_count ) ? 0 : (int) $category_count,
|
||||
'tags_count' => is_wp_error( $tag_count ) ? 0 : (int) $tag_count,
|
||||
'revisions_count' => (int) $revision_count,
|
||||
);
|
||||
|
||||
$data = array_filter( $data, fn( $v ) => $v !== 0 );
|
||||
$data['auto_updates_enabled'] = (bool) get_option( 'auto_update_plugins', false );
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics scheduler.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core;
|
||||
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Scheduler {
|
||||
|
||||
public static function init() {
|
||||
add_filter( 'cron_schedules', array( __CLASS__, 'add_cron_intervals' ) );
|
||||
add_action( 'cmatic_metrics_heartbeat', array( __CLASS__, 'execute_heartbeat' ) );
|
||||
add_action( 'admin_init', array( __CLASS__, 'ensure_schedule' ) );
|
||||
add_action( 'cmatic_subscription_success', array( __CLASS__, 'execute_heartbeat' ) );
|
||||
}
|
||||
|
||||
public static function add_cron_intervals( $schedules ) {
|
||||
$schedules['cmatic_2min'] = array(
|
||||
'interval' => 2 * MINUTE_IN_SECONDS,
|
||||
'display' => 'Every 2 Minutes',
|
||||
);
|
||||
|
||||
$schedules['cmatic_10min'] = array(
|
||||
'interval' => 10 * MINUTE_IN_SECONDS,
|
||||
'display' => 'Every 10 Minutes',
|
||||
);
|
||||
|
||||
$interval_hours = Storage::get_heartbeat_interval();
|
||||
$schedules['cmatic_sparse'] = array(
|
||||
'interval' => $interval_hours * HOUR_IN_SECONDS,
|
||||
'display' => 'Every ' . $interval_hours . ' Hours',
|
||||
);
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
public static function execute_heartbeat() {
|
||||
if ( ! Storage::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
$schedule = Storage::get_schedule();
|
||||
$started_at = Storage::get_frequent_started_at();
|
||||
$elapsed = $started_at > 0 ? time() - $started_at : 0;
|
||||
|
||||
if ( 'super_frequent' === $schedule && $elapsed >= ( 15 * MINUTE_IN_SECONDS ) ) {
|
||||
self::transition_to_frequent();
|
||||
} elseif ( 'frequent' === $schedule && $elapsed >= ( 1 * HOUR_IN_SECONDS ) ) {
|
||||
self::transition_to_sparse();
|
||||
}
|
||||
|
||||
self::sync_global_lisdata();
|
||||
$payload = Collector::collect( 'heartbeat' );
|
||||
Sync::send( $payload );
|
||||
}
|
||||
|
||||
private static function transition_to_frequent() {
|
||||
Storage::set_schedule( 'frequent' );
|
||||
|
||||
$timestamp = wp_next_scheduled( 'cmatic_metrics_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
|
||||
wp_schedule_event( time(), 'cmatic_10min', 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
|
||||
private static function transition_to_sparse() {
|
||||
Storage::set_schedule( 'sparse' );
|
||||
|
||||
$timestamp = wp_next_scheduled( 'cmatic_metrics_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
|
||||
$interval_hours = Storage::get_heartbeat_interval();
|
||||
wp_schedule_single_event(
|
||||
time() + ( $interval_hours * HOUR_IN_SECONDS ),
|
||||
'cmatic_metrics_heartbeat'
|
||||
);
|
||||
}
|
||||
|
||||
public static function ensure_schedule() {
|
||||
if ( ! Storage::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( wp_next_scheduled( 'cmatic_metrics_heartbeat' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schedule = Storage::get_schedule();
|
||||
$started_at = Storage::get_frequent_started_at();
|
||||
$elapsed = $started_at > 0 ? time() - $started_at : 0;
|
||||
|
||||
if ( 'super_frequent' === $schedule ) {
|
||||
if ( $elapsed >= ( 15 * MINUTE_IN_SECONDS ) ) {
|
||||
self::transition_to_frequent();
|
||||
} else {
|
||||
wp_schedule_event( time(), 'cmatic_2min', 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
} elseif ( 'frequent' === $schedule ) {
|
||||
if ( Storage::is_frequent_elapsed() ) {
|
||||
self::transition_to_sparse();
|
||||
} else {
|
||||
wp_schedule_event( time(), 'cmatic_10min', 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
} else {
|
||||
$interval_hours = Storage::get_heartbeat_interval();
|
||||
$last_heartbeat = Storage::get_last_heartbeat();
|
||||
$next_heartbeat = $last_heartbeat + ( $interval_hours * HOUR_IN_SECONDS );
|
||||
|
||||
if ( $next_heartbeat < time() ) {
|
||||
$next_heartbeat = time();
|
||||
}
|
||||
|
||||
wp_schedule_single_event( $next_heartbeat, 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
}
|
||||
|
||||
public static function schedule_next_sparse() {
|
||||
if ( 'sparse' !== Storage::get_schedule() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$timestamp = wp_next_scheduled( 'cmatic_metrics_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
|
||||
$interval_hours = Storage::get_heartbeat_interval();
|
||||
wp_schedule_single_event(
|
||||
time() + ( $interval_hours * HOUR_IN_SECONDS ),
|
||||
'cmatic_metrics_heartbeat'
|
||||
);
|
||||
}
|
||||
|
||||
public static function clear_schedule() {
|
||||
$timestamp = wp_next_scheduled( 'cmatic_metrics_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
}
|
||||
|
||||
private static function sync_global_lisdata() {
|
||||
global $wpdb;
|
||||
$form_options = $wpdb->get_results(
|
||||
"SELECT option_value FROM {$wpdb->options}
|
||||
WHERE option_name LIKE 'cf7_mch_%'
|
||||
AND option_value LIKE '%lisdata%'
|
||||
LIMIT 50"
|
||||
);
|
||||
|
||||
$best_lisdata = null;
|
||||
$best_list_count = 0;
|
||||
|
||||
if ( ! empty( $form_options ) ) {
|
||||
foreach ( $form_options as $row ) {
|
||||
$cf7_mch = maybe_unserialize( $row->option_value );
|
||||
if ( ! is_array( $cf7_mch ) || empty( $cf7_mch['lisdata']['lists'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list_count = count( $cf7_mch['lisdata']['lists'] );
|
||||
if ( $list_count > $best_list_count ) {
|
||||
$best_list_count = $list_count;
|
||||
$best_lisdata = $cf7_mch['lisdata'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always update - either with found data or empty array to clear stale data.
|
||||
Cmatic_Options_Repository::set_option( 'lisdata', $best_lisdata ?? array() );
|
||||
Cmatic_Options_Repository::set_option( 'lisdata_updated', time() );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics storage handler.
|
||||
*
|
||||
* Delegates install_id, quest, and lifecycle tracking to Cmatic_Install_Data
|
||||
* and Cmatic_Activator/Cmatic_Deactivator classes (single source of truth).
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core;
|
||||
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Storage {
|
||||
|
||||
public static function init() {
|
||||
if ( null !== Cmatic_Options_Repository::get_option( 'telemetry.enabled' ) ) {
|
||||
return;
|
||||
}
|
||||
$defaults = array(
|
||||
'enabled' => true,
|
||||
'opt_in_date' => time(),
|
||||
'disabled_count' => 0,
|
||||
'heartbeat_interval' => 48,
|
||||
'schedule' => 'frequent',
|
||||
'frequent_started_at' => time(),
|
||||
'last_heartbeat' => 0,
|
||||
'heartbeat_count' => 0,
|
||||
'failed_count' => 0,
|
||||
'last_payload_hash' => '',
|
||||
'last_error' => '',
|
||||
);
|
||||
|
||||
foreach ( $defaults as $key => $value ) {
|
||||
Cmatic_Options_Repository::set_option( "telemetry.{$key}", $value );
|
||||
}
|
||||
}
|
||||
|
||||
public static function is_enabled() {
|
||||
return (bool) Cmatic_Options_Repository::get_option( 'telemetry.enabled', true );
|
||||
}
|
||||
|
||||
public static function get_schedule() {
|
||||
return Cmatic_Options_Repository::get_option( 'telemetry.schedule', 'frequent' );
|
||||
}
|
||||
|
||||
public static function set_schedule( $schedule ) {
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.schedule', $schedule );
|
||||
|
||||
if ( 'frequent' === $schedule ) {
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.frequent_started_at', time() );
|
||||
}
|
||||
}
|
||||
|
||||
public static function get_heartbeat_interval() {
|
||||
return (int) Cmatic_Options_Repository::get_option( 'telemetry.heartbeat_interval', 48 );
|
||||
}
|
||||
|
||||
public static function get_last_heartbeat() {
|
||||
return (int) Cmatic_Options_Repository::get_option( 'telemetry.last_heartbeat', 0 );
|
||||
}
|
||||
|
||||
public static function update_last_heartbeat( $timestamp = null ) {
|
||||
if ( null === $timestamp ) {
|
||||
$timestamp = time();
|
||||
}
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.last_heartbeat', $timestamp );
|
||||
}
|
||||
|
||||
public static function increment_heartbeat_count() {
|
||||
$count = (int) Cmatic_Options_Repository::get_option( 'telemetry.heartbeat_count', 0 );
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.heartbeat_count', $count + 1 );
|
||||
}
|
||||
|
||||
public static function increment_failed_count() {
|
||||
$count = (int) Cmatic_Options_Repository::get_option( 'telemetry.failed_count', 0 );
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.failed_count', $count + 1 );
|
||||
}
|
||||
|
||||
public static function increment_disabled_count() {
|
||||
$count = (int) Cmatic_Options_Repository::get_option( 'telemetry.disabled_count', 0 );
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.disabled_count', $count + 1 );
|
||||
}
|
||||
|
||||
public static function get_frequent_started_at() {
|
||||
return (int) Cmatic_Options_Repository::get_option( 'telemetry.frequent_started_at', 0 );
|
||||
}
|
||||
|
||||
public static function is_frequent_elapsed() {
|
||||
$started_at = self::get_frequent_started_at();
|
||||
if ( 0 === $started_at ) {
|
||||
return false;
|
||||
}
|
||||
$elapsed = time() - $started_at;
|
||||
return $elapsed >= ( 1 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
public static function record_activation() {
|
||||
}
|
||||
|
||||
public static function record_deactivation() {
|
||||
}
|
||||
|
||||
public static function is_reactivation() {
|
||||
return (bool) Cmatic_Options_Repository::get_option( 'lifecycle.is_reactivation', false );
|
||||
}
|
||||
|
||||
public static function get_activation_count() {
|
||||
$activations = Cmatic_Options_Repository::get_option( 'lifecycle.activations', array() );
|
||||
return is_array( $activations ) ? count( $activations ) : 0;
|
||||
}
|
||||
|
||||
public static function get_deactivation_count() {
|
||||
$deactivations = Cmatic_Options_Repository::get_option( 'lifecycle.deactivations', array() );
|
||||
return is_array( $deactivations ) ? count( $deactivations ) : 0;
|
||||
}
|
||||
|
||||
public static function get_install_id(): string {
|
||||
$install_id = \Cmatic_Options_Repository::get_option( 'install.id', '' );
|
||||
if ( ! empty( $install_id ) ) {
|
||||
return $install_id;
|
||||
}
|
||||
|
||||
$install_data = new \Cmatic_Install_Data( \Cmatic_Options_Repository::instance() );
|
||||
return $install_data->get_install_id();
|
||||
}
|
||||
|
||||
public static function get_quest(): int {
|
||||
$quest = (int) \Cmatic_Options_Repository::get_option( 'install.quest', 0 );
|
||||
if ( $quest >= \Cmatic_Install_Data::MIN_VALID_TIMESTAMP ) {
|
||||
return $quest;
|
||||
}
|
||||
|
||||
$install_data = new \Cmatic_Install_Data( \Cmatic_Options_Repository::instance() );
|
||||
return $install_data->get_quest();
|
||||
}
|
||||
|
||||
public static function save_error( $error ): void {
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.last_error', $error );
|
||||
}
|
||||
|
||||
public static function save_payload_hash( $hash ) {
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.last_payload_hash', $hash );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics sync handler.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core;
|
||||
|
||||
use Cmatic\Metrics\Security\Signature;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Sync {
|
||||
|
||||
private static $endpoint_url = '';
|
||||
|
||||
public static function set_endpoint( $url ) {
|
||||
self::$endpoint_url = $url;
|
||||
}
|
||||
|
||||
public static function send( $payload ) {
|
||||
try {
|
||||
if ( empty( self::$endpoint_url ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$request = self::prepare_request( $payload );
|
||||
|
||||
$response = wp_remote_post(
|
||||
self::$endpoint_url,
|
||||
array(
|
||||
'body' => wp_json_encode( $request ),
|
||||
'headers' => array( 'Content-Type' => 'application/json' ),
|
||||
'timeout' => 5,
|
||||
)
|
||||
);
|
||||
|
||||
return self::handle_response( $response, $payload );
|
||||
} catch ( \Exception $e ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function send_async( $payload ) {
|
||||
try {
|
||||
if ( empty( self::$endpoint_url ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = self::prepare_request( $payload );
|
||||
|
||||
wp_remote_post(
|
||||
self::$endpoint_url,
|
||||
array(
|
||||
'body' => wp_json_encode( $request ),
|
||||
'headers' => array( 'Content-Type' => 'application/json' ),
|
||||
'timeout' => 5,
|
||||
'blocking' => false,
|
||||
)
|
||||
);
|
||||
|
||||
Storage::update_last_heartbeat();
|
||||
} catch ( \Exception $e ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static function prepare_request( $payload ) {
|
||||
$install_id = Storage::get_install_id();
|
||||
$timestamp = time();
|
||||
$payload_json = wp_json_encode( $payload );
|
||||
$signature = Signature::generate( $install_id, $timestamp, $payload_json );
|
||||
|
||||
return array(
|
||||
'install_id' => $install_id,
|
||||
'timestamp' => $timestamp,
|
||||
'signature' => $signature,
|
||||
'public_key' => Signature::get_public_key(),
|
||||
'payload_json' => $payload_json,
|
||||
);
|
||||
}
|
||||
|
||||
private static function handle_response( $response, $payload ) {
|
||||
if ( is_wp_error( $response ) ) {
|
||||
self::handle_failure( $response->get_error_message(), $payload );
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( 200 !== $code && 201 !== $code ) {
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
self::handle_failure( "HTTP {$code}: {$body}", $payload );
|
||||
return false;
|
||||
}
|
||||
|
||||
self::handle_success( $payload );
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function handle_success( $payload ) {
|
||||
Storage::update_last_heartbeat();
|
||||
|
||||
$payload_hash = md5( wp_json_encode( $payload ) );
|
||||
Storage::save_payload_hash( $payload_hash );
|
||||
Storage::save_error( '' );
|
||||
|
||||
if ( 'sparse' === Storage::get_schedule() ) {
|
||||
Scheduler::schedule_next_sparse();
|
||||
}
|
||||
}
|
||||
|
||||
private static function handle_failure( $error, $payload ) {
|
||||
Storage::update_last_heartbeat();
|
||||
Storage::increment_failed_count();
|
||||
Storage::save_error( $error );
|
||||
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
error_log( "[ChimpMatic Metrics] Heartbeat failed: {$error}" ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
}
|
||||
|
||||
if ( 'sparse' === Storage::get_schedule() ) {
|
||||
Scheduler::schedule_next_sparse();
|
||||
}
|
||||
}
|
||||
|
||||
public static function send_lifecycle_signal( $event ) {
|
||||
$options = get_option( 'cmatic', array() );
|
||||
|
||||
$default_enabled = 'activation' === $event;
|
||||
$telemetry_enabled = $options['telemetry']['enabled'] ?? $default_enabled;
|
||||
if ( ! $telemetry_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$install_id = $options['install']['id'] ?? '';
|
||||
if ( empty( $install_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
$payload = array(
|
||||
'event' => $event,
|
||||
'install_id' => $install_id,
|
||||
'version' => defined( 'SPARTAN_MCE_VERSION' ) ? SPARTAN_MCE_VERSION : '1.0.0',
|
||||
'site_url' => home_url(),
|
||||
'timestamp' => time(),
|
||||
'wp_version' => get_bloginfo( 'version' ),
|
||||
'php' => PHP_VERSION,
|
||||
'mysql_version' => $wpdb->db_version(),
|
||||
'software' => array(
|
||||
'server' => isset( $_SERVER['SERVER_SOFTWARE'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_SOFTWARE'] ) ) : 'unknown', // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
|
||||
),
|
||||
);
|
||||
|
||||
$timestamp = time();
|
||||
$payload_json = wp_json_encode( $payload );
|
||||
$signature = Signature::generate( $install_id, $timestamp, $payload_json );
|
||||
|
||||
$request = array(
|
||||
'install_id' => $install_id,
|
||||
'timestamp' => $timestamp,
|
||||
'signature' => $signature,
|
||||
'public_key' => Signature::get_public_key(),
|
||||
'payload_json' => $payload_json,
|
||||
);
|
||||
|
||||
wp_remote_post(
|
||||
'https://signls.dev/wp-json/chimpmatic/v1/telemetry',
|
||||
array(
|
||||
'body' => wp_json_encode( $request ),
|
||||
'headers' => array( 'Content-Type' => 'application/json' ),
|
||||
'timeout' => 5,
|
||||
'blocking' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* Metrics event tracker.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Core;
|
||||
|
||||
use Cmatic_Options_Repository;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Tracker {
|
||||
|
||||
public static function init() {
|
||||
add_action( 'cmatic_metrics_on_activation', array( __CLASS__, 'on_activation' ) );
|
||||
add_action( 'cmatic_metrics_on_deactivation', array( __CLASS__, 'on_deactivation' ) );
|
||||
}
|
||||
|
||||
public static function on_activation() {
|
||||
Storage::init();
|
||||
Storage::record_activation();
|
||||
Storage::set_schedule( 'super_frequent' );
|
||||
|
||||
if ( ! wp_next_scheduled( 'cmatic_metrics_heartbeat' ) ) {
|
||||
wp_schedule_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'cmatic_2min', 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
|
||||
self::send_event_heartbeat( 'activation' );
|
||||
}
|
||||
|
||||
public static function on_deactivation() {
|
||||
Storage::record_deactivation();
|
||||
|
||||
$timestamp = wp_next_scheduled( 'cmatic_metrics_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
|
||||
self::send_event_heartbeat( 'deactivation', true );
|
||||
}
|
||||
|
||||
private static function send_event_heartbeat( $event, $force = false ) {
|
||||
if ( ! $force && ! Storage::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payload = Collector::collect( $event );
|
||||
Sync::send_async( $payload );
|
||||
}
|
||||
|
||||
public static function on_opt_out() {
|
||||
Storage::increment_disabled_count();
|
||||
|
||||
$payload = Collector::collect( 'opt_out' );
|
||||
Sync::send_async( $payload );
|
||||
|
||||
$timestamp = wp_next_scheduled( 'cmatic_metrics_heartbeat' );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
}
|
||||
|
||||
public static function on_re_enable() {
|
||||
if ( ! Cmatic_Options_Repository::get_option( 'telemetry.opt_in_date' ) ) {
|
||||
Cmatic_Options_Repository::set_option( 'telemetry.opt_in_date', time() );
|
||||
}
|
||||
|
||||
Storage::set_schedule( 'super_frequent' );
|
||||
|
||||
if ( ! wp_next_scheduled( 'cmatic_metrics_heartbeat' ) ) {
|
||||
wp_schedule_event( time() + ( 2 * MINUTE_IN_SECONDS ), 'cmatic_2min', 'cmatic_metrics_heartbeat' );
|
||||
}
|
||||
|
||||
$payload = Collector::collect( 'reactivation' );
|
||||
Sync::send_async( $payload );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Cryptographic signature handler.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
namespace Cmatic\Metrics\Security;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
class Signature {
|
||||
|
||||
const PUBLIC_KEY = 'chimpmatic_lite_v1';
|
||||
|
||||
public static function generate( $install_id, $timestamp, $payload_json ) {
|
||||
$derived_secret = self::derive_secret( $install_id );
|
||||
$string_to_sign = $install_id . $timestamp . $payload_json;
|
||||
|
||||
return hash_hmac( 'sha256', $string_to_sign, $derived_secret );
|
||||
}
|
||||
|
||||
public static function derive_secret( $install_id ) {
|
||||
return hash( 'sha256', $install_id . self::PUBLIC_KEY );
|
||||
}
|
||||
|
||||
public static function validate( $signature, $install_id, $timestamp, $payload_json ) {
|
||||
$expected = self::generate( $install_id, $timestamp, $payload_json );
|
||||
return hash_equals( $expected, $signature );
|
||||
}
|
||||
|
||||
public static function get_public_key() {
|
||||
return self::PUBLIC_KEY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
spl_autoload_register( function ( $class ) {
|
||||
$prefix = 'Cmatic\\Metrics\\';
|
||||
if ( strncmp( $prefix, $class, strlen( $prefix ) ) !== 0 ) {
|
||||
return;
|
||||
}
|
||||
$file = __DIR__ . '/' . str_replace( '\\', '/', substr( $class, strlen( $prefix ) ) ) . '.php';
|
||||
if ( file_exists( $file ) ) {
|
||||
require $file;
|
||||
}
|
||||
} );
|
||||
@@ -0,0 +1,441 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin bar menu integration.
|
||||
*
|
||||
* @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_Admin_Bar_Menu {
|
||||
const MENU_IDENTIFIER = 'chimpmatic-menu';
|
||||
private static $instance = null;
|
||||
|
||||
public static function instance() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
$this->register_hooks();
|
||||
}
|
||||
|
||||
private function register_hooks() {
|
||||
add_action( 'admin_bar_menu', array( $this, 'add_menu' ), 95 );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
|
||||
add_action( 'admin_footer', array( $this, 'render_upgrade_click_script' ) );
|
||||
add_action( 'wp_footer', array( $this, 'render_upgrade_click_script' ) );
|
||||
}
|
||||
|
||||
private function can_show_menu() {
|
||||
return current_user_can( 'manage_options' ) && is_admin_bar_showing();
|
||||
}
|
||||
|
||||
private function is_pro_active() {
|
||||
return function_exists( 'cmatic_is_blessed' ) && cmatic_is_blessed();
|
||||
}
|
||||
|
||||
private function is_pro_installed_not_licensed() {
|
||||
if ( ! defined( 'CMATIC_VERSION' ) ) {
|
||||
return false;
|
||||
}
|
||||
return ! $this->is_pro_active();
|
||||
}
|
||||
|
||||
private function has_plugin_update() {
|
||||
$updates = get_site_transient( 'update_plugins' );
|
||||
if ( ! $updates || ! isset( $updates->response ) ) {
|
||||
return false;
|
||||
}
|
||||
return isset( $updates->response['contact-form-7-mailchimp-extension/chimpmatic-lite.php'] )
|
||||
|| isset( $updates->response['chimpmatic/chimpmatic.php'] );
|
||||
}
|
||||
|
||||
private function should_show_upgrade_badge() {
|
||||
return ! Cmatic_Options_Repository::get_option( 'ui.upgrade_clicked', false );
|
||||
}
|
||||
|
||||
private function get_license_activation_url() {
|
||||
return admin_url( 'admin.php?page=wpcf7-integration&service=0_chimpmatic&action=setup' );
|
||||
}
|
||||
|
||||
private function get_update_url() {
|
||||
return admin_url( 'plugins.php?plugin_status=upgrade' );
|
||||
}
|
||||
|
||||
public function add_menu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
if ( ! $this->can_show_menu() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->add_root_menu( $wp_admin_bar );
|
||||
$this->add_submenu_items( $wp_admin_bar );
|
||||
}
|
||||
|
||||
private function add_root_menu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
$badge_count = 0;
|
||||
|
||||
if ( $this->has_plugin_update() ) {
|
||||
++$badge_count;
|
||||
}
|
||||
|
||||
if ( ! $this->is_pro_active() && $this->should_show_upgrade_badge() ) {
|
||||
++$badge_count;
|
||||
}
|
||||
|
||||
$icon_svg = 'data:image/svg+xml;base64,' . $this->get_icon_base64();
|
||||
$icon_styles = 'width:26px;height:30px;float:left;background:url(\'' . esc_attr( $icon_svg ) . '\') center/20px no-repeat;';
|
||||
|
||||
$title = '<div id="cmatic-ab-icon" class="ab-item cmatic-logo svg" style="' . esc_attr( $icon_styles ) . '">';
|
||||
$title .= '<span class="screen-reader-text">' . esc_html__( 'Chimpmatic Lite', 'chimpmatic-lite' ) . '</span>';
|
||||
$title .= '</div>';
|
||||
|
||||
if ( $badge_count > 0 ) {
|
||||
$title .= $this->get_notification_counter( $badge_count );
|
||||
}
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'id' => self::MENU_IDENTIFIER,
|
||||
'title' => $title,
|
||||
'href' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function add_submenu_items( WP_Admin_Bar $wp_admin_bar ) {
|
||||
if ( $this->has_plugin_update() ) {
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-update',
|
||||
'title' => esc_html__( 'Update Available', 'chimpmatic-lite' ) . ' ' . $this->get_notification_counter( 1 ),
|
||||
'href' => $this->get_update_url(),
|
||||
'meta' => array(
|
||||
'title' => esc_attr__( 'Update strongly recommended', 'chimpmatic-lite' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->is_pro_installed_not_licensed() ) {
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-activate-license',
|
||||
'title' => esc_html__( 'Activate License', 'chimpmatic-lite' ),
|
||||
'href' => $this->get_license_activation_url(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Add Forms submenu with all CF7 forms.
|
||||
$this->add_forms_submenu( $wp_admin_bar );
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-docs',
|
||||
'title' => esc_html__( 'Documentation', 'chimpmatic-lite' ),
|
||||
'href' => Cmatic_Pursuit::adminbar( 'docs', 'menu_docs' ),
|
||||
'meta' => array(
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-support',
|
||||
'title' => esc_html__( 'Support', 'chimpmatic-lite' ),
|
||||
'href' => Cmatic_Pursuit::adminbar( 'support', 'menu_support' ),
|
||||
'meta' => array(
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-reviews',
|
||||
'title' => esc_html__( 'Reviews', 'chimpmatic-lite' ),
|
||||
'href' => 'https://wordpress.org/support/plugin/contact-form-7-mailchimp-extension/reviews/',
|
||||
'meta' => array(
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $this->is_pro_active() ) {
|
||||
$upgrade_title = esc_html__( 'Upgrade to Pro', 'chimpmatic-lite' );
|
||||
|
||||
if ( $this->should_show_upgrade_badge() ) {
|
||||
$upgrade_title .= ' ' . $this->get_notification_counter( 1 );
|
||||
}
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-upgrade',
|
||||
'title' => $upgrade_title,
|
||||
'href' => Cmatic_Pursuit::adminbar( 'pricing', 'menu_upgrade' ),
|
||||
'meta' => array(
|
||||
'target' => '_blank',
|
||||
'rel' => 'noopener noreferrer',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function add_forms_submenu( WP_Admin_Bar $wp_admin_bar ) {
|
||||
// Check if CF7 is active.
|
||||
if ( ! class_exists( 'WPCF7_ContactForm' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all CF7 forms.
|
||||
$forms = WPCF7_ContactForm::find( array( 'posts_per_page' => -1 ) );
|
||||
|
||||
if ( empty( $forms ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add "Form Settings" section header (non-clickable label).
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-forms-header',
|
||||
'title' => esc_html__( 'Form Settings', 'chimpmatic-lite' ),
|
||||
'href' => false,
|
||||
)
|
||||
);
|
||||
|
||||
// Add each form directly to main menu (flat, not nested).
|
||||
foreach ( $forms as $form ) {
|
||||
$form_url = admin_url(
|
||||
sprintf(
|
||||
'admin.php?page=wpcf7&post=%d&action=edit&active-tab=Chimpmatic',
|
||||
$form->id()
|
||||
)
|
||||
);
|
||||
|
||||
// Check API connection status for this form.
|
||||
$api_status = $this->get_form_api_status( $form->id() );
|
||||
|
||||
$wp_admin_bar->add_menu(
|
||||
array(
|
||||
'parent' => self::MENU_IDENTIFIER,
|
||||
'id' => 'chimpmatic-form-' . $form->id(),
|
||||
'title' => ' ' . esc_html( $form->title() ) . $api_status,
|
||||
'href' => $form_url,
|
||||
'meta' => array(
|
||||
'class' => 'cmatic-form-item',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function get_form_api_status( $form_id ) {
|
||||
$cf7_mch = get_option( 'cf7_mch_' . $form_id, array() );
|
||||
|
||||
// Check if API is validated and a list/audience is selected.
|
||||
$is_connected = ! empty( $cf7_mch['api-validation'] )
|
||||
&& 1 == $cf7_mch['api-validation']
|
||||
&& ! empty( $cf7_mch['list'] );
|
||||
|
||||
if ( $is_connected ) {
|
||||
return '<span class="cmatic-api-status cmatic-api-connected" title="' . esc_attr__( 'Connected to Mailchimp API', 'chimpmatic-lite' ) . '">' . esc_html__( 'API', 'chimpmatic-lite' ) . '</span>';
|
||||
}
|
||||
|
||||
return '<span class="cmatic-api-status cmatic-api-disconnected" title="' . esc_attr__( 'Not connected to Mailchimp API', 'chimpmatic-lite' ) . '">' . esc_html__( 'API', 'chimpmatic-lite' ) . '</span>';
|
||||
}
|
||||
|
||||
private function get_notification_counter( $count ) {
|
||||
if ( $count < 1 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$screen_reader_text = sprintf(
|
||||
/* translators: %s: number of notifications */
|
||||
_n( '%s notification', '%s notifications', $count, 'chimpmatic-lite' ),
|
||||
number_format_i18n( $count )
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div class="wp-core-ui wp-ui-notification cmatic-issue-counter"><span aria-hidden="true">%1$d</span><span class="screen-reader-text">%2$s</span></div>',
|
||||
(int) $count,
|
||||
esc_html( $screen_reader_text )
|
||||
);
|
||||
}
|
||||
|
||||
private function get_settings_url() {
|
||||
if ( class_exists( 'Cmatic_Plugin_Links' ) ) {
|
||||
$url = Cmatic_Plugin_Links::get_settings_url();
|
||||
if ( ! empty( $url ) ) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
return admin_url( 'admin.php?page=wpcf7' );
|
||||
}
|
||||
|
||||
public function enqueue_assets() {
|
||||
if ( ! $this->can_show_menu() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$css = $this->get_inline_css();
|
||||
wp_add_inline_style( 'admin-bar', $css );
|
||||
}
|
||||
|
||||
private function get_inline_css() {
|
||||
$icon_base64 = $this->get_icon_base64();
|
||||
|
||||
$css = '
|
||||
#wpadminbar .cmatic-logo.svg {
|
||||
background-image: url("data:image/svg+xml;base64,' . $icon_base64 . '");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 20px;
|
||||
float: left;
|
||||
height: 30px;
|
||||
width: 26px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
#wpadminbar #wp-admin-bar-chimpmatic-menu .cmatic-form-item .ab-item {
|
||||
background-color: rgba(255,255,255,0.04) !important;
|
||||
padding-left: 20px !important;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
#wpadminbar #wp-admin-bar-chimpmatic-menu .cmatic-form-item .ab-item:hover {
|
||||
background-color: rgba(255,255,255,0.1) !important;
|
||||
}
|
||||
#wpadminbar .cmatic-api-status {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#wpadminbar .cmatic-api-connected {
|
||||
color: #00ba37;
|
||||
}
|
||||
#wpadminbar .cmatic-api-disconnected {
|
||||
color: #787c82;
|
||||
}
|
||||
#wpadminbar .cmatic-issue-counter {
|
||||
background-color: #d63638;
|
||||
border-radius: 9px;
|
||||
color: #fff;
|
||||
display: inline;
|
||||
padding: 1px 7px 1px 6px !important;
|
||||
}
|
||||
#wpadminbar .quicklinks #wp-admin-bar-chimpmatic-menu #wp-admin-bar-chimpmatic-menu-default li#wp-admin-bar-chimpmatic-upgrade {
|
||||
display: flex;
|
||||
}
|
||||
#wpadminbar .quicklinks #wp-admin-bar-chimpmatic-menu #wp-admin-bar-chimpmatic-menu-default li#wp-admin-bar-chimpmatic-upgrade .ab-item {
|
||||
align-items: center;
|
||||
border-color: transparent;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
margin: 8px 12px;
|
||||
background-color: #00be28;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
padding: 6px 10px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: #fff !important;
|
||||
width: 100%;
|
||||
}
|
||||
#wpadminbar .quicklinks #wp-admin-bar-chimpmatic-menu #wp-admin-bar-chimpmatic-menu-default li#wp-admin-bar-chimpmatic-upgrade .ab-item:hover {
|
||||
background-color: #00a522;
|
||||
color: #fff !important;
|
||||
}
|
||||
#wpadminbar #wp-admin-bar-chimpmatic-upgrade .cmatic-issue-counter {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
min-width: 18px;
|
||||
border-radius: 50%;
|
||||
padding: 0 !important;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 6px;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
@media screen and (max-width: 782px) {
|
||||
#wpadminbar .cmatic-logo.svg {
|
||||
background-position: center 8px;
|
||||
background-size: 30px;
|
||||
height: 46px;
|
||||
width: 52px;
|
||||
}
|
||||
#wpadminbar .cmatic-logo + .cmatic-issue-counter {
|
||||
margin-left: -5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
';
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
private function get_icon_base64() {
|
||||
$svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2382878c">'
|
||||
. '<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/>'
|
||||
. '</svg>';
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
|
||||
return base64_encode( $svg );
|
||||
}
|
||||
|
||||
public function render_upgrade_click_script() {
|
||||
if ( ! $this->can_show_menu() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_pro_active() || ! $this->should_show_upgrade_badge() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
?>
|
||||
<script>
|
||||
(function() {
|
||||
var upgradeLink = document.querySelector('#wp-admin-bar-chimpmatic-upgrade > a');
|
||||
if (upgradeLink) {
|
||||
upgradeLink.addEventListener('click', function() {
|
||||
fetch('<?php echo esc_url( rest_url( 'chimpmatic-lite/v1/notices/dismiss' ) ); ?>', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': '<?php echo esc_js( wp_create_nonce( 'wp_rest' ) ); ?>'
|
||||
},
|
||||
body: JSON.stringify({ notice_id: 'upgrade' })
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Advanced settings panel renderer.
|
||||
*
|
||||
* @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_Advanced_Settings {
|
||||
public static function render(): void {
|
||||
?>
|
||||
<table class="form-table mt0 description">
|
||||
<tbody>
|
||||
|
||||
<tr class="">
|
||||
<th scope="row"><?php esc_html_e( 'Unsubscribed', 'chimpmatic-lite' ); ?></th>
|
||||
<td>
|
||||
<fieldset><legend class="screen-reader-text"><span><?php esc_html_e( 'Unsubscribed', 'chimpmatic-lite' ); ?></span></legend>
|
||||
<label class="cmatic-toggle">
|
||||
<input type="checkbox" id="wpcf7-mailchimp-addunsubscr" name="wpcf7-mailchimp[addunsubscr]" data-field="unsubscribed" value="1" <?php checked( Cmatic_Options_Repository::get_option( 'unsubscribed', false ), true ); ?> />
|
||||
<span class="cmatic-toggle-slider"></span>
|
||||
</label>
|
||||
<span class="cmatic-toggle-label"><?php esc_html_e( 'Marks submitted contacts as unsubscribed.', 'chimpmatic-lite' ); ?></span>
|
||||
<a href="<?php echo esc_url( Cmatic_Pursuit::docs( 'mailchimp-opt-in-checkbox', 'unsubscribed_help' ) ); ?>" class="helping-field" target="_blank" title="<?php esc_attr_e( 'Get help with Custom Fields', 'chimpmatic-lite' ); ?>"> <?php esc_html_e( 'Learn More', 'chimpmatic-lite' ); ?> </a>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Debug Logger', 'chimpmatic-lite' ); ?></th>
|
||||
<td>
|
||||
<fieldset><legend class="screen-reader-text"><span><?php esc_html_e( 'Debug Logger', 'chimpmatic-lite' ); ?></span></legend>
|
||||
<label class="cmatic-toggle">
|
||||
<input type="checkbox" id="wpcf7-mailchimp-logfileEnabled" data-field="debug" value="1" <?php checked( (bool) Cmatic_Options_Repository::get_option( 'debug', false ), true ); ?> />
|
||||
<span class="cmatic-toggle-slider"></span>
|
||||
</label>
|
||||
<span class="cmatic-toggle-label"><?php esc_html_e( 'Enables activity logging to help troubleshoot form issues.', 'chimpmatic-lite' ); ?></span>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Developer', 'chimpmatic-lite' ); ?></th>
|
||||
<td>
|
||||
<fieldset><legend class="screen-reader-text"><span><?php esc_html_e( 'Developer', 'chimpmatic-lite' ); ?></span></legend>
|
||||
<label class="cmatic-toggle">
|
||||
<input type="checkbox" id="wpcf7-mailchimp-cf-support" data-field="backlink" value="1" <?php checked( Cmatic_Options_Repository::get_option( 'backlink', false ), true ); ?> />
|
||||
<span class="cmatic-toggle-slider"></span>
|
||||
</label>
|
||||
<span class="cmatic-toggle-label"><?php esc_html_e( 'A backlink to my site, not compulsory, but appreciated', 'chimpmatic-lite' ); ?></span>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Auto Update', 'chimpmatic-lite' ); ?></th>
|
||||
<td>
|
||||
<fieldset><legend class="screen-reader-text"><span><?php esc_html_e( 'Auto Update', 'chimpmatic-lite' ); ?></span></legend>
|
||||
<label class="cmatic-toggle">
|
||||
<input type="checkbox" id="chimpmatic-update" data-field="auto_update" value="1" <?php checked( (bool) Cmatic_Options_Repository::get_option( 'auto_update', true ), true ); ?> />
|
||||
<span class="cmatic-toggle-slider"></span>
|
||||
</label>
|
||||
<span class="cmatic-toggle-label"><?php esc_html_e( 'Auto Update Chimpmatic Lite', 'chimpmatic-lite' ); ?></span>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'Help Us Improve', 'chimpmatic-lite' ); ?></th>
|
||||
<td>
|
||||
<fieldset><legend class="screen-reader-text"><span><?php esc_html_e( 'Help Us Improve Chimpmatic', 'chimpmatic-lite' ); ?></span></legend>
|
||||
<label class="cmatic-toggle">
|
||||
<input type="checkbox" id="cmatic-telemetry-enabled" data-field="telemetry" value="1" <?php checked( (bool) Cmatic_Options_Repository::get_option( 'telemetry.enabled', true ), true ); ?> />
|
||||
<span class="cmatic-toggle-slider"></span>
|
||||
</label>
|
||||
<span class="cmatic-toggle-label"><?php esc_html_e( 'Help us improve Chimpmatic by sharing anonymous usage data', 'chimpmatic-lite' ); ?></span>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th scope="row"><?php esc_html_e( 'License Reset', 'chimpmatic-lite' ); ?></th>
|
||||
<td>
|
||||
<fieldset><legend class="screen-reader-text"><span><?php esc_html_e( 'License Reset', 'chimpmatic-lite' ); ?></span></legend>
|
||||
<button type="button" id="cmatic-license-reset-btn" class="button"><?php esc_html_e( 'Reset License Data', 'chimpmatic-lite' ); ?></button>
|
||||
<div id="cmatic-license-reset-message" style="margin-top: 10px;"></div>
|
||||
<small class="description"><?php esc_html_e( 'Clears all cached license data. Use this if you see "zombie activation" issues after deactivating your license.', 'chimpmatic-lite' ); ?></small>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/**
|
||||
* API key panel UI 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_Api_Panel {
|
||||
const CMATIC_FB_C = '.com';
|
||||
|
||||
public static function mask_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( "\u{2022}", 20 ) . $suffix;
|
||||
}
|
||||
|
||||
public static function render( array $cf7_mch, string $apivalid = '0' ): void {
|
||||
$api_key = isset( $cf7_mch['api'] ) ? $cf7_mch['api'] : '';
|
||||
$masked_key = self::mask_key( $api_key );
|
||||
$is_masked = ! empty( $api_key ) && strlen( $api_key ) >= 12;
|
||||
$is_valid = '1' === $apivalid;
|
||||
|
||||
$btn_value = $is_valid ? 'Connected' : 'Sync Audiences';
|
||||
$btn_class = 'button';
|
||||
|
||||
$help_url = Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'api_panel_help' );
|
||||
|
||||
?>
|
||||
<div class="cmatic-field-group">
|
||||
<label for="cmatic-api"><?php echo esc_html__( 'MailChimp API Key:', 'chimpmatic-lite' ); ?></label><br />
|
||||
<div class="cmatic-api-wrap">
|
||||
<input
|
||||
type="text"
|
||||
id="cmatic-api"
|
||||
name="wpcf7-mailchimp[api]"
|
||||
class="wide"
|
||||
placeholder="<?php echo esc_attr__( 'Enter Your Mailchimp API key Here', 'chimpmatic-lite' ); ?>"
|
||||
value="<?php echo esc_attr( $is_masked ? $masked_key : $api_key ); ?>"
|
||||
data-masked-key="<?php echo esc_attr( $masked_key ); ?>"
|
||||
data-is-masked="<?php echo $is_masked ? '1' : '0'; ?>"
|
||||
data-has-key="<?php echo ! empty( $api_key ) ? '1' : '0'; ?>"
|
||||
/>
|
||||
<button type="button" class="cmatic-eye" title="<?php echo esc_attr__( 'Show/Hide', 'chimpmatic-lite' ); ?>">
|
||||
<span class="dashicons <?php echo $is_masked ? 'dashicons-visibility' : 'dashicons-hidden'; ?>"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input
|
||||
id="chm_activalist"
|
||||
type="button"
|
||||
value="<?php echo esc_attr( $btn_value ); ?>"
|
||||
class="<?php echo esc_attr( $btn_class ); ?>"
|
||||
/>
|
||||
|
||||
<small class="description need-api">
|
||||
<a href="<?php echo esc_url( $help_url ); ?>" class="helping-field" target="_blank" rel="noopener noreferrer" title="<?php echo esc_attr__( 'Get help with MailChimp API Key', 'chimpmatic-lite' ); ?>">
|
||||
<?php echo esc_html__( 'Find your Mailchimp API here', 'chimpmatic-lite' ); ?>
|
||||
<span class="red-icon dashicons dashicons-arrow-right"></span>
|
||||
<span class="red-icon dashicons dashicons-arrow-right"></span>
|
||||
</a>
|
||||
</small>
|
||||
<div id="chmp-new-user" class="new-user <?php echo esc_attr( '1' === $apivalid ? 'chmp-inactive' : 'chmp-active' ); ?>">
|
||||
<?php Cmatic_Banners::render_new_user_help(); ?>
|
||||
</div>
|
||||
</div><!-- .cmatic-field-group -->
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function output( array $cf7_mch, string $apivalid = '0' ): void {
|
||||
self::render( $cf7_mch, $apivalid );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Mailchimp audiences panel UI.
|
||||
*
|
||||
* @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_Audiences {
|
||||
public static function render( string $apivalid, ?array $listdata, array $cf7_mch ): void {
|
||||
// Handle list value - can be string or array (Pro stores as array).
|
||||
$raw_list = isset( $cf7_mch['list'] ) ? $cf7_mch['list'] : '';
|
||||
$vlist = is_array( $raw_list ) ? sanitize_text_field( reset( $raw_list ) ) : sanitize_text_field( $raw_list );
|
||||
$count = isset( $listdata['lists'] ) && is_array( $listdata['lists'] ) ? count( $listdata['lists'] ) : 0;
|
||||
|
||||
$help_url = Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'audiences_help' );
|
||||
|
||||
$disclosure_class = ( '1' === $apivalid ) ? 'chmp-active' : 'chmp-inactive';
|
||||
|
||||
?>
|
||||
<div class="cmatic-audiences cmatic-field-group <?php echo esc_attr( $disclosure_class ); ?>">
|
||||
<label for="wpcf7-mailchimp-list" id="cmatic-audiences-label">
|
||||
<?php
|
||||
if ( '1' === $apivalid && $count > 0 ) {
|
||||
printf(
|
||||
/* translators: %d: Number of Mailchimp audiences */
|
||||
esc_html__( 'Total Mailchimp Audiences: %d', 'chimpmatic-lite' ),
|
||||
(int) $count
|
||||
);
|
||||
} else {
|
||||
esc_html_e( 'Mailchimp Audiences', 'chimpmatic-lite' );
|
||||
}
|
||||
?>
|
||||
</label><br />
|
||||
|
||||
<select id="wpcf7-mailchimp-list" name="wpcf7-mailchimp[list]">
|
||||
<?php self::render_options( $listdata, $vlist ); ?>
|
||||
</select>
|
||||
|
||||
<button type="button" id="mce_fetch_fields" class="button">
|
||||
<?php esc_html_e( 'Sync Fields', 'chimpmatic-lite' ); ?>
|
||||
</button>
|
||||
|
||||
<small class="description">
|
||||
<?php esc_html_e( 'Hit the Connect button to load your lists', 'chimpmatic-lite' ); ?>
|
||||
<a href="<?php echo esc_url( $help_url ); ?>" class="helping-field" target="_blank" rel="noopener noreferrer" title="<?php esc_attr_e( 'Get help with MailChimp List ID', 'chimpmatic-lite' ); ?>">
|
||||
<?php esc_html_e( 'Learn More', 'chimpmatic-lite' ); ?>
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function render_options( ?array $listdata, string $selected_id = '' ): void {
|
||||
if ( ! isset( $listdata['lists'] ) || ! is_array( $listdata['lists'] ) || empty( $listdata['lists'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
foreach ( $listdata['lists'] as $list ) :
|
||||
if ( ! is_array( $list ) || ! isset( $list['id'], $list['name'] ) ) {
|
||||
continue;
|
||||
}
|
||||
++$i;
|
||||
$list_id = sanitize_text_field( $list['id'] );
|
||||
$list_name = sanitize_text_field( $list['name'] );
|
||||
$member_count = isset( $list['stats']['member_count'] ) ? (int) $list['stats']['member_count'] : 0;
|
||||
$field_count = isset( $list['stats']['merge_field_count'] ) ? (int) $list['stats']['merge_field_count'] : 0;
|
||||
$selected = selected( $selected_id, $list_id, false );
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $list_id ); ?>" <?php echo $selected; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- selected() returns pre-escaped output ?>>
|
||||
<?php
|
||||
printf(
|
||||
'%d:%d %s %d fields #%s',
|
||||
(int) $i,
|
||||
(int) $member_count,
|
||||
esc_html( $list_name ),
|
||||
(int) $field_count,
|
||||
esc_html( $list_id )
|
||||
);
|
||||
?>
|
||||
</option>
|
||||
<?php
|
||||
endforeach;
|
||||
}
|
||||
|
||||
public static function output( string $apivalid, ?array $listdata, array $cf7_mch ): void {
|
||||
self::render( $apivalid, $listdata, $cf7_mch );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* Admin UI banners and frontend credit.
|
||||
*
|
||||
* @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_Banners {
|
||||
public static function init(): void {
|
||||
add_filter( 'wpcf7_form_response_output', array( __CLASS__, 'maybe_append_backlink' ), 10, 4 );
|
||||
}
|
||||
|
||||
public static function maybe_append_backlink( string $output, string $class, string $content, $contact_form ): string {
|
||||
// Check if backlink setting is enabled.
|
||||
if ( ! Cmatic_Options_Repository::get_option( 'backlink', false ) ) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
// Check if this form has Chimpmatic configured.
|
||||
$form_id = $contact_form->id();
|
||||
$cf7_mch = get_option( 'cf7_mch_' . $form_id, array() );
|
||||
|
||||
if ( empty( $cf7_mch ) || empty( $cf7_mch['api-validation'] ) ) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
// Append the author credit.
|
||||
return $output . self::get_author_credit();
|
||||
}
|
||||
|
||||
private const DEFAULT_WELCOME = '<p class="about-description">Hello. My name is Renzo, I <span alt="f487" class="dashicons dashicons-heart red-icon"> </span> WordPress and I develop this free plugin to help users like you. I drink copious amounts of coffee to keep me running longer <span alt="f487" class="dashicons dashicons-smiley red-icon"> </span>. If you\'ve found this plugin useful, please consider making a donation.</p><br>
|
||||
<p class="about-description">Would you like to <a class="button-primary" href="https://bit.ly/cafe4renzo" target="_blank">buy me a coffee?</a> or <a class="button-primary" href="https://bit.ly/cafe4renzo" target="_blank">Donate with Paypal</a></p>';
|
||||
|
||||
public static function get_welcome(): string {
|
||||
$banner = get_site_option( 'mce_conten_panel_welcome', self::DEFAULT_WELCOME );
|
||||
|
||||
return empty( trim( $banner ) ) ? self::DEFAULT_WELCOME : $banner;
|
||||
}
|
||||
|
||||
public static function render_lateral(): void {
|
||||
?>
|
||||
<div id="informationdiv_aux" class="postbox mce-move mce-hidden mc-lateral">
|
||||
<?php echo wp_kses_post( self::get_lateral_content() ); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function get_lateral_content(): string {
|
||||
return '
|
||||
<div class="inside bg-f2"><h3>Upgrade to PRO</h3>
|
||||
<p>We have the the best tool to integrate <b>Contact Form 7</b> & <b>Mailchimp.com</b> mailing lists. We have new nifty features:</p>
|
||||
<ul>
|
||||
<li>Tag Existing Subscribers</li>
|
||||
<li>Group Existing Subscribers</li>
|
||||
<li>Email Verification</li>
|
||||
<li>AWESOME Support And more!</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="promo-2022">
|
||||
<h1>40<span>%</span> Off!</h1>
|
||||
<p class="interesting">Submit your name and email and we\'ll send you a coupon for <b>40% off</b> your upgrade to the pro version.</p>
|
||||
<div class="cm-form" id="promo-form-container">
|
||||
<!-- Form will be injected by JavaScript after page load to prevent CF7 from stripping it -->
|
||||
</div>
|
||||
</div>';
|
||||
}
|
||||
|
||||
public static function render_new_user_help(): void {
|
||||
$help_url = Cmatic_Pursuit::docs( 'how-to-get-your-mailchimp-api-key', 'new_user_help' );
|
||||
?>
|
||||
<h2>
|
||||
<a href="<?php echo esc_url( $help_url ); ?>" class="helping-field" target="_blank" rel="noopener noreferrer">
|
||||
<?php esc_html_e( 'How to get your Mailchimp API key.', 'chimpmatic-lite' ); ?>
|
||||
</a>
|
||||
</h2>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function get_author_credit(): string {
|
||||
$url = 'https://chimpmatic.com/?utm_source=client_site&utm_medium=backlink&utm_campaign=powered_by';
|
||||
|
||||
$html = '<p style="display: none !important">';
|
||||
$html .= '<a href="' . esc_url( $url ) . '" rel="noopener" target="_blank">ChimpMatic</a>';
|
||||
$html .= ' – CF7 Mailchimp Integration';
|
||||
$html .= '</p>' . "\n";
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Data container element renderer.
|
||||
*
|
||||
* @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_Data_Container {
|
||||
const ELEMENT_ID = 'cmatic_data';
|
||||
|
||||
public static function render( int $form_id, string $apivalid = '0', array $extra = array() ): void {
|
||||
$attrs_html = self::build_data_attrs( $form_id, $apivalid, $extra );
|
||||
|
||||
printf(
|
||||
'<div id="%s"%s style="display:none;"></div>',
|
||||
esc_attr( self::ELEMENT_ID ),
|
||||
$attrs_html
|
||||
);
|
||||
}
|
||||
|
||||
public static function render_open( int $form_id, string $apivalid = '0', array $extra = array() ): void {
|
||||
$attrs_html = self::build_data_attrs( $form_id, $apivalid, $extra );
|
||||
|
||||
printf(
|
||||
'<div id="%s" class="cmatic-inner"%s>',
|
||||
esc_attr( self::ELEMENT_ID ),
|
||||
$attrs_html
|
||||
);
|
||||
}
|
||||
|
||||
public static function render_close(): void {
|
||||
echo '</div><!-- #cmatic_data.cmatic-inner -->';
|
||||
}
|
||||
|
||||
private static function build_data_attrs( int $form_id, string $apivalid = '0', array $extra = array() ): string {
|
||||
$data_attrs = array(
|
||||
'form-id' => $form_id,
|
||||
'api-valid' => $apivalid,
|
||||
);
|
||||
|
||||
foreach ( $extra as $key => $value ) {
|
||||
$key = str_replace( '_', '-', sanitize_key( $key ) );
|
||||
|
||||
if ( is_array( $value ) || is_object( $value ) ) {
|
||||
$data_attrs[ $key ] = wp_json_encode( $value );
|
||||
} else {
|
||||
$data_attrs[ $key ] = esc_attr( $value );
|
||||
}
|
||||
}
|
||||
|
||||
$attrs_html = '';
|
||||
foreach ( $data_attrs as $key => $value ) {
|
||||
$attrs_html .= sprintf( ' data-%s="%s"', esc_attr( $key ), esc_attr( $value ) );
|
||||
}
|
||||
|
||||
return $attrs_html;
|
||||
}
|
||||
|
||||
public static function get_element_id(): string {
|
||||
return self::ELEMENT_ID;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* Field mapping UI components.
|
||||
*
|
||||
* @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_Field_Mapper_UI {
|
||||
public static function render( int $api_valid, ?array $list_data, array $cf7_mch, array $form_tags, int $form_id ): void {
|
||||
$disclosure_class = ( 1 === $api_valid ) ? 'chmp-active' : 'chmp-inactive';
|
||||
|
||||
$total_merge = isset( $cf7_mch['total_merge_fields'] ) ? (int) $cf7_mch['total_merge_fields'] : 0;
|
||||
$show_notice = $total_merge > CMATIC_LITE_FIELDS;
|
||||
$notice_class = $show_notice ? 'cmatic-visible' : 'cmatic-hidden';
|
||||
$audience_name = self::resolve_audience_name( $cf7_mch );
|
||||
$docs_url = Cmatic_Pursuit::url( 'https://chimpmatic.com/mailchimp-default-audience-fields-explained', 'plugin', 'fields_notice', 'docs' );
|
||||
?>
|
||||
<div class="mce-custom-fields <?php echo esc_attr( $disclosure_class ); ?>" id="cmatic-fields">
|
||||
<?php
|
||||
self::render_merge_fields( $cf7_mch, $form_tags );
|
||||
self::render_optin_checkbox( $form_tags, $cf7_mch, $form_id );
|
||||
self::render_double_optin( $cf7_mch );
|
||||
?>
|
||||
<div class="cmatic-defaults-fields-notice <?php echo esc_attr( $notice_class ); ?>" id="cmatic-fields-notice">
|
||||
<p class="cmatic-notice">
|
||||
<?php if ( $show_notice ) : ?>
|
||||
<?php
|
||||
$notice_text = sprintf(
|
||||
/* translators: 1: audience name wrapped in <strong>, 2: total merge fields count, 3: lite fields limit */
|
||||
__( 'Your %1$s audience has %2$d merge fields. Chimpmatic Lite supports up to %3$d field mappings.', 'chimpmatic-lite' ),
|
||||
'<strong>' . esc_html( $audience_name ) . '</strong>',
|
||||
$total_merge,
|
||||
CMATIC_LITE_FIELDS
|
||||
);
|
||||
echo wp_kses( $notice_text, array( 'strong' => array() ) );
|
||||
?>
|
||||
<a href="<?php echo esc_url( $docs_url ); ?>" class="helping-field" target="_blank" rel="noopener noreferrer">
|
||||
<?php esc_html_e( 'Read More', 'chimpmatic-lite' ); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
Cmatic_Tags_Preview::render( $form_tags, $cf7_mch, $api_valid );
|
||||
}
|
||||
|
||||
private static function render_merge_fields( array $cf7_mch, array $form_tags ): void {
|
||||
$merge_fields = $cf7_mch['merge_fields'] ?? array();
|
||||
$max_fields = CMATIC_LITE_FIELDS;
|
||||
|
||||
for ( $i = 0; $i < $max_fields; $i++ ) {
|
||||
$field_index = $i + 3;
|
||||
$field_key = 'field' . $field_index;
|
||||
$merge_field = $merge_fields[ $i ] ?? null;
|
||||
|
||||
$field_config = self::build_field_config( $merge_field, $field_index );
|
||||
self::render_field_row( $field_key, $form_tags, $cf7_mch, $field_config );
|
||||
}
|
||||
}
|
||||
|
||||
private static function build_field_config( ?array $merge_field, int $field_index ): array {
|
||||
if ( null === $merge_field ) {
|
||||
return array(
|
||||
'label' => 'Field ' . $field_index,
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'description' => 'Map a form field to Mailchimp',
|
||||
'merge_tag' => '',
|
||||
);
|
||||
}
|
||||
|
||||
$tag = $merge_field['tag'] ?? '';
|
||||
$name = $merge_field['name'] ?? $tag;
|
||||
$type = $merge_field['type'] ?? 'text';
|
||||
|
||||
$config = array(
|
||||
'label' => $name . ' - *|' . $tag . '|* <span class="mce-type">' . esc_html( $type ) . '</span>',
|
||||
'type' => 'text',
|
||||
'required' => false,
|
||||
'description' => 'Map a form field to Mailchimp',
|
||||
'merge_tag' => $tag,
|
||||
);
|
||||
|
||||
if ( 'EMAIL' === $tag ) {
|
||||
$config['required'] = true;
|
||||
$config['type'] = 'email';
|
||||
$config['description'] = 'MUST be an email tag <a href="' . esc_url( Cmatic_Pursuit::docs( 'mailchimp-required-email', 'email_field' ) ) . '" class="helping-field" target="_blank" title="get help with Subscriber Email:"> Learn More</a>';
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
private static function render_field_row( string $field_key, array $form_tags, array $cf7_mch, array $config ): void {
|
||||
?>
|
||||
<div class="mcee-container">
|
||||
<label for="wpcf7-mailchimp-<?php echo esc_attr( $field_key ); ?>">
|
||||
<?php
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML is pre-escaped in build_field_config.
|
||||
echo $config['label'];
|
||||
?>
|
||||
<?php if ( $config['required'] ) : ?>
|
||||
<span class="mce-required">Required</span>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
<?php self::render_dropdown( $field_key, $form_tags, $cf7_mch, $config['type'], $config['merge_tag'] ); ?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function render_dropdown( string $field_name, array $form_tags, array $cf7_mch, string $filter = '', string $merge_tag = '' ): void {
|
||||
$field_name = sanitize_key( $field_name );
|
||||
$saved_value = isset( $cf7_mch[ $field_name ] ) ? trim( sanitize_text_field( $cf7_mch[ $field_name ] ) ) : '';
|
||||
|
||||
if ( '' === $saved_value && ! empty( $merge_tag ) ) {
|
||||
$saved_value = self::auto_match_field( $form_tags, $merge_tag, $filter );
|
||||
}
|
||||
?>
|
||||
<select class="chm-select" id="wpcf7-mailchimp-<?php echo esc_attr( $field_name ); ?>" name="wpcf7-mailchimp[<?php echo esc_attr( $field_name ); ?>]">
|
||||
<?php if ( 'email' !== $filter ) : ?>
|
||||
<option value="" <?php selected( $saved_value, '' ); ?>>
|
||||
<?php esc_html_e( 'Choose..', 'chimpmatic-lite' ); ?>
|
||||
</option>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach ( $form_tags as $tag ) : ?>
|
||||
<?php
|
||||
if ( 'opt-in' === $tag['name'] ) {
|
||||
continue;
|
||||
}
|
||||
if ( 'email' === $filter ) {
|
||||
$is_email = ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) );
|
||||
if ( ! $is_email ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$tag_value = '[' . $tag['name'] . ']';
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $tag_value ); ?>" <?php selected( $saved_value, $tag_value ); ?>>
|
||||
<?php echo esc_html( $tag_value ); ?> - type: <?php echo esc_html( $tag['basetype'] ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<?php
|
||||
}
|
||||
|
||||
private static function auto_match_field( array $form_tags, string $merge_tag, string $filter ): string {
|
||||
$merge_tag_lower = strtolower( $merge_tag );
|
||||
|
||||
foreach ( $form_tags as $tag ) {
|
||||
if ( 'email' === $filter ) {
|
||||
if ( 'email' === $tag['basetype'] || false !== strpos( strtolower( $tag['name'] ), 'email' ) ) {
|
||||
return '[' . $tag['name'] . ']';
|
||||
}
|
||||
} elseif ( false !== strpos( strtolower( $tag['name'] ), $merge_tag_lower ) ) {
|
||||
return '[' . $tag['name'] . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function render_optin_checkbox( array $form_tags, array $cf7_mch, int $form_id ): void {
|
||||
$checkbox_types = array( 'checkbox', 'acceptance' );
|
||||
$checkbox_fields = array_filter(
|
||||
$form_tags,
|
||||
function ( $field ) use ( $checkbox_types ) {
|
||||
return in_array( $field['basetype'], $checkbox_types, true );
|
||||
}
|
||||
);
|
||||
?>
|
||||
<div class="mcee-container">
|
||||
<label for="wpcf7-mailchimp-accept">
|
||||
<?php esc_html_e( 'Opt-in Checkbox', 'chimpmatic-lite' ); ?>
|
||||
<span class="mce-type"><?php esc_html_e( 'Optional', 'chimpmatic-lite' ); ?></span>
|
||||
</label>
|
||||
<select class="chm-select" id="wpcf7-mailchimp-accept" name="wpcf7-mailchimp[accept]">
|
||||
<option value=" " <?php selected( $cf7_mch['accept'] ?? ' ', ' ' ); ?>>
|
||||
<?php esc_html_e( 'None - Always subscribe', 'chimpmatic-lite' ); ?>
|
||||
</option>
|
||||
<?php if ( empty( $checkbox_fields ) ) : ?>
|
||||
<option value="" disabled>
|
||||
<?php
|
||||
$form_title = '';
|
||||
if ( function_exists( 'wpcf7_contact_form' ) ) {
|
||||
$form_obj = wpcf7_contact_form( $form_id );
|
||||
$form_title = $form_obj ? $form_obj->title() : '';
|
||||
}
|
||||
printf(
|
||||
/* translators: %s: Form title */
|
||||
esc_html__( '"%s" has no [checkbox] or [acceptance] fields', 'chimpmatic-lite' ),
|
||||
esc_html( $form_title )
|
||||
);
|
||||
?>
|
||||
</option>
|
||||
<?php else : ?>
|
||||
<?php foreach ( $checkbox_fields as $field ) : ?>
|
||||
<?php
|
||||
$field_value = '[' . $field['name'] . ']';
|
||||
$saved_value = $cf7_mch['accept'] ?? ' ';
|
||||
?>
|
||||
<option value="<?php echo esc_attr( $field_value ); ?>" <?php selected( $saved_value, $field_value ); ?>>
|
||||
<?php echo esc_html( '[' . $field['name'] . ']' ); ?> - type: <?php echo esc_html( $field['basetype'] ); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
<small class="description">
|
||||
<?php esc_html_e( 'Only subscribe if this checkbox is checked', 'chimpmatic-lite' ); ?>
|
||||
<a href="<?php echo esc_url( Cmatic_Pursuit::docs( 'mailchimp-opt-in-checkbox', 'optin_field' ) ); ?>" class="helping-field" target="_blank"><?php esc_html_e( 'Learn More', 'chimpmatic-lite' ); ?></a>
|
||||
</small>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private static function render_double_optin( array $cf7_mch ): void {
|
||||
$value = ! empty( $cf7_mch['double_optin'] ) || ! empty( $cf7_mch['confsubs'] ) ? '1' : '0';
|
||||
?>
|
||||
<div class="mcee-container">
|
||||
<label for="wpcf7-mailchimp-double-optin">
|
||||
<?php esc_html_e( 'Double Opt-in', 'chimpmatic-lite' ); ?>
|
||||
<span class="mce-type"><?php esc_html_e( 'Optional', 'chimpmatic-lite' ); ?></span>
|
||||
</label>
|
||||
<select class="chm-select" id="wpcf7-mailchimp-double-optin" name="wpcf7-mailchimp[confsubs]" data-field="double_optin">
|
||||
<option value="0" <?php selected( $value, '0' ); ?>>
|
||||
<?php esc_html_e( 'Subscribers are added immediately', 'chimpmatic-lite' ); ?>
|
||||
</option>
|
||||
<option value="1" <?php selected( $value, '1' ); ?>>
|
||||
<?php esc_html_e( 'Subscribers must confirm via email', 'chimpmatic-lite' ); ?>
|
||||
</option>
|
||||
</select>
|
||||
<small class="description">
|
||||
<?php esc_html_e( 'Choose how subscribers are added to your Mailchimp list', 'chimpmatic-lite' ); ?>
|
||||
<a href="<?php echo esc_url( Cmatic_Pursuit::docs( 'mailchimp-double-opt-in', 'double_optin' ) ); ?>" class="helping-field" target="_blank"><?php esc_html_e( 'Learn More', 'chimpmatic-lite' ); ?></a>
|
||||
</small>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private static function resolve_audience_name( array $cf7_mch ): string {
|
||||
$list_id = $cf7_mch['list'] ?? '';
|
||||
$lists = $cf7_mch['lisdata']['lists'] ?? array();
|
||||
|
||||
foreach ( $lists as $list ) {
|
||||
if ( isset( $list['id'], $list['name'] ) && $list['id'] === $list_id ) {
|
||||
return $list['name'];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* CF7 form CSS class injector.
|
||||
*
|
||||
* @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_Form_Classes {
|
||||
public static function init(): void {
|
||||
add_filter( 'wpcf7_form_class_attr', array( __CLASS__, 'add_classes' ) );
|
||||
}
|
||||
|
||||
public static function add_classes( string $class_attr ): string {
|
||||
$classes = array();
|
||||
|
||||
// 1. Install ID (pure, no prefix) - stored at install.id.
|
||||
$install_id = Cmatic_Options_Repository::get_option( 'install.id', '' );
|
||||
if ( ! empty( $install_id ) ) {
|
||||
$classes[] = sanitize_html_class( $install_id );
|
||||
}
|
||||
|
||||
// 2. API connection status - check if first_connected timestamp exists.
|
||||
$first_connected = Cmatic_Options_Repository::get_option( 'api.first_connected', 0 );
|
||||
if ( ! empty( $first_connected ) ) {
|
||||
$classes[] = 'cmatic-conn';
|
||||
} else {
|
||||
$classes[] = 'cmatic-disconn';
|
||||
}
|
||||
|
||||
// 3. Audience count - stored in lisdata.lists array.
|
||||
$lisdata = Cmatic_Options_Repository::get_option( 'lisdata', array() );
|
||||
$lists = isset( $lisdata['lists'] ) && is_array( $lisdata['lists'] ) ? $lisdata['lists'] : array();
|
||||
$aud_count = count( $lists );
|
||||
$classes[] = 'cmatic-aud-' . $aud_count;
|
||||
|
||||
// 4. Mapped fields (form-specific).
|
||||
$contact_form = wpcf7_get_current_contact_form();
|
||||
if ( $contact_form ) {
|
||||
$form_id = $contact_form->id();
|
||||
$cf7_mch = get_option( 'cf7_mch_' . $form_id, array() );
|
||||
$mapped = self::count_mapped_fields( $cf7_mch );
|
||||
$total = self::count_total_merge_fields( $cf7_mch );
|
||||
$classes[] = 'cmatic-mapd' . $mapped . '-' . $total;
|
||||
}
|
||||
|
||||
// 5. Lite version (SPARTAN_MCE_VERSION).
|
||||
if ( defined( 'SPARTAN_MCE_VERSION' ) ) {
|
||||
$version = str_replace( '.', '', SPARTAN_MCE_VERSION );
|
||||
$classes[] = 'cmatic-v' . $version;
|
||||
}
|
||||
|
||||
// 6. Pro version (CMATIC_VERSION) if active.
|
||||
if ( defined( 'CMATIC_VERSION' ) ) {
|
||||
$pro_version = str_replace( '.', '', CMATIC_VERSION );
|
||||
$classes[] = 'cmatic-pro-v' . $pro_version;
|
||||
}
|
||||
|
||||
// 7. Per-form sent count.
|
||||
if ( $contact_form ) {
|
||||
$form_sent = (int) ( $cf7_mch['stats_sent'] ?? 0 );
|
||||
$classes[] = 'cmatic-sent-' . $form_sent;
|
||||
}
|
||||
|
||||
// 8. Global total sent.
|
||||
$total_sent = (int) Cmatic_Options_Repository::get_option( 'stats.sent', 0 );
|
||||
$classes[] = 'cmatic-total-' . $total_sent;
|
||||
|
||||
// Append to existing classes.
|
||||
if ( ! empty( $classes ) ) {
|
||||
$class_attr .= ' ' . implode( ' ', $classes );
|
||||
}
|
||||
|
||||
return $class_attr;
|
||||
}
|
||||
|
||||
private static function count_mapped_fields( array $cf7_mch ): int {
|
||||
$merge_fields = isset( $cf7_mch['merge_fields'] ) && is_array( $cf7_mch['merge_fields'] )
|
||||
? $cf7_mch['merge_fields']
|
||||
: array();
|
||||
|
||||
if ( empty( $merge_fields ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$mapped = 0;
|
||||
foreach ( $merge_fields as $index => $field ) {
|
||||
$field_key = 'field' . ( $index + 3 );
|
||||
if ( ! empty( $cf7_mch[ $field_key ] ) && '--' !== $cf7_mch[ $field_key ] ) {
|
||||
++$mapped;
|
||||
}
|
||||
}
|
||||
|
||||
return $mapped;
|
||||
}
|
||||
|
||||
private static function count_total_merge_fields( array $cf7_mch ): int {
|
||||
$merge_fields = isset( $cf7_mch['merge_fields'] ) && is_array( $cf7_mch['merge_fields'] )
|
||||
? $cf7_mch['merge_fields']
|
||||
: array();
|
||||
|
||||
return count( $merge_fields );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
/**
|
||||
* Settings page header 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_Header {
|
||||
const CMATIC_FB_B = '@gmail';
|
||||
|
||||
private $version;
|
||||
private $is_pro;
|
||||
private $api_status;
|
||||
private $review_url;
|
||||
private $review_phrases;
|
||||
|
||||
|
||||
public function __construct( array $args = array() ) {
|
||||
$this->version = $this->resolve_version( $args );
|
||||
$this->is_pro = $this->resolve_pro_status( $args );
|
||||
$this->api_status = isset( $args['api_status'] ) && is_string( $args['api_status'] ) ? $args['api_status'] : null;
|
||||
$this->review_url = isset( $args['review_url'] ) && is_string( $args['review_url'] ) ? $args['review_url'] : $this->get_default_review_url();
|
||||
|
||||
$this->review_phrases = array(
|
||||
__( 'Loving Chimpmatic? Leave a review', 'chimpmatic-lite' ),
|
||||
__( 'We run on coffee & 5-star reviews', 'chimpmatic-lite' ),
|
||||
__( 'Make a developer smile today', 'chimpmatic-lite' ),
|
||||
__( 'Got 10 seconds? Rate us!', 'chimpmatic-lite' ),
|
||||
__( 'Fuel our plugin addiction', 'chimpmatic-lite' ),
|
||||
__( 'Stars make us code faster', 'chimpmatic-lite' ),
|
||||
__( 'Help us stay free & caffeinated', 'chimpmatic-lite' ),
|
||||
__( "Love us? Don't keep it a secret", 'chimpmatic-lite' ),
|
||||
__( 'Your review = our dopamine', 'chimpmatic-lite' ),
|
||||
__( 'Be our hero on WordPress.org', 'chimpmatic-lite' ),
|
||||
__( 'Psst... we love 5 stars', 'chimpmatic-lite' ),
|
||||
__( 'Worth 5 stars? Let us know', 'chimpmatic-lite' ),
|
||||
__( 'Support indie plugins', 'chimpmatic-lite' ),
|
||||
__( 'Reviews keep the lights on', 'chimpmatic-lite' ),
|
||||
__( 'Spread the Chimpmatic love', 'chimpmatic-lite' ),
|
||||
__( 'Got love? Leave stars', 'chimpmatic-lite' ),
|
||||
__( 'One click, endless gratitude', 'chimpmatic-lite' ),
|
||||
__( 'Help other WP folks find us', 'chimpmatic-lite' ),
|
||||
__( 'Like us? Rate us!', 'chimpmatic-lite' ),
|
||||
__( 'Your stars = our motivation', 'chimpmatic-lite' ),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private function resolve_version( array $args ): string {
|
||||
if ( isset( $args['version'] ) && is_string( $args['version'] ) ) {
|
||||
return $args['version'];
|
||||
}
|
||||
|
||||
if ( defined( 'CMATIC_VERSION' ) ) {
|
||||
return (string) CMATIC_VERSION;
|
||||
}
|
||||
|
||||
if ( defined( 'SPARTAN_MCE_VERSION' ) ) {
|
||||
return (string) SPARTAN_MCE_VERSION;
|
||||
}
|
||||
|
||||
return '0.0.0';
|
||||
}
|
||||
|
||||
|
||||
private function resolve_pro_status( array $args ): bool {
|
||||
if ( isset( $args['is_pro'] ) ) {
|
||||
return (bool) $args['is_pro'];
|
||||
}
|
||||
|
||||
if ( function_exists( 'cmatic_is_blessed' ) ) {
|
||||
return (bool) cmatic_is_blessed();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private function get_default_review_url(): string {
|
||||
return 'https://wordpress.org/support/plugin/contact-form-7-mailchimp-extension/reviews/';
|
||||
}
|
||||
|
||||
|
||||
private function get_review_phrase(): string {
|
||||
$index = wp_rand( 0, count( $this->review_phrases ) - 1 );
|
||||
return $this->review_phrases[ $index ];
|
||||
}
|
||||
|
||||
|
||||
public function render(): void {
|
||||
$badge_class = $this->is_pro ? 'cmatic-header__badge--pro' : 'cmatic-header__badge--lite';
|
||||
$badge_text = $this->is_pro ? __( 'PRO', 'chimpmatic-lite' ) : __( 'Lite', 'chimpmatic-lite' );
|
||||
?>
|
||||
<header class="cmatic-header">
|
||||
<div class="cmatic-header__inner">
|
||||
<div class="cmatic-header__brand">
|
||||
<span class="cmatic-header__title"><?php esc_html_e( 'Chimpmatic', 'chimpmatic-lite' ); ?></span>
|
||||
<span class="cmatic-header__badge <?php echo esc_attr( $badge_class ); ?>"><?php echo esc_html( $badge_text ); ?></span>
|
||||
<span class="cmatic-header__version">v<?php echo esc_html( $this->version ); ?></span>
|
||||
<?php $this->render_api_status(); ?>
|
||||
</div>
|
||||
<div class="cmatic-header__actions">
|
||||
<a href="<?php echo esc_url( $this->review_url ); ?>" target="_blank" rel="noopener noreferrer" class="cmatic-header__review">
|
||||
<?php echo esc_html( $this->get_review_phrase() ); ?>
|
||||
<span class="cmatic-sparkles" aria-label="5 sparkles"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
private function render_api_status(): void {
|
||||
if ( null === $this->api_status ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$is_connected = ( 'connected' === $this->api_status );
|
||||
$dot_class = $is_connected ? 'cmatic-header__status-dot--connected' : 'cmatic-header__status-dot--disconnected';
|
||||
$status_text = $is_connected
|
||||
? __( 'API Connected', 'chimpmatic-lite' )
|
||||
: __( 'API Inactive', 'chimpmatic-lite' );
|
||||
?>
|
||||
<div class="cmatic-header__status">
|
||||
<span class="cmatic-header__status-dot <?php echo esc_attr( $dot_class ); ?>"></span>
|
||||
<span class="cmatic-header__status-text"><?php echo esc_html( $status_text ); ?></span>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
||||
public function set_api_status( ?string $status ): self {
|
||||
$this->api_status = $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public static function output( array $args = array() ): void {
|
||||
$header = new self( $args );
|
||||
$header->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* Modal base class.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
if ( ! class_exists( 'Cmatic_Modal' ) ) {
|
||||
abstract class Cmatic_Modal {
|
||||
protected $modal_id;
|
||||
protected $admin_hooks = array();
|
||||
protected $initialized = false;
|
||||
|
||||
public function __construct( $modal_id, $admin_hooks = array() ) {
|
||||
$this->modal_id = sanitize_key( $modal_id );
|
||||
$this->admin_hooks = is_array( $admin_hooks ) ? $admin_hooks : array( $admin_hooks );
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if ( $this->initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_assets' ) );
|
||||
add_action( $this->get_render_hook(), array( $this, 'maybe_render_modal' ), $this->get_render_priority(), $this->get_render_args() );
|
||||
|
||||
$this->initialized = true;
|
||||
}
|
||||
|
||||
protected function get_render_hook() {
|
||||
return 'admin_footer';
|
||||
}
|
||||
|
||||
protected function get_render_priority() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
protected function get_render_args() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function is_valid_admin_page( $hook ) {
|
||||
if ( empty( $this->admin_hooks ) ) {
|
||||
return true;
|
||||
}
|
||||
return in_array( $hook, $this->admin_hooks, true );
|
||||
}
|
||||
|
||||
public function maybe_enqueue_assets( $hook ) {
|
||||
if ( ! $this->is_valid_admin_page( $hook ) ) {
|
||||
return;
|
||||
}
|
||||
$this->enqueue_assets( $hook );
|
||||
}
|
||||
|
||||
public function maybe_render_modal() {
|
||||
$screen = get_current_screen();
|
||||
if ( ! $screen ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_hook = $screen->id;
|
||||
if ( ! empty( $this->admin_hooks ) && ! in_array( $current_hook, $this->admin_hooks, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->render_modal();
|
||||
}
|
||||
|
||||
protected function enqueue_assets( $hook ) {
|
||||
}
|
||||
|
||||
protected function render_modal() {
|
||||
$title = $this->get_title();
|
||||
$body = $this->get_body();
|
||||
$footer = $this->get_footer();
|
||||
$extra_class = $this->get_extra_class();
|
||||
$description = $this->get_description();
|
||||
|
||||
?>
|
||||
<div id="<?php echo esc_attr( $this->modal_id ); ?>"
|
||||
class="cmatic-modal <?php echo esc_attr( $extra_class ); ?>"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="<?php echo esc_attr( $this->modal_id ); ?>-title"
|
||||
<?php if ( $description ) : ?>
|
||||
aria-describedby="<?php echo esc_attr( $this->modal_id ); ?>-description"
|
||||
<?php endif; ?>
|
||||
>
|
||||
<div class="cmatic-modal__overlay"></div>
|
||||
<div class="cmatic-modal__dialog">
|
||||
<div class="cmatic-modal__header">
|
||||
<h2 id="<?php echo esc_attr( $this->modal_id ); ?>-title"><?php echo esc_html( $title ); ?></h2>
|
||||
<?php $this->render_header_actions(); ?>
|
||||
<button type="button" class="cmatic-modal__close" aria-label="<?php esc_attr_e( 'Close dialog', 'chimpmatic-lite' ); ?>">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="cmatic-modal__body">
|
||||
<?php if ( $description ) : ?>
|
||||
<p id="<?php echo esc_attr( $this->modal_id ); ?>-description" class="cmatic-modal__description">
|
||||
<?php echo esc_html( $description ); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $body;
|
||||
?>
|
||||
</div>
|
||||
<?php if ( $footer ) : ?>
|
||||
<div class="cmatic-modal__footer">
|
||||
<?php
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $footer;
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
protected function render_header_actions() {
|
||||
}
|
||||
|
||||
abstract protected function get_title();
|
||||
|
||||
abstract protected function get_body();
|
||||
|
||||
protected function get_footer() {
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function get_description() {
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function get_extra_class() {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function get_modal_id() {
|
||||
return $this->modal_id;
|
||||
}
|
||||
|
||||
protected function get_strings() {
|
||||
return array(
|
||||
'closeLabel' => __( 'Close dialog', 'chimpmatic-lite' ),
|
||||
);
|
||||
}
|
||||
|
||||
protected function get_js_data() {
|
||||
return array(
|
||||
'modalId' => $this->modal_id,
|
||||
'strings' => $this->get_strings(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
/**
|
||||
* Notification center manager.
|
||||
*
|
||||
* @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_Notification_Center {
|
||||
const STORAGE_KEY = 'cmatic_notifications';
|
||||
|
||||
private static $instance = null;
|
||||
private $notifications = array();
|
||||
private $notifications_retrieved = false;
|
||||
private $notifications_dirty = false;
|
||||
|
||||
public static function get() {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
add_action( 'init', array( $this, 'setup_notifications' ), 1 );
|
||||
add_action( 'shutdown', array( $this, 'save_notifications' ) );
|
||||
}
|
||||
|
||||
public function setup_notifications() {
|
||||
$this->retrieve_notifications();
|
||||
$this->add_dynamic_notifications();
|
||||
}
|
||||
|
||||
private function retrieve_notifications() {
|
||||
if ( $this->notifications_retrieved ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->notifications_retrieved = true;
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( ! $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stored = get_user_option( self::STORAGE_KEY, $user_id );
|
||||
|
||||
if ( ! is_array( $stored ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $stored as $data ) {
|
||||
$notification = Cmatic_Notification::from_array( $data );
|
||||
if ( $notification->display_for_current_user() ) {
|
||||
$this->notifications[] = $notification;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function add_dynamic_notifications() {
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$api_key = $this->get_api_key_status();
|
||||
|
||||
if ( ! $api_key ) {
|
||||
$this->add_notification(
|
||||
new Cmatic_Notification(
|
||||
__( 'Connect your Mailchimp API to enable email subscriptions.', 'chimpmatic-lite' ),
|
||||
array(
|
||||
'id' => 'cmatic-api-not-connected',
|
||||
'type' => Cmatic_Notification::WARNING,
|
||||
'priority' => 1.0,
|
||||
'link' => $this->get_settings_url(),
|
||||
'link_text' => __( 'Connect Now', 'chimpmatic-lite' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( class_exists( 'Cmatic_Options_Repository' ) && Cmatic_Options_Repository::get_option( 'debug', false ) ) {
|
||||
$this->add_notification(
|
||||
new Cmatic_Notification(
|
||||
__( 'Debug logging is currently enabled.', 'chimpmatic-lite' ),
|
||||
array(
|
||||
'id' => 'cmatic-debug-enabled',
|
||||
'type' => Cmatic_Notification::INFO,
|
||||
'priority' => 0.3,
|
||||
'link' => $this->get_settings_url(),
|
||||
'link_text' => __( 'View Settings', 'chimpmatic-lite' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function get_api_key_status() {
|
||||
$cache_key = 'cmatic_api_connected';
|
||||
$cached = get_transient( $cache_key );
|
||||
|
||||
if ( false !== $cached ) {
|
||||
return (bool) $cached;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached via transient.
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT option_value FROM {$wpdb->options} WHERE option_name LIKE %s LIMIT 10",
|
||||
'cf7_mch_%'
|
||||
)
|
||||
);
|
||||
|
||||
$is_connected = false;
|
||||
|
||||
if ( ! empty( $results ) ) {
|
||||
foreach ( $results as $row ) {
|
||||
$data = maybe_unserialize( $row->option_value );
|
||||
if ( is_array( $data ) && ! empty( $data['api_key'] ) ) {
|
||||
$is_connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_transient( $cache_key, $is_connected ? '1' : '0', HOUR_IN_SECONDS );
|
||||
|
||||
return $is_connected;
|
||||
}
|
||||
|
||||
private function get_settings_url() {
|
||||
if ( class_exists( 'Cmatic_Plugin_Links' ) ) {
|
||||
$url = Cmatic_Plugin_Links::get_settings_url();
|
||||
if ( ! empty( $url ) ) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
return admin_url( 'admin.php?page=wpcf7' );
|
||||
}
|
||||
|
||||
public function add_notification( Cmatic_Notification $notification ) {
|
||||
if ( ! $notification->display_for_current_user() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_notification_dismissed( $notification ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = $notification->get_id();
|
||||
if ( $id ) {
|
||||
foreach ( $this->notifications as $existing ) {
|
||||
if ( $existing->get_id() === $id ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->notifications[] = $notification;
|
||||
$this->notifications_dirty = true;
|
||||
}
|
||||
|
||||
public function remove_notification( $notification_id ) {
|
||||
foreach ( $this->notifications as $index => $notification ) {
|
||||
if ( $notification->get_id() === $notification_id ) {
|
||||
unset( $this->notifications[ $index ] );
|
||||
$this->notifications = array_values( $this->notifications );
|
||||
$this->notifications_dirty = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get_notification_by_id( $notification_id ) {
|
||||
foreach ( $this->notifications as $notification ) {
|
||||
if ( $notification->get_id() === $notification_id ) {
|
||||
return $notification;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_notifications() {
|
||||
return $this->notifications;
|
||||
}
|
||||
|
||||
public function get_notification_count() {
|
||||
return count( $this->notifications );
|
||||
}
|
||||
|
||||
public function get_sorted_notifications() {
|
||||
$notifications = $this->notifications;
|
||||
|
||||
usort(
|
||||
$notifications,
|
||||
function ( $a, $b ) {
|
||||
$type_priority = array(
|
||||
Cmatic_Notification::ERROR => 4,
|
||||
Cmatic_Notification::WARNING => 3,
|
||||
Cmatic_Notification::INFO => 2,
|
||||
Cmatic_Notification::SUCCESS => 1,
|
||||
);
|
||||
|
||||
$a_type = isset( $type_priority[ $a->get_type() ] ) ? $type_priority[ $a->get_type() ] : 0;
|
||||
$b_type = isset( $type_priority[ $b->get_type() ] ) ? $type_priority[ $b->get_type() ] : 0;
|
||||
|
||||
if ( $a_type !== $b_type ) {
|
||||
return $b_type - $a_type;
|
||||
}
|
||||
|
||||
if ( $b->get_priority() > $a->get_priority() ) {
|
||||
return 1;
|
||||
} elseif ( $b->get_priority() < $a->get_priority() ) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
|
||||
return $notifications;
|
||||
}
|
||||
|
||||
public function is_notification_dismissed( Cmatic_Notification $notification ) {
|
||||
$dismissal_key = $notification->get_dismissal_key();
|
||||
|
||||
if ( empty( $dismissal_key ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$value = get_user_option( 'cmatic_dismissed_' . $dismissal_key, $user_id );
|
||||
|
||||
return ! empty( $value );
|
||||
}
|
||||
|
||||
public function dismiss_notification( Cmatic_Notification $notification ) {
|
||||
$dismissal_key = $notification->get_dismissal_key();
|
||||
|
||||
if ( empty( $dismissal_key ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$result = update_user_option( $user_id, 'cmatic_dismissed_' . $dismissal_key, time() );
|
||||
|
||||
if ( $result ) {
|
||||
$this->remove_notification( $notification->get_id() );
|
||||
}
|
||||
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
public function save_notifications() {
|
||||
if ( ! $this->notifications_dirty ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( ! $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$to_store = array();
|
||||
|
||||
foreach ( $this->notifications as $notification ) {
|
||||
if ( $notification->is_persistent() ) {
|
||||
$to_store[] = $notification->to_array();
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $to_store ) ) {
|
||||
delete_user_option( $user_id, self::STORAGE_KEY );
|
||||
} else {
|
||||
update_user_option( $user_id, self::STORAGE_KEY, $to_store );
|
||||
}
|
||||
}
|
||||
|
||||
public function clear_notifications() {
|
||||
$this->notifications = array();
|
||||
$this->notifications_dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/**
|
||||
* Notification message 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_Notification {
|
||||
const ERROR = 'error';
|
||||
const WARNING = 'warning';
|
||||
const INFO = 'info';
|
||||
const SUCCESS = 'success';
|
||||
|
||||
private $message;
|
||||
private $options = array();
|
||||
private $defaults = array(
|
||||
'type' => self::INFO,
|
||||
'id' => '',
|
||||
'user_id' => null,
|
||||
'priority' => 0.5,
|
||||
'dismissal_key' => null,
|
||||
'capabilities' => array( 'manage_options' ),
|
||||
'link' => '',
|
||||
'link_text' => '',
|
||||
);
|
||||
|
||||
public function __construct( $message, $options = array() ) {
|
||||
$this->message = $message;
|
||||
$this->options = wp_parse_args( $options, $this->defaults );
|
||||
|
||||
if ( null === $this->options['user_id'] ) {
|
||||
$this->options['user_id'] = get_current_user_id();
|
||||
}
|
||||
|
||||
$this->options['priority'] = min( 1, max( 0, $this->options['priority'] ) );
|
||||
}
|
||||
|
||||
public function get_id() {
|
||||
return $this->options['id'];
|
||||
}
|
||||
|
||||
public function get_message() {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function get_type() {
|
||||
return $this->options['type'];
|
||||
}
|
||||
|
||||
public function get_priority() {
|
||||
return $this->options['priority'];
|
||||
}
|
||||
|
||||
public function get_user_id() {
|
||||
return (int) $this->options['user_id'];
|
||||
}
|
||||
|
||||
public function get_dismissal_key() {
|
||||
if ( empty( $this->options['dismissal_key'] ) ) {
|
||||
return $this->options['id'];
|
||||
}
|
||||
return $this->options['dismissal_key'];
|
||||
}
|
||||
|
||||
public function get_link() {
|
||||
return $this->options['link'];
|
||||
}
|
||||
|
||||
public function get_link_text() {
|
||||
return $this->options['link_text'];
|
||||
}
|
||||
|
||||
public function is_persistent() {
|
||||
return ! empty( $this->options['id'] );
|
||||
}
|
||||
|
||||
public function display_for_current_user() {
|
||||
if ( ! $this->is_persistent() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->user_has_capabilities();
|
||||
}
|
||||
|
||||
private function user_has_capabilities() {
|
||||
$capabilities = $this->options['capabilities'];
|
||||
|
||||
if ( empty( $capabilities ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( $capabilities as $capability ) {
|
||||
if ( ! current_user_can( $capability ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function to_array() {
|
||||
return array(
|
||||
'message' => $this->message,
|
||||
'options' => $this->options,
|
||||
);
|
||||
}
|
||||
|
||||
public static function from_array( $data ) {
|
||||
$message = isset( $data['message'] ) ? $data['message'] : '';
|
||||
$options = isset( $data['options'] ) ? $data['options'] : array();
|
||||
return new self( $message, $options );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
/**
|
||||
* Accordion panel toggle buttons.
|
||||
*
|
||||
* @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_Panel_Toggles {
|
||||
public static function cmatic_get_default_buttons() {
|
||||
return array(
|
||||
'advanced_settings' => array(
|
||||
'label' => __( 'Advanced Settings', 'flavor' ),
|
||||
'aria_controls' => 'cme-container',
|
||||
'extra_class' => '',
|
||||
'priority' => 10,
|
||||
),
|
||||
'submission_logs' => array(
|
||||
'label' => __( 'Submission Logs', 'flavor' ),
|
||||
'aria_controls' => 'eventlog-sys',
|
||||
'extra_class' => '',
|
||||
'priority' => 40,
|
||||
),
|
||||
'form_preview' => array(
|
||||
'label' => __( 'Form Preview and Test', 'flavor' ),
|
||||
'aria_controls' => 'cmatic-test-container',
|
||||
'extra_class' => 'vc-test-submission',
|
||||
'priority' => 50,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public static function cmatic_get_buttons() {
|
||||
$buttons = self::cmatic_get_default_buttons();
|
||||
$buttons = apply_filters( 'cmatic_panel_toggle_buttons', $buttons );
|
||||
|
||||
// Sort by priority.
|
||||
uasort(
|
||||
$buttons,
|
||||
function ( $a, $b ) {
|
||||
$priority_a = isset( $a['priority'] ) ? $a['priority'] : 50;
|
||||
$priority_b = isset( $b['priority'] ) ? $b['priority'] : 50;
|
||||
return $priority_a - $priority_b;
|
||||
}
|
||||
);
|
||||
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
public static function cmatic_render_button( $key, $config ) {
|
||||
$classes = 'button site-health-view-passed cmatic-accordion-btn';
|
||||
if ( ! empty( $config['extra_class'] ) ) {
|
||||
$classes .= ' ' . esc_attr( $config['extra_class'] );
|
||||
}
|
||||
|
||||
printf(
|
||||
'<button type="button" class="%s" aria-expanded="false" aria-controls="%s">%s<span class="icon"></span></button>',
|
||||
esc_attr( $classes ),
|
||||
esc_attr( $config['aria_controls'] ),
|
||||
esc_html( $config['label'] )
|
||||
);
|
||||
}
|
||||
|
||||
public static function cmatic_render() {
|
||||
$buttons = self::cmatic_get_buttons();
|
||||
|
||||
if ( empty( $buttons ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="cmatic-section cmatic-panel-toggles">';
|
||||
|
||||
foreach ( $buttons as $key => $config ) {
|
||||
self::cmatic_render_button( $key, $config );
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
/**
|
||||
* Sidebar panel components.
|
||||
*
|
||||
* @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_Sidebar_Panel {
|
||||
public static function render_submit_info( int $post_id ): void {
|
||||
$cf7_mch = get_option( 'cf7_mch_' . $post_id, array() );
|
||||
$api_valid = (int) ( $cf7_mch['api-validation'] ?? 0 );
|
||||
$sent = Cmatic_Options_Repository::get_option( 'stats.sent', 0 );
|
||||
|
||||
$status_text = ( 1 === $api_valid )
|
||||
? '<span class="chmm valid">API Connected</span>'
|
||||
: '<span class="chmm invalid">API Inactive</span>';
|
||||
?>
|
||||
<div class="misc-pub-section chimpmatic-info" id="chimpmatic-version-info">
|
||||
<div style="margin-bottom: 3px;">
|
||||
<strong><?php echo esc_html__( 'ChimpMatic Lite', 'chimpmatic-lite' ) . ' ' . esc_html( SPARTAN_MCE_VERSION ); ?></strong>
|
||||
</div>
|
||||
<div style="margin-top: 5px;">
|
||||
<div class="mc-stats" style="color: #646970; font-size: 12px; margin-bottom: 3px;">
|
||||
<?php
|
||||
echo esc_html( $sent ) . ' synced contacts in ' .
|
||||
esc_html( Cmatic_Utils::get_days_since( (int) Cmatic_Options_Repository::get_option( 'install.quest', time() ) ) ) . ' days';
|
||||
?>
|
||||
</div>
|
||||
<div style="margin-bottom: 3px;">
|
||||
<?php echo wp_kses_post( $status_text ); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
public static function render_footer_promo(): void {
|
||||
if ( function_exists( 'cmatic_is_blessed' ) && cmatic_is_blessed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pricing = self::get_pricing_data();
|
||||
$text = $pricing['formatted'] ?? '$39 → $29.25 • Save 40%';
|
||||
$discount = (int) ( $pricing['discount_percent'] ?? 40 );
|
||||
|
||||
$install_id = Cmatic_Options_Repository::get_option( 'install.id', '' );
|
||||
|
||||
$promo_url = add_query_arg(
|
||||
array(
|
||||
'source' => $install_id,
|
||||
'promo' => 'pro' . $discount,
|
||||
),
|
||||
Cmatic_Pursuit::promo( 'footer_banner', $discount )
|
||||
);
|
||||
?>
|
||||
<div id="informationdiv_aux" class="postbox mce-move mc-lateral">
|
||||
<div class="inside bg-f2">
|
||||
<h3>Upgrade to PRO</h3>
|
||||
<p>Get the best Contact Form 7 and Mailchimp integration tool available. Now with these new features:</p>
|
||||
<ul>
|
||||
<li>Tag Existing Subscribers</li>
|
||||
<li>Group Existing Subscribers</li>
|
||||
<li>Email Verification</li>
|
||||
<li>AWESOME Support And more!</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="promo-2022">
|
||||
<h1><?php echo (int) $discount; ?><span>%</span> Off!</h1>
|
||||
<p class="interesting">Unlock advanced tagging, subscriber groups, email verification, and priority support for your Mailchimp campaigns.</p>
|
||||
<div class="cm-form">
|
||||
<a href="<?php echo esc_url( $promo_url ); ?>" target="_blank" class="button cm-submit">Get PRO Now</a>
|
||||
<span class="cm-pricing"><?php echo esc_html( $text ); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private static function get_pricing_data(): array {
|
||||
$fetcher = new CMatic_Remote_Fetcher(
|
||||
array(
|
||||
'url' => 'https://api.chimpmatic.com/promo',
|
||||
'cache_key' => 'cmatic_pricing_data',
|
||||
'cache_duration' => DAY_IN_SECONDS,
|
||||
'fallback_data' => array(
|
||||
'regular_price' => 39,
|
||||
'sale_price' => 29.25,
|
||||
'discount_percent' => 40,
|
||||
'coupon_code' => 'NOW40',
|
||||
'formatted' => '$39 → $29.25 • Save 40%',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
return $fetcher->get_data();
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* Tags preview panel for Pro feature showcase.
|
||||
*
|
||||
* @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_Tags_Preview {
|
||||
|
||||
private static $type_abbrev = array(
|
||||
'checkbox' => 'CHK',
|
||||
'radio' => 'RAD',
|
||||
'select' => 'SEL',
|
||||
'text' => 'TXT',
|
||||
'hidden' => 'HID',
|
||||
'dynamictext' => 'DYN',
|
||||
'dynamichidden' => 'DYN',
|
||||
'tel' => 'TEL',
|
||||
'number' => 'NUM',
|
||||
);
|
||||
|
||||
private static $allowed_types = array(
|
||||
'checkbox',
|
||||
'radio',
|
||||
'select',
|
||||
'text',
|
||||
'hidden',
|
||||
'dynamictext',
|
||||
'dynamichidden',
|
||||
'tel',
|
||||
'number',
|
||||
);
|
||||
|
||||
public static function render( array $form_tags, array $cf7_mch, int $api_valid ): void {
|
||||
if ( empty( $form_tags ) || ! is_array( $form_tags ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filtered_tags = array_filter(
|
||||
$form_tags,
|
||||
function ( $tag ) {
|
||||
$basetype = is_array( $tag ) ? ( $tag['basetype'] ?? '' ) : ( $tag->basetype ?? '' );
|
||||
return in_array( $basetype, self::$allowed_types, true );
|
||||
}
|
||||
);
|
||||
|
||||
if ( empty( $filtered_tags ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$disclosure_class = ( 1 === $api_valid ) ? 'spt-response-out spt-valid' : 'spt-response-out chmp-inactive';
|
||||
$name_list = self::get_audience_name( $cf7_mch );
|
||||
?>
|
||||
<div class="<?php echo esc_attr( $disclosure_class ); ?>">
|
||||
<div class="mce-custom-fields holder-img">
|
||||
<h3 class="title cmatic-title-with-toggle">
|
||||
<span>Tags for <span class="audience-name"><?php echo esc_html( $name_list ); ?></span></span>
|
||||
<label class="cmatic-toggle-row">
|
||||
<span class="cmatic-toggle-label">Sync Tags</span>
|
||||
<a href="<?php echo esc_url( Cmatic_Pursuit::upgrade( 'sync_tags_help' ) ); ?>" target="_blank" class="cmatic-help-icon" title="Learn about Sync Tags">?</a>
|
||||
<span class="cmatic-toggle">
|
||||
<input type="checkbox" data-field="sync_tags" value="1"<?php echo ! empty( $cf7_mch['sync_tags'] ) ? ' checked' : ''; ?>>
|
||||
<span class="cmatic-toggle-slider"></span>
|
||||
</span>
|
||||
</label>
|
||||
</h3>
|
||||
<p>You can add these as your contacts tags:</p>
|
||||
<div id="chm_panel_camposformatags">
|
||||
<?php self::render_tag_chips( $filtered_tags, $cf7_mch ); ?>
|
||||
<label class="atags"><b>Arbitrary Tags Here:</b> <input type="text" id="wpcf7-mailchimp-labeltags_cm-tag" name="wpcf7-mailchimp[labeltags_cm-tag]" value="<?php echo isset( $cf7_mch['labeltags_cm-tag'] ) ? esc_attr( $cf7_mch['labeltags_cm-tag'] ) : ''; ?>" placeholder="comma, separated, texts, or [mail-tags]">
|
||||
<p class="description">You can type in your tags here. Comma separated text or [mail-tags]</p>
|
||||
</label>
|
||||
</div>
|
||||
<a class="lin-to-pro" href="<?php echo esc_url( Cmatic_Pursuit::upgrade( 'tags_link' ) ); ?>" target="_blank" title="ChimpMatic Pro Options"><span>PRO Feature <span>Learn More...</span></span></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
private static function render_tag_chips( array $tags, array $cf7_mch ): void {
|
||||
echo '<div class="cmatic-tags-grid">';
|
||||
$i = 1;
|
||||
foreach ( $tags as $tag ) {
|
||||
$tag_name = is_array( $tag ) ? ( $tag['name'] ?? null ) : ( $tag->name ?? null );
|
||||
$tag_basetype = is_array( $tag ) ? ( $tag['basetype'] ?? null ) : ( $tag->basetype ?? null );
|
||||
|
||||
if ( empty( $tag_name ) || empty( $tag_basetype ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_checked = isset( $cf7_mch['labeltags'][ $tag_name ] );
|
||||
$type_short = self::$type_abbrev[ $tag_basetype ] ?? strtoupper( substr( $tag_basetype, 0, 3 ) );
|
||||
$selected_class = $is_checked ? ' selected' : '';
|
||||
?>
|
||||
<label class="cmatic-tag-chip<?php echo esc_attr( $selected_class ); ?>">
|
||||
<input type="checkbox" id="wpcf7-mailchimp-labeltags-<?php echo esc_attr( $i ); ?>" name="wpcf7-mailchimp[labeltags][<?php echo esc_attr( trim( $tag_name ) ); ?>]" value="1"<?php echo $is_checked ? ' checked="checked"' : ''; ?> />
|
||||
<span class="cmatic-tag-name">[<?php echo esc_html( $tag_name ); ?>]</span>
|
||||
<span class="cmatic-tag-type"><?php echo esc_html( $type_short ); ?></span>
|
||||
</label>
|
||||
<?php
|
||||
++$i;
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
private static function get_audience_name( array $cf7_mch ): string {
|
||||
$arrlist = isset( $cf7_mch['lisdata']['lists'] ) ? array_column( $cf7_mch['lisdata']['lists'], 'name', 'id' ) : array();
|
||||
$idlist = '';
|
||||
|
||||
if ( isset( $cf7_mch['list'] ) ) {
|
||||
if ( is_array( $cf7_mch['list'] ) ) {
|
||||
$idlist = reset( $cf7_mch['list'] );
|
||||
if ( false === $idlist ) {
|
||||
$idlist = '';
|
||||
}
|
||||
} else {
|
||||
$idlist = $cf7_mch['list'];
|
||||
}
|
||||
}
|
||||
|
||||
return ( ! empty( $idlist ) && isset( $arrlist[ $idlist ] ) ) ? $arrlist[ $idlist ] : '';
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* Test submission modal 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;
|
||||
|
||||
if ( ! class_exists( 'Cmatic_Test_Submission_Modal' ) ) {
|
||||
class Cmatic_Test_Submission_Modal extends Cmatic_Modal {
|
||||
private $contact_form = null;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct( 'cmatic-test-modal' );
|
||||
}
|
||||
|
||||
public function init() {
|
||||
if ( $this->initialized ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'wpcf7_admin_footer', array( $this, 'render_modal_with_form' ), 20, 1 );
|
||||
|
||||
$this->initialized = true;
|
||||
}
|
||||
|
||||
public function render_modal_with_form( $post ) {
|
||||
if ( ! $post || ! method_exists( $post, 'id' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$form_id = $post->id();
|
||||
if ( ! $form_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->contact_form = wpcf7_contact_form( $form_id );
|
||||
if ( ! $this->contact_form ) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::render_modal();
|
||||
}
|
||||
|
||||
protected function get_title() {
|
||||
return __( 'Test Current Form Submission', 'chimpmatic-lite' );
|
||||
}
|
||||
|
||||
protected function render_header_actions() {
|
||||
?>
|
||||
<button type="button" class="cmatic-modal__submit button button-primary">
|
||||
<?php esc_html_e( 'Submit', 'chimpmatic-lite' ); ?>
|
||||
</button>
|
||||
<?php
|
||||
}
|
||||
|
||||
protected function get_body() {
|
||||
if ( ! $this->contact_form ) {
|
||||
return '<p>' . esc_html__( 'No form available.', 'chimpmatic-lite' ) . '</p>';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<div class="cmatic-modal__feedback" style="display: none;">
|
||||
<div class="cmatic-modal__feedback-icon"></div>
|
||||
<div class="cmatic-modal__feedback-content">
|
||||
<div class="cmatic-modal__feedback-title"></div>
|
||||
<div class="cmatic-modal__feedback-details"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cmatic-test-form-wrap">
|
||||
<?php
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo $this->contact_form->form_html( array( 'html_class' => 'cmatic-test-form' ) );
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
protected function get_strings() {
|
||||
return array_merge(
|
||||
parent::get_strings(),
|
||||
array(
|
||||
'submit' => __( 'Submit', 'chimpmatic-lite' ),
|
||||
'submitting' => __( 'Submitting...', 'chimpmatic-lite' ),
|
||||
'success' => __( 'Success!', 'chimpmatic-lite' ),
|
||||
'error' => __( 'Error', 'chimpmatic-lite' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Asset cache busting utility.
|
||||
*
|
||||
* @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_Buster {
|
||||
|
||||
private string $plugin_version;
|
||||
|
||||
private bool $is_debug;
|
||||
|
||||
private static ?self $instance = null;
|
||||
|
||||
public function __construct( string $plugin_version = SPARTAN_MCE_VERSION, ?bool $is_debug = null ) {
|
||||
$this->plugin_version = $plugin_version;
|
||||
$this->is_debug = $is_debug ?? ( defined( 'WP_DEBUG' ) && WP_DEBUG );
|
||||
}
|
||||
|
||||
public function get_version( string $file_path ): string {
|
||||
$version_parts = array( $this->plugin_version );
|
||||
|
||||
if ( file_exists( $file_path ) ) {
|
||||
$version_parts[] = (string) filemtime( $file_path );
|
||||
$version_parts[] = substr( md5_file( $file_path ), 0, 8 );
|
||||
}
|
||||
|
||||
if ( $this->is_debug ) {
|
||||
$version_parts[] = (string) time();
|
||||
}
|
||||
|
||||
return implode( '-', $version_parts );
|
||||
}
|
||||
|
||||
public static function instance(): self {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* Debug file logger.
|
||||
*
|
||||
* @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_File_Logger implements Cmatic_Logger_Interface {
|
||||
|
||||
private $is_write_enabled = false;
|
||||
|
||||
private $log_prefix;
|
||||
|
||||
public function __construct( $context = 'ChimpMatic', $enabled = false ) {
|
||||
|
||||
$this->is_write_enabled = (bool) $enabled && ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG );
|
||||
$this->log_prefix = '[' . sanitize_key( $context ) . ']';
|
||||
}
|
||||
|
||||
public function log( string $level, string $message, $context = null ): void {
|
||||
if ( ! $this->is_write_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$level_str = strtoupper( $level );
|
||||
$log_message = "[ChimpMatic Lite] {$this->log_prefix} [{$level_str}] " . trim( $message );
|
||||
|
||||
if ( ! is_null( $context ) ) {
|
||||
$context_string = $this->format_data( $context );
|
||||
$log_message .= ' | Data: ' . $context_string;
|
||||
}
|
||||
|
||||
error_log( $log_message ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
}
|
||||
|
||||
private function format_data( $data ) {
|
||||
if ( is_string( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( ! is_array( $data ) ) {
|
||||
return wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
if ( isset( $data['email_address'] ) && isset( $data['status'] ) ) {
|
||||
$summary = array(
|
||||
'email' => $data['email_address'] ?? '',
|
||||
'status' => $data['status'] ?? '',
|
||||
'id' => $data['id'] ?? '',
|
||||
);
|
||||
return wp_json_encode( $summary, JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) && isset( $data['payload'] ) ) {
|
||||
$merge_fields = $data['payload']['merge_fields'] ?? array();
|
||||
if ( is_object( $merge_fields ) ) {
|
||||
$merge_fields = (array) $merge_fields;
|
||||
}
|
||||
$summary = array(
|
||||
'url' => $data['url'],
|
||||
'email' => $data['payload']['email_address'] ?? '',
|
||||
'status' => $data['payload']['status'] ?? '',
|
||||
'fields' => is_array( $merge_fields ) ? array_keys( $merge_fields ) : array(),
|
||||
);
|
||||
return wp_json_encode( $summary, JSON_UNESCAPED_SLASHES );
|
||||
}
|
||||
|
||||
$json = wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
|
||||
if ( strlen( $json ) <= 1000 ) {
|
||||
return $json;
|
||||
}
|
||||
|
||||
return substr( $json, 0, 1000 ) . '... [truncated]';
|
||||
}
|
||||
|
||||
private function map_numeric_level_to_string( $numeric_level ) {
|
||||
switch ( (int) $numeric_level ) {
|
||||
case 1:
|
||||
return 'INFO';
|
||||
case 2:
|
||||
return 'DEBUG';
|
||||
case 3:
|
||||
return 'WARNING';
|
||||
case 4:
|
||||
return 'ERROR';
|
||||
case 5:
|
||||
return 'CRITICAL';
|
||||
default:
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Lite field restrictions and limits.
|
||||
*
|
||||
* @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_Lite_Get_Fields {
|
||||
|
||||
public static function cmatic_lite_fields() {
|
||||
return array(
|
||||
'source',
|
||||
'ip_signup',
|
||||
'subscribed',
|
||||
'timestamp_signup',
|
||||
'member_rating',
|
||||
'location',
|
||||
'email_client',
|
||||
'vip',
|
||||
'language',
|
||||
'email_type',
|
||||
'consents_to_one_to_one_messaging',
|
||||
);
|
||||
}
|
||||
|
||||
public static function cmatic_lite_sections() {
|
||||
return array(
|
||||
'tags',
|
||||
'interests',
|
||||
'marketing_permissions',
|
||||
);
|
||||
}
|
||||
|
||||
public static function cmatic_lite_merge_fields() {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* URL tracking and link 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;
|
||||
|
||||
final class Cmatic_Pursuit {
|
||||
|
||||
const PLUGIN_ID = 'chimpmatic_lite';
|
||||
const BASE_URLS = array(
|
||||
'docs' => 'https://chimpmatic.com',
|
||||
'pricing' => 'https://chimpmatic.com/pricing',
|
||||
'support' => 'https://chimpmatic.com/contact',
|
||||
'promo' => 'https://chimpmatic.com/almost-there',
|
||||
'home' => 'https://chimpmatic.com',
|
||||
'author' => 'https://renzojohnson.com',
|
||||
);
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
public static function url( string $base_url, string $medium, string $content = '', string $campaign = '' ): string {
|
||||
if ( empty( $base_url ) ) {
|
||||
return '';
|
||||
}
|
||||
$params = array(
|
||||
'utm_source' => self::PLUGIN_ID,
|
||||
'utm_medium' => self::sanitize( $medium ),
|
||||
'utm_campaign' => $campaign ? self::sanitize( $campaign ) : 'plugin_' . gmdate( 'Y' ),
|
||||
);
|
||||
if ( $content ) {
|
||||
$params['utm_content'] = self::sanitize( $content );
|
||||
}
|
||||
return add_query_arg( $params, $base_url );
|
||||
}
|
||||
|
||||
public static function docs( string $slug = '', string $content = '' ): string {
|
||||
$base = self::BASE_URLS['docs'];
|
||||
if ( $slug ) {
|
||||
$base = trailingslashit( $base ) . ltrim( $slug, '/' );
|
||||
}
|
||||
return self::url( $base, 'plugin', $content, 'docs' );
|
||||
}
|
||||
|
||||
public static function upgrade( string $content = '' ): string {
|
||||
return self::url( self::BASE_URLS['pricing'], 'plugin', $content, 'upgrade' );
|
||||
}
|
||||
|
||||
public static function support( string $content = '' ): string {
|
||||
return self::url( self::BASE_URLS['support'], 'plugin', $content, 'support' );
|
||||
}
|
||||
|
||||
public static function promo( string $content = '', int $discount = 40 ): string {
|
||||
$params = array(
|
||||
'u_source' => self::PLUGIN_ID,
|
||||
'u_medium' => 'banner',
|
||||
'u_campaign' => 'promo_' . $discount . 'off',
|
||||
);
|
||||
if ( $content ) {
|
||||
$params['u_content'] = self::sanitize( $content );
|
||||
}
|
||||
return add_query_arg( $params, self::BASE_URLS['promo'] );
|
||||
}
|
||||
|
||||
public static function home( string $content = '' ): string {
|
||||
return self::url( self::BASE_URLS['home'], 'plugin', $content, 'brand' );
|
||||
}
|
||||
|
||||
public static function author( string $content = '' ): string {
|
||||
return self::url( self::BASE_URLS['author'], 'plugin', $content, 'author' );
|
||||
}
|
||||
|
||||
public static function adminbar( string $destination, string $content = '' ): string {
|
||||
$base = self::BASE_URLS[ $destination ] ?? self::BASE_URLS['home'];
|
||||
return self::url( $base, 'adminbar', $content, $destination );
|
||||
}
|
||||
|
||||
private static function sanitize( string $value ): string {
|
||||
return preg_replace( '/[^a-z0-9_]/', '', str_replace( array( ' ', '-' ), '_', strtolower( trim( $value ) ) ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* Remote data fetcher with caching.
|
||||
*
|
||||
* @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_Remote_Fetcher {
|
||||
|
||||
private $config;
|
||||
|
||||
private $defaults = array(
|
||||
'url' => '',
|
||||
'cache_key' => 'cmatic_remote_data',
|
||||
'cache_duration' => DAY_IN_SECONDS,
|
||||
'retry_interval' => 600, // 10 minutes in seconds
|
||||
'max_retries' => 3,
|
||||
'retry_count_key' => 'cmatic_retry_count',
|
||||
'cron_hook' => 'cmatic_fetch_retry',
|
||||
'timeout' => 15,
|
||||
'fallback_data' => array(),
|
||||
'parser_callback' => null,
|
||||
);
|
||||
|
||||
public function __construct( $config = array() ) {
|
||||
$this->config = wp_parse_args( $config, $this->defaults );
|
||||
add_action( $this->config['cron_hook'], array( $this, 'cron_retry_fetch' ) );
|
||||
}
|
||||
|
||||
public function get_data() {
|
||||
$cached_data = $this->get_cache();
|
||||
|
||||
if ( false !== $cached_data ) {
|
||||
return $cached_data;
|
||||
}
|
||||
|
||||
$fresh_data = $this->fetch_fresh_data();
|
||||
|
||||
if ( false !== $fresh_data ) {
|
||||
$this->set_cache( $fresh_data );
|
||||
$this->clear_retry_schedule();
|
||||
return $fresh_data;
|
||||
}
|
||||
|
||||
$this->schedule_retry();
|
||||
return $this->get_fallback_data();
|
||||
}
|
||||
|
||||
public function get_cache() {
|
||||
return get_transient( $this->config['cache_key'] );
|
||||
}
|
||||
|
||||
public function set_cache( $data ) {
|
||||
return set_transient(
|
||||
$this->config['cache_key'],
|
||||
$data,
|
||||
$this->config['cache_duration']
|
||||
);
|
||||
}
|
||||
|
||||
public function clear_cache() {
|
||||
return delete_transient( $this->config['cache_key'] );
|
||||
}
|
||||
|
||||
private function fetch_fresh_data() {
|
||||
if ( empty( $this->config['url'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response = wp_remote_get(
|
||||
$this->config['url'],
|
||||
array(
|
||||
'timeout' => $this->config['timeout'],
|
||||
'user-agent' => 'WordPress/' . get_bloginfo( 'version' ) . '; ' . home_url(),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 !== $response_code ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
if ( empty( $body ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->parse_content( $body );
|
||||
}
|
||||
|
||||
/** Parse fetched content. */
|
||||
private function parse_content( $content ) {
|
||||
if ( is_callable( $this->config['parser_callback'] ) ) {
|
||||
return call_user_func( $this->config['parser_callback'], $content );
|
||||
}
|
||||
|
||||
$json_data = $this->parse_pricing_json( $content );
|
||||
if ( false !== $json_data ) {
|
||||
return $json_data;
|
||||
}
|
||||
|
||||
return $this->parse_pricing_html( $content );
|
||||
}
|
||||
|
||||
private function parse_pricing_json( $content ) {
|
||||
$json = json_decode( $content, true );
|
||||
|
||||
if ( null === $json || ! is_array( $json ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $json['regular_price'] ) || ! isset( $json['discount_percent'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pricing_data = array(
|
||||
'regular_price' => (int) $json['regular_price'],
|
||||
'sale_price' => isset( $json['sale_price'] ) ? (float) $json['sale_price'] : null,
|
||||
'discount_percent' => (int) $json['discount_percent'],
|
||||
'coupon_code' => isset( $json['coupon_code'] ) ? sanitize_text_field( $json['coupon_code'] ) : null,
|
||||
'last_updated' => current_time( 'mysql' ),
|
||||
);
|
||||
|
||||
if ( null === $pricing_data['sale_price'] ) {
|
||||
$discount_amount = $pricing_data['regular_price'] * ( $pricing_data['discount_percent'] / 100 );
|
||||
$pricing_data['sale_price'] = $pricing_data['regular_price'] - $discount_amount;
|
||||
}
|
||||
|
||||
if ( null === $pricing_data['coupon_code'] ) {
|
||||
$pricing_data['coupon_code'] = 'NOW' . $pricing_data['discount_percent'];
|
||||
}
|
||||
|
||||
$pricing_data['formatted'] = sprintf(
|
||||
'$%d → $%s • Save %d%%',
|
||||
$pricing_data['regular_price'],
|
||||
number_format( $pricing_data['sale_price'], 2 ),
|
||||
$pricing_data['discount_percent']
|
||||
);
|
||||
|
||||
return $pricing_data;
|
||||
}
|
||||
|
||||
private function parse_pricing_html( $html ) {
|
||||
$pricing_data = array(
|
||||
'regular_price' => null,
|
||||
'sale_price' => null,
|
||||
'discount_percent' => null,
|
||||
'coupon_code' => null,
|
||||
'formatted' => null,
|
||||
'last_updated' => current_time( 'mysql' ),
|
||||
);
|
||||
|
||||
if ( preg_match( '/Single\s+Site[^$]*\$(\d+)\/year/i', $html, $matches ) ) {
|
||||
$pricing_data['regular_price'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
if ( preg_match( '/(\d+)%\s+Off/i', $html, $matches ) ) {
|
||||
$pricing_data['discount_percent'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
if ( preg_match( '/coupon\s+code\s+["\']([A-Z0-9]+)["\']/i', $html, $matches ) ) {
|
||||
$pricing_data['coupon_code'] = sanitize_text_field( $matches[1] );
|
||||
}
|
||||
|
||||
if ( $pricing_data['regular_price'] && $pricing_data['discount_percent'] ) {
|
||||
$discount_amount = $pricing_data['regular_price'] * ( $pricing_data['discount_percent'] / 100 );
|
||||
$pricing_data['sale_price'] = $pricing_data['regular_price'] - $discount_amount;
|
||||
}
|
||||
|
||||
if ( $pricing_data['regular_price'] && $pricing_data['sale_price'] && $pricing_data['discount_percent'] ) {
|
||||
$pricing_data['formatted'] = sprintf(
|
||||
'$%d → $%s • Save %d%%',
|
||||
$pricing_data['regular_price'],
|
||||
number_format( $pricing_data['sale_price'], 2 ),
|
||||
$pricing_data['discount_percent']
|
||||
);
|
||||
}
|
||||
|
||||
if ( null === $pricing_data['regular_price'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $pricing_data;
|
||||
}
|
||||
|
||||
private function get_fallback_data() {
|
||||
if ( ! empty( $this->config['fallback_data'] ) ) {
|
||||
return $this->config['fallback_data'];
|
||||
}
|
||||
|
||||
return array(
|
||||
'regular_price' => 39,
|
||||
'sale_price' => 29.25,
|
||||
'discount_percent' => 25,
|
||||
'coupon_code' => 'NOW25',
|
||||
'formatted' => '$39 → $29.25 • Save 25%',
|
||||
'last_updated' => null,
|
||||
);
|
||||
}
|
||||
|
||||
private function schedule_retry() {
|
||||
$retry_count = (int) get_option( $this->config['retry_count_key'], 0 );
|
||||
|
||||
if ( $retry_count >= $this->config['max_retries'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_option( $this->config['retry_count_key'], $retry_count + 1 );
|
||||
|
||||
if ( ! wp_next_scheduled( $this->config['cron_hook'] ) ) {
|
||||
wp_schedule_single_event(
|
||||
time() + $this->config['retry_interval'],
|
||||
$this->config['cron_hook']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function cron_retry_fetch() {
|
||||
$fresh_data = $this->fetch_fresh_data();
|
||||
|
||||
if ( false !== $fresh_data ) {
|
||||
$this->set_cache( $fresh_data );
|
||||
$this->clear_retry_schedule();
|
||||
} else {
|
||||
$this->schedule_retry();
|
||||
}
|
||||
}
|
||||
|
||||
private function clear_retry_schedule() {
|
||||
$timestamp = wp_next_scheduled( $this->config['cron_hook'] );
|
||||
if ( $timestamp ) {
|
||||
wp_unschedule_event( $timestamp, $this->config['cron_hook'] );
|
||||
}
|
||||
|
||||
delete_option( $this->config['retry_count_key'] );
|
||||
}
|
||||
|
||||
public function clear_all() {
|
||||
$this->clear_cache();
|
||||
$this->clear_retry_schedule();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Utility functions for ChimpMatic.
|
||||
*
|
||||
* @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_Utils {
|
||||
|
||||
const CMATIC_FB_A = 'chimpmatic';
|
||||
|
||||
public static function validate_bool( $value ): bool {
|
||||
if ( is_bool( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ( is_int( $value ) || is_float( $value ) ) {
|
||||
return (bool) $value;
|
||||
}
|
||||
|
||||
if ( is_string( $value ) ) {
|
||||
$v = strtolower( trim( $value ) );
|
||||
|
||||
if ( '' === $v ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_numeric( $v ) ) {
|
||||
return 0.0 !== (float) $v;
|
||||
}
|
||||
|
||||
static $true = array( 'true', 'on', 'yes', 'y', '1' );
|
||||
static $false = array( 'false', 'off', 'no', 'n', '0' );
|
||||
|
||||
if ( in_array( $v, $true, true ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( in_array( $v, $false, true ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function get_first( $array, $default = '' ) {
|
||||
if ( is_array( $array ) && ! empty( $array ) ) {
|
||||
return reset( $array );
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
public static function get_newest_form_id(): ?int {
|
||||
$forms = get_posts(
|
||||
array(
|
||||
'post_type' => 'wpcf7_contact_form',
|
||||
'posts_per_page' => 1,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'DESC',
|
||||
'post_status' => 'publish',
|
||||
'fields' => 'ids',
|
||||
)
|
||||
);
|
||||
|
||||
return ! empty( $forms ) ? (int) $forms[0] : null;
|
||||
}
|
||||
|
||||
public static function get_days_since( int $timestamp ): int {
|
||||
$datetime_now = new \DateTime( 'now' );
|
||||
$datetime_from = new \DateTime( '@' . $timestamp );
|
||||
|
||||
$diff = date_diff( $datetime_now, $datetime_from );
|
||||
|
||||
return (int) $diff->format( '%a' );
|
||||
}
|
||||
|
||||
private function __construct() {}
|
||||
|
||||
private function __clone() {}
|
||||
|
||||
public function __wakeup() {
|
||||
throw new \Exception( 'Cannot unserialize singleton' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
== For Translators ==
|
||||
|
||||
Note: this folder contains MO files and POT file only. If you are looking for PO file, you can download it from here:
|
||||
|
||||
URL Coming soon...
|
||||
|
||||
If you have created your own translation, or have an update of an existing one, please send it to Renzo Johnson <renzo.johnson@gmail.com> so that I can bundle it into the next release of Contact Form 7 Campaign Monitor Ext.
|
||||
|
||||
Thank you.
|
||||
@@ -0,0 +1,355 @@
|
||||
Chimpmatic Lite
|
||||
Copyright (C) 2010–2025 Chimpmatic <hello@chimpmatic.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it
|
||||
under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, see https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
-----------------------------------------------------------------------
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Library General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS FOR COPYING,
|
||||
DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, see https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Library General
|
||||
Public License instead of this License.
|
||||
@@ -0,0 +1,225 @@
|
||||
=== Connect Contact Form 7 and Mailchimp ===
|
||||
Contributors: rnzo, chimpmatic
|
||||
Donate link: https://bit.ly/2HdTzmO
|
||||
Tags: contact form 7 mailchimp, cf7 mailchimp, mailchimp, mailchimp integration, contact form 7
|
||||
Requires at least: 6.4
|
||||
Tested up to: 6.9
|
||||
Stable tag: 0.9.76
|
||||
License: GPLv2 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
Connect Contact Form 7 to Mailchimp. Automatically sync form submissions to your Mailchimp audiences with merge field mapping, double opt-in, and opt-in checkbox support.
|
||||
|
||||
== Description ==
|
||||
|
||||
**Chimpmatic Lite** is the easiest way to connect **Contact Form 7 to Mailchimp**. When a visitor submits your CF7 form, their information is automatically added to your Mailchimp audience using the Mailchimp API v3.
|
||||
|
||||
Unlike heavyweight form builders, Chimpmatic is a specialist CF7-to-Mailchimp integration. It does one thing and does it well: syncing your Contact Form 7 submissions directly to Mailchimp.
|
||||
|
||||
= Why Chimpmatic? =
|
||||
|
||||
* **Built specifically for Contact Form 7** - not a generic form connector
|
||||
* **Lightweight** - no bloat, no extra form builders, no overhead
|
||||
* **Mailchimp API v3** - uses the latest official Mailchimp API
|
||||
* **50,000+ active installs** - trusted by WordPress sites worldwide
|
||||
|
||||
= Free Features =
|
||||
|
||||
* Connect any Contact Form 7 form to a Mailchimp audience
|
||||
* Map form fields to Mailchimp merge fields (FNAME, LNAME, etc.)
|
||||
* Use a **different Mailchimp API key** per contact form
|
||||
* Use a **different mailing list** per contact form
|
||||
* **Single opt-in** or **double opt-in** support
|
||||
* **Opt-in checkbox** - let visitors choose to subscribe
|
||||
* Unlimited contact forms
|
||||
* Works with any Contact Form 7 form layout
|
||||
|
||||
= Premium Features (Chimpmatic PRO) =
|
||||
|
||||
Upgrade to [Chimpmatic PRO](https://chimpmatic.com/pro) for advanced Mailchimp integration:
|
||||
|
||||
* **Mailchimp Tags** - automatically tag subscribers from form submissions
|
||||
* **Mailchimp Groups** - assign subscribers to interest groups
|
||||
* **Unlimited custom merge fields** - map any CF7 field to Mailchimp
|
||||
* **GDPR consent checkbox** - built-in compliance for email marketing
|
||||
* **Birthday field support** - sync date fields to Mailchimp
|
||||
* **Email verification** - validate emails before subscribing
|
||||
* **Unsubscribe, archive, and delete** subscriber management
|
||||
* **Priority support** - direct help from the developer
|
||||
|
||||
[Learn more about Chimpmatic PRO](https://chimpmatic.com/pro)
|
||||
|
||||
= Requirements =
|
||||
|
||||
1. WordPress 6.4 or higher
|
||||
2. Contact Form 7 (5.0 or higher)
|
||||
3. PHP 7.4 or higher
|
||||
4. A free or paid Mailchimp account
|
||||
|
||||
= Support =
|
||||
|
||||
* [Documentation & Help Center](https://chimpmatic.com/help)
|
||||
* [Getting Started Guide](https://chimpmatic.com/connect-contact-form-7-with-mailchimp)
|
||||
* [Bug Reports & Feature Requests](https://chimpmatic.com/contact)
|
||||
|
||||
== Installation ==
|
||||
|
||||
1. Install the plugin from the WordPress Plugin Directory, or upload the plugin folder to `/wp-content/plugins/`.
|
||||
2. Activate the plugin through the **Plugins** menu in WordPress.
|
||||
3. Edit any Contact Form 7 form - you will see a new **Mailchimp** tab.
|
||||
4. Enter your Mailchimp API key, select your audience, and map your fields.
|
||||
|
||||
= How to Find Your Mailchimp API Key =
|
||||
|
||||
1. Log in to your Mailchimp account.
|
||||
2. Click your profile icon and select **Account & billing**.
|
||||
3. Go to **Extras** > **API Keys**.
|
||||
4. Click **Create A Key** and copy the key.
|
||||
5. Paste it into the Mailchimp tab in your Contact Form 7 editor.
|
||||
|
||||
For a detailed walkthrough with screenshots, see our [Mailchimp API Key Guide](https://chimpmatic.com/how-to-get-your-mailchimp-api-key).
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= How do I connect Contact Form 7 to Mailchimp? =
|
||||
|
||||
After installing Chimpmatic Lite, edit any Contact Form 7 form. Click the **Mailchimp** tab, enter your API key, select your Mailchimp audience, and map your form fields to Mailchimp merge tags. Submissions will sync automatically.
|
||||
|
||||
See the full [setup guide](https://chimpmatic.com/connect-contact-form-7-with-mailchimp).
|
||||
|
||||
= How do I find my Mailchimp API key? =
|
||||
|
||||
Log in to Mailchimp, click your profile icon, go to Account & billing > Extras > API Keys, and click Create A Key. Copy the key and paste it into the Chimpmatic settings in your CF7 form editor.
|
||||
|
||||
= Can I use different Mailchimp lists for different forms? =
|
||||
|
||||
Yes. Each Contact Form 7 form has its own Mailchimp tab where you can set a different API key, audience, and field mapping.
|
||||
|
||||
= Does this support double opt-in? =
|
||||
|
||||
Yes. You can choose between single opt-in (subscriber is added immediately) or double opt-in (Mailchimp sends a confirmation email first). This is configured per form.
|
||||
|
||||
= Can I add an opt-in checkbox? =
|
||||
|
||||
Yes. Chimpmatic Lite includes an opt-in checkbox feature so visitors can choose whether to subscribe to your Mailchimp list when submitting the form.
|
||||
|
||||
= What is the difference between Mailchimp tags and groups? =
|
||||
|
||||
Tags are labels you assign to subscribers for internal organization (e.g., "From Contact Page"). Groups are subscriber-facing categories that let people choose their interests. Both are available in [Chimpmatic PRO](https://chimpmatic.com/pricing).
|
||||
|
||||
= How do I add GDPR consent to Contact Form 7? =
|
||||
|
||||
GDPR consent checkbox support is available in [Chimpmatic PRO](https://chimpmatic.com/pro). It adds a required opt-in checkbox that maps to Mailchimp's GDPR marketing permission fields.
|
||||
|
||||
= Contact Form 7 not sending to Mailchimp? =
|
||||
|
||||
Common fixes: (1) Check your API key is correct and not expired. (2) Verify the email field is mapped properly. (3) Check for trailing spaces in the API key. (4) Make sure your Mailchimp audience is not archived. See our [troubleshooting guide](https://chimpmatic.com/connect-contact-form-7-with-mailchimp).
|
||||
|
||||
= Does this work with Mailchimp merge fields? =
|
||||
|
||||
Yes. You can map any Contact Form 7 field to Mailchimp merge fields like FNAME, LNAME, ADDRESS, and more. For unlimited custom merge fields, upgrade to [Chimpmatic PRO](https://chimpmatic.com/pro).
|
||||
|
||||
== Screenshots ==
|
||||
|
||||
1. Mailchimp tab in the Contact Form 7 editor - enter your API key and select your audience.
|
||||
2. Field mapping interface - connect your CF7 form fields to Mailchimp merge fields.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
For the full changelog with details, see [Release History](https://renzojohnson.com/changelog).
|
||||
|
||||
= 0.9.75 =
|
||||
|
||||
[Version 0.9.75 release notes](https://renzojohnson.com/changelog#0.9.75)
|
||||
|
||||
= 0.9.73 =
|
||||
|
||||
[Version 0.9.73 release notes](https://renzojohnson.com/changelog#0.9.73)
|
||||
|
||||
= 0.9.22 =
|
||||
|
||||
[Version 0.9.22 release notes](https://renzojohnson.com/changelog#0.9.22)
|
||||
|
||||
= 0.8.01 =
|
||||
|
||||
[Version 0.8.01 release notes](https://renzojohnson.com/changelog#0.8.01)
|
||||
|
||||
= 0.7.50 =
|
||||
|
||||
[Version 0.7.50 release notes](https://renzojohnson.com/changelog#0.7.50)
|
||||
|
||||
= 0.7.01 =
|
||||
|
||||
[Version 0.7.01 release notes](https://renzojohnson.com/changelog#0.7.01)
|
||||
|
||||
= 0.6.10 =
|
||||
|
||||
[Version 0.6.10 release notes](https://renzojohnson.com/changelog#0.6.10)
|
||||
|
||||
= 0.5.64 =
|
||||
|
||||
[Version 0.5.64 release notes](https://renzojohnson.com/changelog#0.5.64)
|
||||
|
||||
= 0.5.01 =
|
||||
|
||||
[Version 0.5.01 release notes](https://renzojohnson.com/changelog#0.5.01)
|
||||
|
||||
= 0.4.60 =
|
||||
|
||||
[Version 0.4.60 release notes](https://renzojohnson.com/changelog#0.4.60)
|
||||
|
||||
= 0.4.43 =
|
||||
|
||||
[Version 0.4.43 release notes](https://renzojohnson.com/changelog#0.4.43)
|
||||
|
||||
= 0.4.01 =
|
||||
|
||||
[Version 0.4.01 release notes](https://renzojohnson.com/changelog#0.4.01)
|
||||
|
||||
= 0.3.50 =
|
||||
|
||||
[Version 0.3.50 release notes](https://renzojohnson.com/changelog#0.3.50)
|
||||
|
||||
= 0.3.40 =
|
||||
|
||||
[Version 0.3.40 release notes](https://renzojohnson.com/changelog#0.3.40)
|
||||
|
||||
= 0.3.20 =
|
||||
|
||||
[Version 0.3.20 release notes](https://renzojohnson.com/changelog#0.3.20)
|
||||
|
||||
= 0.3.10 =
|
||||
|
||||
[Version 0.3.10 release notes](https://renzojohnson.com/changelog#0.3.10)
|
||||
|
||||
= 0.3.01 =
|
||||
|
||||
[Version 0.3.01 release notes](https://renzojohnson.com/changelog#0.3.01)
|
||||
|
||||
= 0.2.30 =
|
||||
|
||||
[Version 0.2.30 release notes](https://renzojohnson.com/changelog#0.2.30)
|
||||
|
||||
= 0.2.20 =
|
||||
|
||||
[Version 0.2.20 release notes](https://renzojohnson.com/changelog#0.2.20)
|
||||
|
||||
= 0.2.15 =
|
||||
|
||||
[Version 0.2.15 release notes](https://renzojohnson.com/changelog#0.2.15)
|
||||
|
||||
= 0.2.10 =
|
||||
|
||||
[Version 0.2.10 release notes](https://renzojohnson.com/changelog#0.2.10)
|
||||
|
||||
= 0.2.5 =
|
||||
|
||||
[Version 0.2.5 release notes](https://renzojohnson.com/changelog#0.2.5)
|
||||
|
||||
= 0.1.5 =
|
||||
|
||||
[Version 0.1.5 release notes](https://renzojohnson.com/changelog#0.1.5)
|
||||
|
||||
= 0.1.2 =
|
||||
|
||||
[Version 0.1.2 release notes](https://renzojohnson.com/changelog#0.1.2)
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin uninstall handler.
|
||||
*
|
||||
* @package contact-form-7-mailchimp-extension
|
||||
* @author renzo.johnson@gmail.com
|
||||
* @copyright 2014-2026 https://renzojohnson.com
|
||||
* @license GPL-3.0+
|
||||
*/
|
||||
|
||||
defined( 'WP_UNINSTALL_PLUGIN' ) || exit;
|
||||
|
||||
delete_option( 'mce_loyalty' );
|
||||
delete_option( 'chimpmatic-update' );
|
||||
delete_option( 'cmatic_log_on' );
|
||||
delete_option( 'cmatic_do_activation_redirect' );
|
||||
delete_option( 'cmatic_news_retry_count' );
|
||||
delete_option( 'csyncr_last_weekly_run' );
|
||||
|
||||
delete_option( 'cmatic' );
|
||||
|
||||
wp_clear_scheduled_hook( 'cmatic_daily_cron' );
|
||||
wp_clear_scheduled_hook( 'cmatic_weekly_telemetry' );
|
||||
wp_clear_scheduled_hook( 'cmatic_metrics_heartbeat' );
|
||||
wp_clear_scheduled_hook( 'csyncr_weekly_telemetry' );
|
||||
wp_clear_scheduled_hook( 'csyncr_metrics_heartbeat' );
|
||||
|
||||
global $wpdb;
|
||||
$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", 'cf7_mch_%' ) );
|
||||
759
html/wp-content/plugins/contact-form-7/admin/admin.php
Normal file
759
html/wp-content/plugins/contact-form-7/admin/admin.php
Normal file
@@ -0,0 +1,759 @@
|
||||
<?php
|
||||
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/admin-functions.php';
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/help-tabs.php';
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/tag-generator.php';
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/welcome-panel.php';
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/config-validator.php';
|
||||
|
||||
|
||||
add_action(
|
||||
'admin_init',
|
||||
static function () {
|
||||
do_action( 'wpcf7_admin_init' );
|
||||
},
|
||||
10, 0
|
||||
);
|
||||
|
||||
|
||||
add_action(
|
||||
'admin_menu',
|
||||
'wpcf7_admin_menu',
|
||||
9, 0
|
||||
);
|
||||
|
||||
function wpcf7_admin_menu() {
|
||||
do_action( 'wpcf7_admin_menu' );
|
||||
|
||||
add_menu_page(
|
||||
__( 'Contact Form 7', 'contact-form-7' ),
|
||||
__( 'Contact', 'contact-form-7' )
|
||||
. wpcf7_admin_menu_change_notice(),
|
||||
'wpcf7_read_contact_forms',
|
||||
'wpcf7',
|
||||
'wpcf7_admin_management_page',
|
||||
'dashicons-email',
|
||||
30
|
||||
);
|
||||
|
||||
$edit = add_submenu_page( 'wpcf7',
|
||||
__( 'Edit Contact Form', 'contact-form-7' ),
|
||||
__( 'Contact Forms', 'contact-form-7' )
|
||||
. wpcf7_admin_menu_change_notice( 'wpcf7' ),
|
||||
'wpcf7_read_contact_forms',
|
||||
'wpcf7',
|
||||
'wpcf7_admin_management_page'
|
||||
);
|
||||
|
||||
add_action( 'load-' . $edit, 'wpcf7_load_contact_form_admin', 10, 0 );
|
||||
|
||||
$addnew = add_submenu_page( 'wpcf7',
|
||||
__( 'Add Contact Form', 'contact-form-7' ),
|
||||
__( 'Add Contact Form', 'contact-form-7' )
|
||||
. wpcf7_admin_menu_change_notice( 'wpcf7-new' ),
|
||||
'wpcf7_edit_contact_forms',
|
||||
'wpcf7-new',
|
||||
'wpcf7_admin_add_new_page'
|
||||
);
|
||||
|
||||
add_action( 'load-' . $addnew, 'wpcf7_load_contact_form_admin', 10, 0 );
|
||||
|
||||
$integration = WPCF7_Integration::get_instance();
|
||||
|
||||
if ( $integration->service_exists() ) {
|
||||
$integration = add_submenu_page( 'wpcf7',
|
||||
__( 'Integration with External API', 'contact-form-7' ),
|
||||
__( 'Integration', 'contact-form-7' )
|
||||
. wpcf7_admin_menu_change_notice( 'wpcf7-integration' ),
|
||||
'wpcf7_manage_integration',
|
||||
'wpcf7-integration',
|
||||
'wpcf7_admin_integration_page'
|
||||
);
|
||||
|
||||
add_action( 'load-' . $integration, 'wpcf7_load_integration_page', 10, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function wpcf7_admin_menu_change_notice( $menu_slug = '' ) {
|
||||
$counts = apply_filters( 'wpcf7_admin_menu_change_notice',
|
||||
array(
|
||||
'wpcf7' => 0,
|
||||
'wpcf7-new' => 0,
|
||||
'wpcf7-integration' => 0,
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $menu_slug ) ) {
|
||||
$count = absint( array_sum( $counts ) );
|
||||
} elseif ( isset( $counts[$menu_slug] ) ) {
|
||||
$count = absint( $counts[$menu_slug] );
|
||||
} else {
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
if ( $count ) {
|
||||
return sprintf(
|
||||
' <span class="update-plugins %1$d"><span class="plugin-count">%2$s</span></span>',
|
||||
$count,
|
||||
esc_html( number_format_i18n( $count ) )
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
'wpcf7_admin_enqueue_scripts',
|
||||
10, 1
|
||||
);
|
||||
|
||||
function wpcf7_admin_enqueue_scripts( $hook_suffix ) {
|
||||
if ( false === strpos( $hook_suffix, 'wpcf7' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_style( 'contact-form-7-admin',
|
||||
wpcf7_plugin_url( 'admin/includes/css/styles.css' ),
|
||||
array(), WPCF7_VERSION, 'all'
|
||||
);
|
||||
|
||||
if ( wpcf7_is_rtl() ) {
|
||||
wp_enqueue_style( 'contact-form-7-admin-rtl',
|
||||
wpcf7_plugin_url( 'admin/includes/css/styles-rtl.css' ),
|
||||
array(), WPCF7_VERSION, 'all'
|
||||
);
|
||||
}
|
||||
|
||||
$assets = include wpcf7_plugin_path( 'admin/includes/js/index.asset.php' );
|
||||
|
||||
$assets = wp_parse_args( $assets, array(
|
||||
'dependencies' => array(),
|
||||
'version' => WPCF7_VERSION,
|
||||
) );
|
||||
|
||||
wp_enqueue_script( 'wpcf7-admin',
|
||||
wpcf7_plugin_url( 'admin/includes/js/index.js' ),
|
||||
$assets['dependencies'],
|
||||
$assets['version'],
|
||||
array( 'in_footer' => true )
|
||||
);
|
||||
|
||||
wp_set_script_translations( 'wpcf7-admin', 'contact-form-7' );
|
||||
|
||||
$wpcf7_obj = array(
|
||||
'apiSettings' => array(
|
||||
'root' => sanitize_url( rest_url( 'contact-form-7/v1' ) ),
|
||||
'namespace' => 'contact-form-7/v1',
|
||||
),
|
||||
);
|
||||
|
||||
$post = wpcf7_get_current_contact_form();
|
||||
|
||||
if ( $post ) {
|
||||
$wpcf7_obj = array_merge( $wpcf7_obj, array(
|
||||
'nonce' => array(
|
||||
'save' => wp_create_nonce(
|
||||
sprintf(
|
||||
'wpcf7-save-contact-form_%s',
|
||||
$post->initial() ? -1 : $post->id()
|
||||
)
|
||||
),
|
||||
'copy' => wp_create_nonce(
|
||||
sprintf(
|
||||
'wpcf7-copy-contact-form_%s',
|
||||
$post->initial() ? -1 : $post->id()
|
||||
)
|
||||
),
|
||||
'delete' => wp_create_nonce(
|
||||
sprintf(
|
||||
'wpcf7-delete-contact-form_%s',
|
||||
$post->initial() ? -1 : $post->id()
|
||||
)
|
||||
),
|
||||
),
|
||||
'configValidator' => array(
|
||||
'errors' => array(),
|
||||
'docUrl' => WPCF7_ConfigValidator::get_doc_link(),
|
||||
),
|
||||
) );
|
||||
|
||||
if (
|
||||
current_user_can( 'wpcf7_edit_contact_form', $post->id() ) and
|
||||
wpcf7_validate_configuration()
|
||||
) {
|
||||
$config_validator = new WPCF7_ConfigValidator( $post );
|
||||
$config_validator->restore();
|
||||
|
||||
$wpcf7_obj['configValidator'] = array_merge(
|
||||
$wpcf7_obj['configValidator'],
|
||||
array(
|
||||
'errors' => $config_validator->collect_error_messages(
|
||||
array( 'decodes_html_entities' => true )
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
wp_add_inline_script( 'wpcf7-admin',
|
||||
sprintf(
|
||||
'var wpcf7 = %s;',
|
||||
wp_json_encode( $wpcf7_obj, JSON_PRETTY_PRINT )
|
||||
),
|
||||
'before'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
add_filter(
|
||||
'set_screen_option_wpcf7_contact_forms_per_page',
|
||||
static function ( $result, $option, $value ) {
|
||||
$wpcf7_screens = array(
|
||||
'wpcf7_contact_forms_per_page',
|
||||
);
|
||||
|
||||
if ( in_array( $option, $wpcf7_screens, true ) ) {
|
||||
$result = $value;
|
||||
}
|
||||
|
||||
return $result;
|
||||
},
|
||||
10, 3
|
||||
);
|
||||
|
||||
|
||||
function wpcf7_load_contact_form_admin() {
|
||||
global $plugin_page;
|
||||
|
||||
$action = wpcf7_current_action();
|
||||
|
||||
do_action( 'wpcf7_admin_load',
|
||||
wpcf7_superglobal_get( 'page' ),
|
||||
$action
|
||||
);
|
||||
|
||||
if ( 'save' === $action ) {
|
||||
$id = wpcf7_superglobal_post( 'post_ID', '-1' );
|
||||
|
||||
check_admin_referer( 'wpcf7-save-contact-form_' . $id );
|
||||
|
||||
if ( ! current_user_can( 'wpcf7_edit_contact_form', $id ) ) {
|
||||
wp_die(
|
||||
esc_html( __( 'You are not allowed to edit this item.', 'contact-form-7' ) )
|
||||
);
|
||||
}
|
||||
|
||||
$contact_form = wpcf7_save_contact_form(
|
||||
array_merge(
|
||||
wp_unslash( $_REQUEST ),
|
||||
array(
|
||||
'id' => $id,
|
||||
'title' => wpcf7_superglobal_post( 'post_title', null ),
|
||||
'locale' => wpcf7_superglobal_post( 'wpcf7-locale', null ),
|
||||
'form' => wpcf7_superglobal_post( 'wpcf7-form', '' ),
|
||||
'mail' => wpcf7_superglobal_post( 'wpcf7-mail', array() ),
|
||||
'mail_2' => wpcf7_superglobal_post( 'wpcf7-mail-2', array() ),
|
||||
'messages' => wpcf7_superglobal_post( 'wpcf7-messages', array() ),
|
||||
'additional_settings' => wpcf7_superglobal_post( 'wpcf7-additional-settings', '' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if ( $contact_form and wpcf7_validate_configuration() ) {
|
||||
$config_validator = new WPCF7_ConfigValidator( $contact_form );
|
||||
$config_validator->validate();
|
||||
$config_validator->save();
|
||||
}
|
||||
|
||||
$query = array(
|
||||
'post' => $contact_form ? $contact_form->id() : 0,
|
||||
'active-tab' => wpcf7_canonicalize_name(
|
||||
wpcf7_superglobal_post( 'active-tab' )
|
||||
),
|
||||
);
|
||||
|
||||
if ( ! $contact_form ) {
|
||||
$query['message'] = 'failed';
|
||||
} elseif ( -1 === (int) $id ) {
|
||||
$query['message'] = 'created';
|
||||
} else {
|
||||
$query['message'] = 'saved';
|
||||
}
|
||||
|
||||
$redirect_to = add_query_arg( $query, menu_page_url( 'wpcf7', false ) );
|
||||
wp_safe_redirect( $redirect_to );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( 'copy' === $action ) {
|
||||
$id = absint( $_POST['post_ID'] ?? $_REQUEST['post'] ?? '' );
|
||||
|
||||
check_admin_referer( 'wpcf7-copy-contact-form_' . $id );
|
||||
|
||||
if ( ! current_user_can( 'wpcf7_edit_contact_form', $id ) ) {
|
||||
wp_die(
|
||||
esc_html( __( 'You are not allowed to edit this item.', 'contact-form-7' ) )
|
||||
);
|
||||
}
|
||||
|
||||
$query = array();
|
||||
|
||||
if ( $contact_form = wpcf7_contact_form( $id ) ) {
|
||||
$new_contact_form = $contact_form->copy();
|
||||
$new_contact_form->save();
|
||||
|
||||
$query['post'] = $new_contact_form->id();
|
||||
$query['message'] = 'created';
|
||||
}
|
||||
|
||||
$redirect_to = add_query_arg( $query, menu_page_url( 'wpcf7', false ) );
|
||||
|
||||
wp_safe_redirect( $redirect_to );
|
||||
exit();
|
||||
}
|
||||
|
||||
if ( 'delete' === $action ) {
|
||||
$nonce_action = 'bulk-posts';
|
||||
|
||||
if (
|
||||
$post_id = wpcf7_superglobal_post( 'post_ID' ) or
|
||||
! is_array( $post_id = wpcf7_superglobal_request( 'post', array() ) )
|
||||
) {
|
||||
$nonce_action = sprintf( 'wpcf7-delete-contact-form_%s', $post_id );
|
||||
}
|
||||
|
||||
check_admin_referer( $nonce_action );
|
||||
|
||||
$posts = array_filter( (array) $post_id );
|
||||
|
||||
$deleted = 0;
|
||||
|
||||
foreach ( $posts as $post ) {
|
||||
$post = WPCF7_ContactForm::get_instance( $post );
|
||||
|
||||
if ( empty( $post ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'wpcf7_delete_contact_form', $post->id() ) ) {
|
||||
wp_die(
|
||||
esc_html( __( 'You are not allowed to delete this item.', 'contact-form-7' ) )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $post->delete() ) {
|
||||
wp_die(
|
||||
esc_html( __( 'Error in deleting.', 'contact-form-7' ) )
|
||||
);
|
||||
}
|
||||
|
||||
$deleted += 1;
|
||||
}
|
||||
|
||||
$query = array();
|
||||
|
||||
if ( ! empty( $deleted ) ) {
|
||||
$query['message'] = 'deleted';
|
||||
}
|
||||
|
||||
$redirect_to = add_query_arg( $query, menu_page_url( 'wpcf7', false ) );
|
||||
|
||||
wp_safe_redirect( $redirect_to );
|
||||
exit();
|
||||
}
|
||||
|
||||
$post = null;
|
||||
|
||||
if ( 'wpcf7-new' === $plugin_page ) {
|
||||
$post = WPCF7_ContactForm::get_template( array(
|
||||
'locale' => wpcf7_superglobal_get( 'locale', null ),
|
||||
) );
|
||||
} elseif ( $post_id = wpcf7_superglobal_get( 'post' ) ) {
|
||||
$post = WPCF7_ContactForm::get_instance( $post_id );
|
||||
}
|
||||
|
||||
$current_screen = get_current_screen();
|
||||
|
||||
$help_tabs = new WPCF7_Help_Tabs( $current_screen );
|
||||
|
||||
if ( $post and current_user_can( 'wpcf7_edit_contact_form', $post->id() ) ) {
|
||||
$help_tabs->set_help_tabs( 'edit' );
|
||||
} else {
|
||||
$help_tabs->set_help_tabs( 'list' );
|
||||
|
||||
if ( ! class_exists( 'WPCF7_Contact_Form_List_Table' ) ) {
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/class-contact-forms-list-table.php';
|
||||
}
|
||||
|
||||
add_filter(
|
||||
'manage_' . $current_screen->id . '_columns',
|
||||
array( 'WPCF7_Contact_Form_List_Table', 'define_columns' ),
|
||||
10, 0
|
||||
);
|
||||
|
||||
add_screen_option( 'per_page', array(
|
||||
'default' => 20,
|
||||
'option' => 'wpcf7_contact_forms_per_page',
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function wpcf7_admin_management_page() {
|
||||
if ( $post = wpcf7_get_current_contact_form() ) {
|
||||
$post_id = $post->initial() ? -1 : $post->id();
|
||||
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/editor.php';
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/edit-contact-form.php';
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
'validate' === wpcf7_current_action() and
|
||||
wpcf7_validate_configuration() and
|
||||
current_user_can( 'wpcf7_edit_contact_forms' )
|
||||
) {
|
||||
wpcf7_admin_bulk_validate_page();
|
||||
return;
|
||||
}
|
||||
|
||||
$list_table = new WPCF7_Contact_Form_List_Table();
|
||||
$list_table->prepare_items();
|
||||
|
||||
$formatter = new WPCF7_HTMLFormatter( array(
|
||||
'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array(
|
||||
'form' => array(
|
||||
'method' => true,
|
||||
),
|
||||
) ),
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'wrap',
|
||||
'id' => 'wpcf7-contact-form-list-table',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'h1', array(
|
||||
'class' => 'wp-heading-inline',
|
||||
) );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( __( 'Contact Forms', 'contact-form-7' ) )
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'h1' );
|
||||
|
||||
if ( current_user_can( 'wpcf7_edit_contact_forms' ) ) {
|
||||
$formatter->append_preformatted(
|
||||
wpcf7_link(
|
||||
menu_page_url( 'wpcf7-new', false ),
|
||||
__( 'Add Contact Form', 'contact-form-7' ),
|
||||
array( 'class' => 'page-title-action' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $search_keyword = wpcf7_superglobal_request( 's' ) ) {
|
||||
$formatter->append_start_tag( 'span', array(
|
||||
'class' => 'subtitle',
|
||||
) );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
sprintf(
|
||||
/* translators: %s: Search query. */
|
||||
__( 'Search results for: <strong>%s</strong>', 'contact-form-7' ),
|
||||
esc_html( $search_keyword )
|
||||
)
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'span' );
|
||||
}
|
||||
|
||||
$formatter->append_start_tag( 'hr', array(
|
||||
'class' => 'wp-header-end',
|
||||
) );
|
||||
|
||||
$formatter->call_user_func( static function () {
|
||||
do_action( 'wpcf7_admin_warnings',
|
||||
'wpcf7', wpcf7_current_action(), null
|
||||
);
|
||||
|
||||
wpcf7_welcome_panel();
|
||||
|
||||
do_action( 'wpcf7_admin_notices',
|
||||
'wpcf7', wpcf7_current_action(), null
|
||||
);
|
||||
} );
|
||||
|
||||
$formatter->append_start_tag( 'form', array(
|
||||
'method' => 'get',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'hidden',
|
||||
'name' => 'page',
|
||||
'value' => wpcf7_superglobal_request( 'page' ),
|
||||
) );
|
||||
|
||||
$formatter->call_user_func( static function () use ( $list_table ) {
|
||||
$list_table->search_box(
|
||||
__( 'Search Contact Forms', 'contact-form-7' ),
|
||||
'wpcf7-contact'
|
||||
);
|
||||
|
||||
$list_table->display();
|
||||
} );
|
||||
|
||||
$formatter->print();
|
||||
}
|
||||
|
||||
|
||||
function wpcf7_admin_add_new_page() {
|
||||
$post = wpcf7_get_current_contact_form();
|
||||
|
||||
if ( ! $post ) {
|
||||
$post = WPCF7_ContactForm::get_template();
|
||||
}
|
||||
|
||||
$post_id = -1;
|
||||
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/includes/editor.php';
|
||||
require_once WPCF7_PLUGIN_DIR . '/admin/edit-contact-form.php';
|
||||
}
|
||||
|
||||
|
||||
function wpcf7_load_integration_page() {
|
||||
do_action( 'wpcf7_admin_load',
|
||||
wpcf7_superglobal_get( 'page' ),
|
||||
wpcf7_current_action()
|
||||
);
|
||||
|
||||
$integration = WPCF7_Integration::get_instance();
|
||||
|
||||
if (
|
||||
$service_name = wpcf7_superglobal_request( 'service' ) and
|
||||
$integration->service_exists( $service_name )
|
||||
) {
|
||||
$service = $integration->get_service( $service_name );
|
||||
$service->load( wpcf7_current_action() );
|
||||
}
|
||||
|
||||
$help_tabs = new WPCF7_Help_Tabs( get_current_screen() );
|
||||
$help_tabs->set_help_tabs( 'integration' );
|
||||
}
|
||||
|
||||
|
||||
function wpcf7_admin_integration_page() {
|
||||
$integration = WPCF7_Integration::get_instance();
|
||||
|
||||
$service_name = wpcf7_superglobal_request( 'service' );
|
||||
$service = null;
|
||||
|
||||
if ( $service_name and $integration->service_exists( $service_name ) ) {
|
||||
$service = $integration->get_service( $service_name );
|
||||
}
|
||||
|
||||
$formatter = new WPCF7_HTMLFormatter( array(
|
||||
'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array(
|
||||
'form' => array(
|
||||
'action' => true,
|
||||
'method' => true,
|
||||
),
|
||||
) ),
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'wrap',
|
||||
'id' => 'wpcf7-integration',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'h1' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( __( 'Integration with External API', 'contact-form-7' ) )
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'h1' );
|
||||
|
||||
$formatter->append_start_tag( 'p' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
sprintf(
|
||||
/* translators: %s: URL to support page about integration with external APIs */
|
||||
__( 'You can expand the possibilities of your contact forms by integrating them with external services. For details, see <a href="%s">Integration with external APIs</a>.', 'contact-form-7' ),
|
||||
__( 'https://contactform7.com/integration-with-external-apis/', 'contact-form-7' )
|
||||
)
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'p' );
|
||||
|
||||
$formatter->call_user_func(
|
||||
static function () use ( $integration, $service, $service_name ) {
|
||||
do_action( 'wpcf7_admin_warnings',
|
||||
'wpcf7-integration', wpcf7_current_action(), $service
|
||||
);
|
||||
|
||||
do_action( 'wpcf7_admin_notices',
|
||||
'wpcf7-integration', wpcf7_current_action(), $service
|
||||
);
|
||||
|
||||
if ( $service ) {
|
||||
$message = wpcf7_superglobal_request( 'message' );
|
||||
$service->admin_notice( $message );
|
||||
|
||||
$integration->list_services( array(
|
||||
'include' => $service_name,
|
||||
) );
|
||||
} else {
|
||||
$integration->list_services();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$formatter->print();
|
||||
}
|
||||
|
||||
|
||||
add_action( 'wpcf7_admin_notices', 'wpcf7_admin_updated_message', 10, 3 );
|
||||
|
||||
function wpcf7_admin_updated_message( $page, $action, $object ) {
|
||||
if ( ! in_array( $page, array( 'wpcf7', 'wpcf7-new' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message_type = wpcf7_superglobal_request( 'message' );
|
||||
|
||||
if ( ! $message_type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notice_type = 'success';
|
||||
|
||||
if ( 'created' === $message_type ) {
|
||||
$message = __( 'Contact form created.', 'contact-form-7' );
|
||||
} elseif ( 'saved' === $message_type ) {
|
||||
$message = __( 'Contact form saved.', 'contact-form-7' );
|
||||
} elseif ( 'deleted' === $message_type ) {
|
||||
$message = __( 'Contact form deleted.', 'contact-form-7' );
|
||||
} elseif ( 'failed' === $message_type ) {
|
||||
$notice_type = 'error';
|
||||
$message = __( 'There was an error saving the contact form.', 'contact-form-7' );
|
||||
} elseif ( 'validated' === $message_type ) {
|
||||
$bulk_validate = WPCF7::get_option( 'bulk_validate', array() );
|
||||
$count_invalid = absint( $bulk_validate['count_invalid'] ?? 0 );
|
||||
|
||||
if ( $count_invalid ) {
|
||||
$notice_type = 'warning';
|
||||
|
||||
$message = sprintf(
|
||||
/* translators: %s: number of contact forms */
|
||||
_n(
|
||||
'Configuration validation completed. %s invalid contact form was found.',
|
||||
'Configuration validation completed. %s invalid contact forms were found.',
|
||||
$count_invalid, 'contact-form-7'
|
||||
),
|
||||
number_format_i18n( $count_invalid )
|
||||
);
|
||||
} else {
|
||||
$message = __( 'Configuration validation completed. No invalid contact form was found.', 'contact-form-7' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $message ) ) {
|
||||
wp_admin_notice(
|
||||
$message,
|
||||
array( 'type' => $notice_type )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
add_filter( 'plugin_action_links', 'wpcf7_plugin_action_links', 10, 2 );
|
||||
|
||||
function wpcf7_plugin_action_links( $links, $file ) {
|
||||
if ( WPCF7_PLUGIN_BASENAME !== $file ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'wpcf7_read_contact_forms' ) ) {
|
||||
return $links;
|
||||
}
|
||||
|
||||
$settings_link = wpcf7_link(
|
||||
menu_page_url( 'wpcf7', false ),
|
||||
__( 'Settings', 'contact-form-7' )
|
||||
);
|
||||
|
||||
array_unshift( $links, $settings_link );
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
|
||||
add_action( 'wpcf7_admin_warnings', 'wpcf7_old_wp_version_error', 10, 3 );
|
||||
|
||||
function wpcf7_old_wp_version_error( $page, $action, $object ) {
|
||||
$wp_version = get_bloginfo( 'version' );
|
||||
|
||||
if ( version_compare( $wp_version, WPCF7_REQUIRED_WP_VERSION, '<' ) ) {
|
||||
wp_admin_notice(
|
||||
sprintf(
|
||||
/* translators: 1: version of Contact Form 7, 2: version of WordPress, 3: URL */
|
||||
__( '<strong>Contact Form 7 %1$s requires WordPress %2$s or higher.</strong> Please <a href="%3$s">update WordPress</a> first.', 'contact-form-7' ),
|
||||
WPCF7_VERSION,
|
||||
WPCF7_REQUIRED_WP_VERSION,
|
||||
admin_url( 'update-core.php' )
|
||||
),
|
||||
array( 'type' => 'warning' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
add_action( 'wpcf7_admin_warnings', 'wpcf7_not_allowed_to_edit', 10, 3 );
|
||||
|
||||
function wpcf7_not_allowed_to_edit( $page, $action, $object ) {
|
||||
if ( $object instanceof WPCF7_ContactForm ) {
|
||||
$contact_form = $object;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'wpcf7_edit_contact_form', $contact_form->id() ) ) {
|
||||
wp_admin_notice(
|
||||
__( 'You are not allowed to edit this contact form.', 'contact-form-7' ),
|
||||
array( 'type' => 'warning' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
add_action( 'wpcf7_admin_warnings', 'wpcf7_ctct_deprecated_warning', 10, 3 );
|
||||
|
||||
function wpcf7_ctct_deprecated_warning( $page, $action, $object ) {
|
||||
$service = WPCF7_ConstantContact::get_instance();
|
||||
|
||||
if ( $service->is_active() ) {
|
||||
wp_admin_notice(
|
||||
__( 'Contact Form 7 has completed the <a href="https://contactform7.com/2025/01/08/complete-removal-of-constant-contact-integration/">removal of the Constant Contact integration</a>. We recommend <a href="https://contactform7.com/sendinblue-integration/">Brevo</a> as an alternative.', 'contact-form-7' ),
|
||||
array( 'type' => 'warning' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
add_action( 'wpcf7_admin_warnings', 'wpcf7_captcha_future_warning', 10, 3 );
|
||||
|
||||
function wpcf7_captcha_future_warning( $page, $action, $object ) {
|
||||
$service = WPCF7_RECAPTCHA::get_instance();
|
||||
|
||||
if ( $service->is_active() ) {
|
||||
wp_admin_notice(
|
||||
__( '<strong>Attention reCAPTCHA users:</strong> Google attempts to make all reCAPTCHA users migrate to reCAPTCHA Enterprise, meaning Google charges you for API calls exceeding the free tier. Contact Form 7 supports <a href="https://contactform7.com/turnstile-integration/">Cloudflare Turnstile</a>, and we recommend it unless you have reasons to use reCAPTCHA.', 'contact-form-7' ),
|
||||
array( 'type' => 'warning' )
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
<?php
|
||||
|
||||
// don't load directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
die( '-1' );
|
||||
}
|
||||
|
||||
$save_button = sprintf(
|
||||
'<input %s />',
|
||||
wpcf7_format_atts( array(
|
||||
'type' => 'submit',
|
||||
'class' => 'button-primary',
|
||||
'name' => 'wpcf7-save',
|
||||
'value' => __( 'Save', 'contact-form-7' ),
|
||||
) )
|
||||
);
|
||||
|
||||
$formatter = new WPCF7_HTMLFormatter( array(
|
||||
'allowed_html' => array_merge( wpcf7_kses_allowed_html(), array(
|
||||
'form' => array(
|
||||
'method' => true,
|
||||
'action' => true,
|
||||
'id' => true,
|
||||
'class' => true,
|
||||
'disabled' => true,
|
||||
),
|
||||
) ),
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'wrap',
|
||||
'id' => 'wpcf7-contact-form-editor',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'h1', array(
|
||||
'class' => 'wp-heading-inline',
|
||||
) );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( $post->initial()
|
||||
? __( 'Add Contact Form', 'contact-form-7' )
|
||||
: __( 'Edit Contact Form', 'contact-form-7' )
|
||||
)
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'h1' );
|
||||
|
||||
if ( ! $post->initial() and current_user_can( 'wpcf7_edit_contact_forms' ) ) {
|
||||
$formatter->append_whitespace();
|
||||
|
||||
$formatter->append_preformatted(
|
||||
wpcf7_link(
|
||||
menu_page_url( 'wpcf7-new', false ),
|
||||
__( 'Add Contact Form', 'contact-form-7' ),
|
||||
array( 'class' => 'page-title-action' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$formatter->append_start_tag( 'hr', array(
|
||||
'class' => 'wp-header-end',
|
||||
) );
|
||||
|
||||
$formatter->call_user_func( static function () use ( $post ) {
|
||||
do_action( 'wpcf7_admin_warnings',
|
||||
$post->initial() ? 'wpcf7-new' : 'wpcf7',
|
||||
wpcf7_current_action(),
|
||||
$post
|
||||
);
|
||||
|
||||
do_action( 'wpcf7_admin_notices',
|
||||
$post->initial() ? 'wpcf7-new' : 'wpcf7',
|
||||
wpcf7_current_action(),
|
||||
$post
|
||||
);
|
||||
} );
|
||||
|
||||
if ( $post ) {
|
||||
$formatter->append_start_tag( 'form', array(
|
||||
'method' => 'post',
|
||||
'action' => esc_url( add_query_arg(
|
||||
array( 'post' => $post_id ),
|
||||
menu_page_url( 'wpcf7', false )
|
||||
) ),
|
||||
'id' => 'wpcf7-admin-form-element',
|
||||
'disabled' => ! current_user_can( 'wpcf7_edit_contact_form', $post_id ),
|
||||
) );
|
||||
|
||||
if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) {
|
||||
$formatter->call_user_func( static function () use ( $post_id ) {
|
||||
wp_nonce_field( 'wpcf7-save-contact-form_' . $post_id );
|
||||
} );
|
||||
}
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'hidden',
|
||||
'id' => 'post_ID',
|
||||
'name' => 'post_ID',
|
||||
'value' => (int) $post_id,
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'hidden',
|
||||
'id' => 'wpcf7-locale',
|
||||
'name' => 'wpcf7-locale',
|
||||
'value' => $post->locale(),
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'hidden',
|
||||
'id' => 'hiddenaction',
|
||||
'name' => 'action',
|
||||
'value' => 'save',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'hidden',
|
||||
'id' => 'active-tab',
|
||||
'name' => 'active-tab',
|
||||
'value' => wpcf7_superglobal_get( 'active-tab' ),
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'poststuff',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'post-body',
|
||||
'class' => 'metabox-holder columns-2 wp-clearfix',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'post-body-content',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'titlediv',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'titlewrap',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'text',
|
||||
'name' => 'post_title',
|
||||
'value' => $post->initial() ? '' : $post->title(),
|
||||
'id' => 'title',
|
||||
'spellcheck' => 'true',
|
||||
'autocomplete' => 'off',
|
||||
'disabled' => ! current_user_can( 'wpcf7_edit_contact_form', $post_id ),
|
||||
'placeholder' => __( 'Enter title here', 'contact-form-7' ),
|
||||
'aria-label' => __( 'Enter title here', 'contact-form-7' ),
|
||||
) );
|
||||
|
||||
$formatter->end_tag( 'div' ); // #titlewrap
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'inside',
|
||||
) );
|
||||
|
||||
if ( ! $post->initial() ) {
|
||||
if ( $shortcode = $post->shortcode() ) {
|
||||
$formatter->append_start_tag( 'p', array(
|
||||
'class' => 'description',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'label', array(
|
||||
'for' => 'wpcf7-shortcode',
|
||||
) );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( __( 'Copy this shortcode and paste it into your post, page, or text widget content:', 'contact-form-7' ) )
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'label' );
|
||||
|
||||
$formatter->append_whitespace();
|
||||
|
||||
$formatter->append_start_tag( 'span', array(
|
||||
'class' => 'shortcode wp-ui-highlight',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'text',
|
||||
'id' => 'wpcf7-shortcode',
|
||||
'readonly' => true,
|
||||
'class' => 'large-text code selectable',
|
||||
'value' => $shortcode,
|
||||
) );
|
||||
|
||||
$formatter->end_tag( 'p' );
|
||||
}
|
||||
|
||||
if ( $shortcode = $post->shortcode( array( 'use_old_format' => true ) ) ) {
|
||||
$formatter->append_start_tag( 'p', array(
|
||||
'class' => 'description',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'label', array(
|
||||
'for' => 'wpcf7-shortcode-old',
|
||||
) );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( __( 'You can also use this old-style shortcode:', 'contact-form-7' ) )
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'label' );
|
||||
|
||||
$formatter->append_whitespace();
|
||||
|
||||
$formatter->append_start_tag( 'span', array(
|
||||
'class' => 'shortcode old',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'text',
|
||||
'id' => 'wpcf7-shortcode-old',
|
||||
'readonly' => true,
|
||||
'class' => 'large-text code selectable',
|
||||
'value' => $shortcode,
|
||||
) );
|
||||
|
||||
$formatter->end_tag( 'p' );
|
||||
}
|
||||
}
|
||||
|
||||
$formatter->end_tag( 'div' ); // .inside
|
||||
$formatter->end_tag( 'div' ); // #titlediv
|
||||
$formatter->end_tag( 'div' ); // #post-body-content
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'postbox-container-1',
|
||||
'class' => 'postbox-container',
|
||||
) );
|
||||
|
||||
if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) {
|
||||
$formatter->append_start_tag( 'section', array(
|
||||
'id' => 'submitdiv',
|
||||
'class' => 'postbox',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'h2' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( __( 'Status', 'contact-form-7' ) )
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'h2' );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'inside',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'submitbox',
|
||||
'id' => 'submitpost',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'minor-publishing-actions',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'hidden',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'submit',
|
||||
'class' => 'button-primary',
|
||||
'name' => 'wpcf7-save',
|
||||
'value' => __( 'Save', 'contact-form-7' ),
|
||||
) );
|
||||
|
||||
$formatter->end_tag( 'div' ); // .hidden
|
||||
|
||||
if ( ! $post->initial() ) {
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'submit',
|
||||
'name' => 'wpcf7-copy',
|
||||
'class' => 'copy button',
|
||||
'value' => __( 'Duplicate', 'contact-form-7' ),
|
||||
) );
|
||||
}
|
||||
|
||||
$formatter->end_tag( 'div' ); // #minor-publishing-actions
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'misc-publishing-actions',
|
||||
) );
|
||||
|
||||
$formatter->call_user_func( static function () use ( $post_id ) {
|
||||
do_action( 'wpcf7_admin_misc_pub_section', $post_id );
|
||||
} );
|
||||
|
||||
$formatter->end_tag( 'div' ); // #misc-publishing-actions
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'major-publishing-actions',
|
||||
) );
|
||||
|
||||
if ( ! $post->initial() ) {
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'delete-action',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'input', array(
|
||||
'type' => 'submit',
|
||||
'name' => 'wpcf7-delete',
|
||||
'class' => 'delete submitdelete',
|
||||
'value' => __( 'Delete', 'contact-form-7' ),
|
||||
) );
|
||||
|
||||
$formatter->end_tag( 'div' ); // #delete-action
|
||||
}
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'publishing-action',
|
||||
) );
|
||||
|
||||
$formatter->append_preformatted( '<span class="spinner"></span>' );
|
||||
$formatter->append_preformatted( $save_button );
|
||||
|
||||
$formatter->end_tag( 'div' ); // #publishing-action
|
||||
|
||||
$formatter->append_preformatted( '<div class="clear"></div>' );
|
||||
|
||||
$formatter->end_tag( 'div' ); // #major-publishing-actions
|
||||
$formatter->end_tag( 'div' ); // #submitpost
|
||||
$formatter->end_tag( 'div' ); // .inside
|
||||
$formatter->end_tag( 'section' ); // #submitdiv
|
||||
}
|
||||
|
||||
$formatter->append_start_tag( 'section', array(
|
||||
'id' => 'informationdiv',
|
||||
'class' => 'postbox',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'h2' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( __( 'Do you need help?', 'contact-form-7' ) )
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'h2' );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'class' => 'inside',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'p' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
esc_html( __( 'Here are some available options to help solve your problems.', 'contact-form-7' ) )
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'p' );
|
||||
|
||||
$formatter->append_start_tag( 'ol' );
|
||||
|
||||
$formatter->append_start_tag( 'li' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
sprintf(
|
||||
/* translators: 1: URL to FAQ, 2: URL to docs */
|
||||
'<a href="%1$s">FAQ</a> and <a href="%2$s">docs</a>',
|
||||
__( 'https://contactform7.com/faq/', 'contact-form-7' ),
|
||||
__( 'https://contactform7.com/docs/', 'contact-form-7' )
|
||||
)
|
||||
);
|
||||
|
||||
$formatter->append_start_tag( 'li' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
wpcf7_link(
|
||||
__( 'https://wordpress.org/support/plugin/contact-form-7/', 'contact-form-7' ),
|
||||
__( 'Support forums', 'contact-form-7' )
|
||||
)
|
||||
);
|
||||
|
||||
$formatter->append_start_tag( 'li' );
|
||||
|
||||
$formatter->append_preformatted(
|
||||
wpcf7_link(
|
||||
__( 'https://contactform7.com/custom-development/', 'contact-form-7' ),
|
||||
__( 'Professional services', 'contact-form-7' )
|
||||
)
|
||||
);
|
||||
|
||||
$formatter->end_tag( 'ol' );
|
||||
$formatter->end_tag( 'div' ); // .inside
|
||||
$formatter->end_tag( 'section' ); // #informationdiv
|
||||
$formatter->end_tag( 'div' ); // #postbox-container-1
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'postbox-container-2',
|
||||
'class' => 'postbox-container',
|
||||
) );
|
||||
|
||||
$formatter->append_start_tag( 'div', array(
|
||||
'id' => 'contact-form-editor',
|
||||
) );
|
||||
|
||||
$formatter->call_user_func( static function () use ( $post, $post_id ) {
|
||||
$editor = new WPCF7_Editor( $post );
|
||||
$panels = array();
|
||||
|
||||
if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) {
|
||||
$panels = array(
|
||||
'form-panel' => array(
|
||||
'title' => __( 'Form', 'contact-form-7' ),
|
||||
'callback' => 'wpcf7_editor_panel_form',
|
||||
),
|
||||
'mail-panel' => array(
|
||||
'title' => __( 'Mail', 'contact-form-7' ),
|
||||
'callback' => 'wpcf7_editor_panel_mail',
|
||||
),
|
||||
'messages-panel' => array(
|
||||
'title' => __( 'Messages', 'contact-form-7' ),
|
||||
'callback' => 'wpcf7_editor_panel_messages',
|
||||
),
|
||||
);
|
||||
|
||||
$additional_settings = $post->prop( 'additional_settings' );
|
||||
|
||||
if ( ! is_scalar( $additional_settings ) ) {
|
||||
$additional_settings = '';
|
||||
}
|
||||
|
||||
$additional_settings = trim( $additional_settings );
|
||||
$additional_settings = explode( "\n", $additional_settings );
|
||||
$additional_settings = array_filter( $additional_settings );
|
||||
$additional_settings = count( $additional_settings );
|
||||
|
||||
$panels['additional-settings-panel'] = array(
|
||||
'title' => $additional_settings
|
||||
? sprintf(
|
||||
/* translators: %d: number of additional settings */
|
||||
__( 'Additional Settings (%d)', 'contact-form-7' ),
|
||||
$additional_settings
|
||||
)
|
||||
: __( 'Additional Settings', 'contact-form-7' ),
|
||||
'callback' => 'wpcf7_editor_panel_additional_settings',
|
||||
);
|
||||
}
|
||||
|
||||
$panels = apply_filters( 'wpcf7_editor_panels', $panels );
|
||||
|
||||
foreach ( $panels as $id => $panel ) {
|
||||
$editor->add_panel( $id, $panel['title'], $panel['callback'] );
|
||||
}
|
||||
|
||||
$editor->display();
|
||||
} );
|
||||
|
||||
$formatter->end_tag( 'div' ); // #contact-form-editor
|
||||
|
||||
if ( current_user_can( 'wpcf7_edit_contact_form', $post_id ) ) {
|
||||
$formatter->append_start_tag( 'p', array(
|
||||
'class' => 'submit',
|
||||
) );
|
||||
|
||||
$formatter->append_preformatted( $save_button );
|
||||
|
||||
$formatter->end_tag( 'p' );
|
||||
}
|
||||
|
||||
$formatter->end_tag( 'div' ); // #postbox-container-2
|
||||
$formatter->end_tag( 'div' ); // #post-body
|
||||
|
||||
$formatter->append_preformatted( '<br class="clear" />' );
|
||||
|
||||
$formatter->end_tag( 'div' ); // #poststuff
|
||||
$formatter->end_tag( 'form' );
|
||||
}
|
||||
|
||||
$formatter->end_tag( 'div' ); // .wrap
|
||||
|
||||
$formatter->print();
|
||||
|
||||
$tag_generator = WPCF7_TagGenerator::get_instance();
|
||||
$tag_generator->print_panels( $post );
|
||||
|
||||
do_action( 'wpcf7_admin_footer', $post );
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
function wpcf7_current_action() {
|
||||
foreach ( array( 'action', 'action2' ) as $var ) {
|
||||
$action = wpcf7_superglobal_request( $var, null );
|
||||
|
||||
if ( isset( $action ) and -1 !== $action ) {
|
||||
return $action;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function wpcf7_admin_has_edit_cap() {
|
||||
return current_user_can( 'wpcf7_edit_contact_forms' );
|
||||
}
|
||||
|
||||
function wpcf7_add_tag_generator( $name, $title, $elm_id, $callback, $options = array() ) {
|
||||
$tag_generator = WPCF7_TagGenerator::get_instance();
|
||||
return $tag_generator->add( $name, $title, $callback, $options );
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user