Removed pre-installed packages

This commit is contained in:
2026-03-06 10:53:34 +01:00
parent b7b6cd896d
commit e63fa40b87
5461 changed files with 0 additions and 749635 deletions

View File

@@ -1,469 +0,0 @@
/**
* 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;
}

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 267 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,383 +0,0 @@
/**
* 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">&times;</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);
}
})();

View File

@@ -1,56 +0,0 @@
/**
* 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);
}
}
});
})();

View File

@@ -1,55 +0,0 @@
<?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 );
}
}

View File

@@ -1,211 +0,0 @@
<?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() {}
}

View File

@@ -1,260 +0,0 @@
<?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;
}
}
}

View File

@@ -1,325 +0,0 @@
<?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' ),
);
}
}
}

View File

@@ -1,106 +0,0 @@
<?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;
}
}

View File

@@ -1,315 +0,0 @@
<?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
}
}

View File

@@ -1,453 +0,0 @@
<?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
}
}

View File

@@ -1,404 +0,0 @@
<?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() {}
}

View File

@@ -1,375 +0,0 @@
<?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() {}
}

View File

@@ -1,178 +0,0 @@
<?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() {}
}

View File

@@ -1,212 +0,0 @@
<?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() {}
}

View File

@@ -1,203 +0,0 @@
<?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 );
}
}

View File

@@ -1,17 +0,0 @@
<?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();

View File

@@ -1,215 +0,0 @@
<?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
);
}
}

View File

@@ -1,69 +0,0 @@
<?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 );
}
);
}
}

View File

@@ -1,41 +0,0 @@
<?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 );
}
}

View File

@@ -1,124 +0,0 @@
<?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();
}
}

View File

@@ -1,196 +0,0 @@
<?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 );
}
}
}

View File

@@ -1,191 +0,0 @@
<?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',
)
);
}
}

View File

@@ -1,42 +0,0 @@
<?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;
}

View File

@@ -1,24 +0,0 @@
<?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;
}

View File

@@ -1,54 +0,0 @@
<?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();
}

View File

@@ -1,67 +0,0 @@
<?php
/**
* Mailchimp API key importer.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Api_Key_Importer {
public static function detect() {
$mc4wp = get_option( 'mc4wp' );
if ( ! empty( $mc4wp['api_key'] ) ) {
return $mc4wp['api_key'];
}
$yikes = get_option( 'yikes-mc-api-key' );
if ( ! empty( $yikes ) ) {
return $yikes;
}
$easy_forms = get_option( 'yikes-easy-mailchimp-extender-api-key' );
if ( ! empty( $easy_forms ) ) {
return $easy_forms;
}
$woo_mc = get_option( 'mailchimp-woocommerce' );
if ( ! empty( $woo_mc['mailchimp_api_key'] ) ) {
return $woo_mc['mailchimp_api_key'];
}
$mc4wp_top_bar = get_option( 'mc4wp_top_bar' );
if ( ! empty( $mc4wp_top_bar['api_key'] ) ) {
return $mc4wp_top_bar['api_key'];
}
return false;
}
public static function import_to_newest_form() {
$existing_key = self::detect();
if ( ! $existing_key ) {
return false;
}
$form_id = Cmatic_Utils::get_newest_form_id();
if ( ! $form_id ) {
return false;
}
$option_name = 'cf7_mch_' . $form_id;
$cf7_mch = get_option( $option_name, array() );
if ( ! empty( $cf7_mch['api'] ) ) {
return false;
}
$cf7_mch['api'] = $existing_key;
update_option( $option_name, $cf7_mch );
return true;
}
}

View File

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

View File

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

View File

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

View File

@@ -1,75 +0,0 @@
<?php
/**
* Cron job handler.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Cron {
private const DAILY_HOOK = 'cmatic_daily_cron';
private const DAILY_TIME = '03:00:00';
public static function init( string $plugin_file ): void {
add_action( 'init', array( __CLASS__, 'schedule' ) );
add_action( self::DAILY_HOOK, array( __CLASS__, 'run_daily_job' ) );
register_deactivation_hook( $plugin_file, array( __CLASS__, 'unschedule' ) );
}
public static function schedule(): void {
if ( wp_next_scheduled( self::DAILY_HOOK ) ) {
return;
}
wp_schedule_event( strtotime( self::DAILY_TIME ), 'daily', self::DAILY_HOOK );
}
public static function unschedule(): void {
// Idempotent: wp_clear_scheduled_hook is safe if hook doesn't exist.
wp_clear_scheduled_hook( self::DAILY_HOOK );
}
public static function run_daily_job(): void {
self::disable_all_logging();
}
private static function disable_all_logging(): int {
global $wpdb;
$option_names = $wpdb->get_col(
$wpdb->prepare(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
'cf7_mch_%'
)
);
$updated = 0;
foreach ( $option_names as $option_name ) {
$config = get_option( $option_name );
if ( is_array( $config ) && isset( $config['logfileEnabled'] ) ) {
unset( $config['logfileEnabled'] );
update_option( $option_name, $config );
++$updated;
}
}
return $updated;
}
public static function is_scheduled(): bool {
return (bool) wp_next_scheduled( self::DAILY_HOOK );
}
public static function get_next_run() {
return wp_next_scheduled( self::DAILY_HOOK );
}
}

View File

@@ -1,37 +0,0 @@
<?php
/**
* CF7 form tag utilities.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
final class Cmatic_Form_Tags {
public static function get_tags_with_types( $contact_form ): array {
if ( ! $contact_form ) {
return array();
}
$mail_tags = $contact_form->collect_mail_tags();
$all_tags = $contact_form->scan_form_tags();
$result = array();
foreach ( $all_tags as $tag ) {
if ( ! empty( $tag->name ) && in_array( $tag->name, $mail_tags, true ) ) {
$result[] = array(
'name' => $tag->name,
'basetype' => $tag->basetype,
);
}
}
return $result;
}
private function __construct() {}
}

View File

@@ -1,26 +0,0 @@
<?php
/**
* Lifecycle telemetry signals.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Lifecycle_Signal {
public function send_activation() {
if ( class_exists( 'Cmatic\Metrics\Core\Sync' ) ) {
\Cmatic\Metrics\Core\Sync::send_lifecycle_signal( 'activation' );
}
}
public function send_deactivation() {
if ( class_exists( 'Cmatic\Metrics\Core\Sync' ) ) {
\Cmatic\Metrics\Core\Sync::send_lifecycle_signal( 'deactivation' );
}
}
}

View File

@@ -1,105 +0,0 @@
<?php
/**
* Options repository.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Options_Repository implements Cmatic_Options_Interface {
private const OPTION_NAME = 'cmatic';
private $cache = null;
public function get_all() {
if ( null === $this->cache ) {
$this->cache = get_option( self::OPTION_NAME, array() );
if ( ! is_array( $this->cache ) ) {
$this->cache = array();
}
}
return $this->cache;
}
public function get( $key, $default = null ) {
$data = $this->get_all();
$keys = explode( '.', $key );
$value = $data;
foreach ( $keys as $k ) {
if ( ! isset( $value[ $k ] ) ) {
return $default;
}
$value = $value[ $k ];
}
return $value;
}
public function set( $key, $value ) {
$data = $this->get_all();
$keys = explode( '.', $key );
$ref = &$data;
foreach ( $keys as $i => $k ) {
if ( count( $keys ) - 1 === $i ) {
$ref[ $k ] = $value;
} else {
if ( ! isset( $ref[ $k ] ) || ! is_array( $ref[ $k ] ) ) {
$ref[ $k ] = array();
}
$ref = &$ref[ $k ];
}
}
$this->cache = $data;
return update_option( self::OPTION_NAME, $data );
}
public function save( $data ) {
$this->cache = $data;
return update_option( self::OPTION_NAME, $data );
}
public function get_legacy( $name, $default = null ) {
return get_option( $name, $default );
}
public function delete_legacy( $name ) {
return delete_option( $name );
}
public function clear_cache() {
$this->cache = null;
}
// =========================================================================
// STATIC API (for global access without instantiation)
// =========================================================================
private static $instance = null;
public static function instance(): self {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
public static function get_option( string $key, $default = null ) {
return self::instance()->get( $key, $default );
}
public static function set_option( string $key, $value ): bool {
return self::instance()->set( $key, $value );
}
public static function get_all_options(): array {
return self::instance()->get_all();
}
}

View File

@@ -1,84 +0,0 @@
<?php
/**
* Pro plugin status detection.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Pro_Status {
private const PRO_PLUGIN_FILE = 'chimpmatic/chimpmatic.php';
private $options;
public function __construct( Cmatic_Options_Repository $options ) {
$this->options = $options;
}
public function update() {
$status = array(
'installed' => $this->is_installed(),
'activated' => $this->is_activated(),
'version' => $this->get_version(),
'licensed' => $this->is_licensed(),
'license_expires' => $this->get_license_expiry(),
);
$current = $this->options->get( 'install.pro', array() );
if ( $current !== $status ) {
$this->options->set( 'install.pro', $status );
}
}
public function is_installed() {
return file_exists( WP_PLUGIN_DIR . '/' . self::PRO_PLUGIN_FILE );
}
public function is_activated() {
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
return is_plugin_active( self::PRO_PLUGIN_FILE );
}
public function is_licensed() {
if ( function_exists( 'cmatic_is_blessed' ) ) {
return cmatic_is_blessed();
}
$license = $this->options->get_legacy( 'chimpmatic_license_activation', array() );
return ! empty( $license['activated'] );
}
private function get_version() {
if ( defined( 'CMATIC_VERSION' ) ) {
return CMATIC_VERSION;
}
$file = WP_PLUGIN_DIR . '/' . self::PRO_PLUGIN_FILE;
if ( ! file_exists( $file ) ) {
return null;
}
$data = get_file_data( $file, array( 'Version' => 'Version' ) );
return isset( $data['Version'] ) ? $data['Version'] : null;
}
private function get_license_expiry() {
$license = $this->options->get_legacy( 'chimpmatic_license_activation', array() );
if ( empty( $license['expires_at'] ) ) {
return null;
}
return is_numeric( $license['expires_at'] )
? (int) $license['expires_at']
: (int) strtotime( (string) $license['expires_at'] );
}
}

View File

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

View File

@@ -1,68 +0,0 @@
<?php
/**
* Post-activation redirect handler.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Redirect {
private $options;
public function __construct( Cmatic_Options_Repository $options ) {
$this->options = $options;
}
public function schedule() {
if ( ! $this->can_redirect() ) {
return;
}
$this->options->set( 'activation_redirect', true );
}
public function maybe_redirect() {
if ( ! $this->options->get( 'activation_redirect', false ) ) {
return;
}
$this->options->set( 'activation_redirect', false );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['activate-multi'] ) ) {
return;
}
$form_id = absint( Cmatic_Utils::get_newest_form_id() ?? 0 );
$url = admin_url( 'admin.php?page=wpcf7&post=' . $form_id . '&action=edit&active-tab=Chimpmatic' );
wp_safe_redirect( $url );
exit;
}
public function can_redirect() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['activate-multi'] ) ) {
return false;
}
if ( is_network_admin() ) {
return false;
}
if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
return false;
}
if ( defined( 'WP_CLI' ) && WP_CLI ) {
return false;
}
return true;
}
}

View File

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

View File

@@ -1,45 +0,0 @@
<?php
/**
* Email extraction handler.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Email_Extractor {
private const TAG_PATTERN = '/\[\s*([a-zA-Z_][0-9a-zA-Z:._-]*)\s*\]/';
public static function extract( array $cf7_mch, array $posted_data ): string {
if ( empty( $cf7_mch['merge_fields'] ) || ! is_array( $cf7_mch['merge_fields'] ) ) {
return '';
}
foreach ( $cf7_mch['merge_fields'] as $idx => $merge_field ) {
if ( ( $merge_field['tag'] ?? '' ) === 'EMAIL' ) {
$field_key = 'field' . ( $idx + 3 );
if ( ! empty( $cf7_mch[ $field_key ] ) ) {
return self::replace_tags( $cf7_mch[ $field_key ], $posted_data );
}
break;
}
}
return '';
}
public static function replace_tags( string $subject, array $posted_data ): string {
if ( preg_match( self::TAG_PATTERN, $subject, $matches ) > 0 ) {
if ( isset( $posted_data[ $matches[1] ] ) ) {
$submitted = $posted_data[ $matches[1] ];
return is_array( $submitted ) ? implode( ', ', $submitted ) : $submitted;
}
return $matches[0];
}
return $subject;
}
}

View File

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

View File

@@ -1,53 +0,0 @@
<?php
/**
* Merge variables builder.
*
* @package contact-form-7-mailchimp-extension
* @author renzo.johnson@gmail.com
* @copyright 2014-2026 https://renzojohnson.com
* @license GPL-3.0+
*/
defined( 'ABSPATH' ) || exit;
class Cmatic_Merge_Vars_Builder {
public static function build( array $cf7_mch, array $posted_data ): array {
$merge_vars = array();
if ( empty( $cf7_mch['merge_fields'] ) || ! is_array( $cf7_mch['merge_fields'] ) ) {
return $merge_vars;
}
$field_index = 3;
$max_index = CMATIC_LITE_FIELDS + 2;
foreach ( $cf7_mch['merge_fields'] as $merge_field ) {
$field_key = 'field' . $field_index;
$merge_tag = $merge_field['tag'] ?? '';
if ( ! empty( $cf7_mch[ $field_key ] ) && ! empty( $merge_tag ) ) {
$value = Cmatic_Email_Extractor::replace_tags( $cf7_mch[ $field_key ], $posted_data );
if ( ! empty( $value ) ) {
$merge_vars[ $merge_tag ] = $value;
}
}
++$field_index;
if ( $field_index > $max_index ) {
break;
}
}
return self::filter_empty( $merge_vars );
}
private static function filter_empty( array $merge_vars ): array {
return array_filter(
$merge_vars,
function ( $value ) {
return ! empty( $value ) || 0 === $value || '0' === $value;
}
);
}
}

View File

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

View File

@@ -1,39 +0,0 @@
<?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';
}
}

View File

@@ -1,146 +0,0 @@
<?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;
}
}

View File

@@ -1,51 +0,0 @@
<?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(),
);
}
}

View File

@@ -1,104 +0,0 @@
<?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;
}
}

View File

@@ -1,159 +0,0 @@
<?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;
}
}

View File

@@ -1,94 +0,0 @@
<?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' );
}
}

View File

@@ -1,172 +0,0 @@
<?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 !== '' );
}
}

View File

@@ -1,426 +0,0 @@
<?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 );
}
}

View File

@@ -1,32 +0,0 @@
<?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 ),
),
);
}
}

View File

@@ -1,90 +0,0 @@
<?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;
}
}

View File

@@ -1,36 +0,0 @@
<?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,
);
}
}

View File

@@ -1,140 +0,0 @@
<?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 );
}
}

View File

@@ -1,135 +0,0 @@
<?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' ),
);
}
}

View File

@@ -1,90 +0,0 @@
<?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 : '';
}
}

View File

@@ -1,116 +0,0 @@
<?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 );
}
}

View File

@@ -1,50 +0,0 @@
<?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;
}
}

View File

@@ -1,184 +0,0 @@
<?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() );
}
}

View File

@@ -1,151 +0,0 @@
<?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 );
}
}

View File

@@ -1,182 +0,0 @@
<?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,
)
);
}
}

View File

@@ -1,82 +0,0 @@
<?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 );
}
}

View File

@@ -1,38 +0,0 @@
<?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;
}
}

View File

@@ -1,13 +0,0 @@
<?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;
}
} );

View File

@@ -1,441 +0,0 @@
<?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' => '&nbsp;&nbsp;' . 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
}
}

View File

@@ -1,100 +0,0 @@
<?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
}
}

View File

@@ -1,80 +0,0 @@
<?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 );
}
}

View File

@@ -1,94 +0,0 @@
<?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 );
}
}

View File

@@ -1,94 +0,0 @@
<?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;
}
}

View File

@@ -1,67 +0,0 @@
<?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;
}
}

View File

@@ -1,259 +0,0 @@
<?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() {}
}

View File

@@ -1,108 +0,0 @@
<?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 );
}
}

View File

@@ -1,148 +0,0 @@
<?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();
}
}

View File

@@ -1,164 +0,0 @@
<?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">&times;</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(),
);
}
}
}

View File

@@ -1,290 +0,0 @@
<?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;
}
}

View File

@@ -1,118 +0,0 @@
<?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 );
}
}

View File

@@ -1,83 +0,0 @@
<?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>';
}
}

View File

@@ -1,104 +0,0 @@
<?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() {}
}

View File

@@ -1,130 +0,0 @@
<?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() {}
}

View File

@@ -1,97 +0,0 @@
<?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' ),
)
);
}
}
}

View File

@@ -1,48 +0,0 @@
<?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;
}
}

View File

@@ -1,97 +0,0 @@
<?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';
}
}
}

View File

@@ -1,42 +0,0 @@
<?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;
}
}

View File

@@ -1,86 +0,0 @@
<?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 ) ) ) );
}
}

View File

@@ -1,251 +0,0 @@
<?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();
}
}

View File

@@ -1,89 +0,0 @@
<?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' );
}
}

View File

@@ -1,9 +0,0 @@
== 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.

View File

@@ -1,355 +0,0 @@
Chimpmatic Lite
Copyright (C) 20102025 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.

View File

@@ -1,225 +0,0 @@
=== 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)

View File

@@ -1,29 +0,0 @@
<?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_%' ) );

View File

@@ -1,759 +0,0 @@
<?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' )
);
}
}

View File

@@ -1,484 +0,0 @@
<?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 );

View File

@@ -1,22 +0,0 @@
<?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 );
}

View File

@@ -1,241 +0,0 @@
<?php
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
}
class WPCF7_Contact_Form_List_Table extends WP_List_Table {
public static function define_columns() {
$columns = array(
'cb' => '<input type="checkbox" />',
'title' => __( 'Title', 'contact-form-7' ),
'shortcode' => __( 'Shortcode', 'contact-form-7' ),
'author' => __( 'Author', 'contact-form-7' ),
'date' => __( 'Date', 'contact-form-7' ),
);
return $columns;
}
public function __construct() {
parent::__construct( array(
'singular' => 'post',
'plural' => 'posts',
'ajax' => false,
) );
}
public function prepare_items() {
$current_screen = get_current_screen();
$per_page = $this->get_items_per_page( 'wpcf7_contact_forms_per_page' );
$args = array(
'posts_per_page' => $per_page,
'orderby' => 'title',
'order' => 'ASC',
'offset' => ( $this->get_pagenum() - 1 ) * $per_page,
);
if ( $search_keyword = wpcf7_superglobal_request( 's' ) ) {
$args['s'] = $search_keyword;
}
if ( $order_by = wpcf7_superglobal_request( 'orderby' ) ) {
$args['orderby'] = $order_by;
}
if (
$order = wpcf7_superglobal_request( 'order' ) and
'desc' === strtolower( $order )
) {
$args['order'] = 'DESC';
}
$this->items = WPCF7_ContactForm::find( $args );
$total_items = WPCF7_ContactForm::count();
$total_pages = ceil( $total_items / $per_page );
$this->set_pagination_args( array(
'total_items' => $total_items,
'total_pages' => $total_pages,
'per_page' => $per_page,
) );
}
public function get_columns() {
return get_column_headers( get_current_screen() );
}
protected function get_sortable_columns() {
$columns = array(
'title' => array( 'title', true ),
'author' => array( 'author', false ),
'date' => array( 'date', false ),
);
return $columns;
}
protected function get_bulk_actions() {
$actions = array(
'delete' => __( 'Delete', 'contact-form-7' ),
);
return $actions;
}
protected function column_default( $item, $column_name ) {
return '';
}
public function column_cb( $item ) {
return sprintf(
'<input type="checkbox" name="%1$s[]" value="%2$s" />',
$this->_args['singular'],
$item->id()
);
}
public function column_title( $item ) {
$edit_link = add_query_arg(
array(
'post' => absint( $item->id() ),
'action' => 'edit',
),
menu_page_url( 'wpcf7', false )
);
$output = sprintf(
'<a class="row-title" href="%1$s" aria-label="%2$s">%3$s</a>',
esc_url( $edit_link ),
esc_attr( sprintf(
/* translators: %s: title of contact form */
__( 'Edit &#8220;%s&#8221;', 'contact-form-7' ),
$item->title()
) ),
esc_html( $item->title() )
);
$output = sprintf( '<strong>%s</strong>', $output );
if ( wpcf7_validate_configuration()
and current_user_can( 'wpcf7_edit_contact_form', $item->id() ) ) {
$config_validator = new WPCF7_ConfigValidator( $item );
$config_validator->restore();
if ( $count_errors = $config_validator->count_errors() ) {
$error_notice = sprintf(
/* translators: %s: number of errors detected */
_n(
'%s configuration error detected',
'%s configuration errors detected',
$count_errors, 'contact-form-7' ),
number_format_i18n( $count_errors )
);
$output .= sprintf(
'<div class="config-error"><span class="icon-in-circle" aria-hidden="true">!</span> %s</div>',
$error_notice
);
}
}
return $output;
}
protected function handle_row_actions( $item, $column_name, $primary ) {
if ( $column_name !== $primary ) {
return '';
}
$edit_link = add_query_arg(
array(
'post' => absint( $item->id() ),
'action' => 'edit',
),
menu_page_url( 'wpcf7', false )
);
$actions = array(
'edit' => wpcf7_link( $edit_link, __( 'Edit', 'contact-form-7' ) ),
);
if ( current_user_can( 'wpcf7_edit_contact_form', $item->id() ) ) {
$copy_link = add_query_arg(
array(
'post' => absint( $item->id() ),
'action' => 'copy',
),
menu_page_url( 'wpcf7', false )
);
$copy_link = wp_nonce_url(
$copy_link,
'wpcf7-copy-contact-form_' . absint( $item->id() )
);
$actions = array_merge( $actions, array(
'copy' => wpcf7_link( $copy_link, __( 'Duplicate', 'contact-form-7' ) ),
) );
}
return $this->row_actions( $actions );
}
public function column_author( $item ) {
$post = get_post( $item->id() );
if ( ! $post ) {
return;
}
$author = get_userdata( $post->post_author );
if ( false === $author ) {
return;
}
return esc_html( $author->display_name );
}
public function column_shortcode( $item ) {
$shortcodes = array( $item->shortcode() );
$output = '';
foreach ( $shortcodes as $shortcode ) {
$output .= "\n" . sprintf(
'<span class="shortcode"><input %s /></span>',
wpcf7_format_atts( array(
'type' => 'text',
'readonly' => true,
'value' => $shortcode,
'class' => 'large-text code selectable',
) )
);
}
return trim( $output );
}
public function column_date( $item ) {
$datetime = get_post_datetime( $item->id() );
if ( false === $datetime ) {
return '';
}
$t_time = sprintf(
/* translators: 1: date, 2: time */
__( '%1$s at %2$s', 'contact-form-7' ),
/* translators: date format, see https://www.php.net/date */
$datetime->format( __( 'Y/m/d', 'contact-form-7' ) ),
/* translators: time format, see https://www.php.net/date */
$datetime->format( __( 'g:i a', 'contact-form-7' ) )
);
return $t_time;
}
}

View File

@@ -1,180 +0,0 @@
<?php
add_action( 'wpcf7_admin_menu', 'wpcf7_admin_init_bulk_cv', 10, 0 );
function wpcf7_admin_init_bulk_cv() {
if (
! wpcf7_validate_configuration() or
! current_user_can( 'wpcf7_edit_contact_forms' )
) {
return;
}
$result = WPCF7::get_option( 'bulk_validate' );
$last_important_update = WPCF7_ConfigValidator::last_important_update;
if (
! empty( $result['version'] ) and
version_compare( $last_important_update, $result['version'], '<=' )
) {
return;
}
add_filter( 'wpcf7_admin_menu_change_notice',
'wpcf7_admin_menu_change_notice_bulk_cv',
10, 1
);
add_action( 'wpcf7_admin_warnings',
'wpcf7_admin_warnings_bulk_cv',
5, 3
);
}
function wpcf7_admin_menu_change_notice_bulk_cv( $counts ) {
$counts['wpcf7'] += 1;
return $counts;
}
function wpcf7_admin_warnings_bulk_cv( $page, $action, $object ) {
if ( 'wpcf7' === $page and 'validate' === $action ) {
return;
}
wp_admin_notice(
sprintf(
'%1$s &raquo; %2$s',
__( 'Misconfiguration leads to mail delivery failure or other troubles. Validate your contact forms now.', 'contact-form-7' ),
wpcf7_link(
add_query_arg(
array( 'action' => 'validate' ),
menu_page_url( 'wpcf7', false )
),
__( 'Validate Contact Form 7 Configuration', 'contact-form-7' )
)
),
array( 'type' => 'warning' )
);
}
add_action( 'wpcf7_admin_load', 'wpcf7_load_bulk_validate_page', 10, 2 );
function wpcf7_load_bulk_validate_page( $page, $action ) {
if (
'wpcf7' !== $page or
'validate' !== $action or
! wpcf7_validate_configuration() or
'POST' !== wpcf7_superglobal_server( 'REQUEST_METHOD' )
) {
return;
}
check_admin_referer( 'wpcf7-bulk-validate' );
if ( ! current_user_can( 'wpcf7_edit_contact_forms' ) ) {
wp_die( wp_kses_data( __( 'You are not allowed to validate configuration.', 'contact-form-7' ) ) );
}
$contact_forms = WPCF7_ContactForm::find();
$result = array(
'timestamp' => time(),
'version' => WPCF7_VERSION,
'count_valid' => 0,
'count_invalid' => 0,
);
foreach ( $contact_forms as $contact_form ) {
$config_validator = new WPCF7_ConfigValidator( $contact_form );
$config_validator->validate();
$config_validator->save();
if ( $config_validator->is_valid() ) {
$result['count_valid'] += 1;
} else {
$result['count_invalid'] += 1;
}
}
WPCF7::update_option( 'bulk_validate', $result );
$redirect_to = add_query_arg(
array(
'message' => 'validated',
),
menu_page_url( 'wpcf7', false )
);
wp_safe_redirect( $redirect_to );
exit();
}
function wpcf7_admin_bulk_validate_page() {
$contact_forms = WPCF7_ContactForm::find();
$count = WPCF7_ContactForm::count();
$submit_text = sprintf(
/* translators: %s: number of contact forms */
_n(
'Validate %s contact form now',
'Validate %s contact forms now',
$count, 'contact-form-7'
),
number_format_i18n( $count )
);
$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',
) );
$formatter->append_start_tag( 'h1' );
$formatter->append_preformatted(
esc_html( __( 'Validate Configuration', 'contact-form-7' ) )
);
$formatter->end_tag( 'h1' );
$formatter->append_start_tag( 'form', array(
'method' => 'post',
'action' => '',
) );
$formatter->append_start_tag( 'p' );
$formatter->call_user_func( static function () {
wp_nonce_field( 'wpcf7-bulk-validate' );
} );
$formatter->append_start_tag( 'input', array(
'type' => 'hidden',
'name' => 'action',
'value' => 'validate',
) );
$formatter->append_start_tag( 'input', array(
'type' => 'submit',
'class' => 'button',
'value' => $submit_text,
) );
$formatter->end_tag( 'form' );
$formatter->append_preformatted(
wpcf7_link(
__( 'https://contactform7.com/configuration-validator-faq/', 'contact-form-7' ),
__( 'FAQ about Configuration Validator', 'contact-form-7' )
)
);
$formatter->print();
}

Some files were not shown because too many files have changed in this diff Show More